UNPKG

penguins-eggs

Version:

A remaster system tool, compatible with Almalinux, Alpine, Arch, Debian, Devuan, Fedora, Manjaro, Opensuse, Ubuntu and derivatives

571 lines (570 loc) 22.9 kB
/** * ./src/classes/tailor.ts * penguins-eggs v.25.7.x / ecmascript 2020 * author: Piero Proietti * email: piero.proietti@gmail.com * license: MIT */ import chalk from 'chalk'; import yaml from 'js-yaml'; import fs from 'node:fs'; import path from 'node:path'; import { exec } from '../lib/utils.js'; import Distro from './distro.js'; import Pacman from './pacman.js'; import SourcesList from './sources_list.js'; import Utils from './utils.js'; /** * */ export default class Tailor { materials = {}; category = 'costume'; costume = ''; echo = {}; log = ''; toNull = ' > /dev/null 2>&1'; verbose = false; wardrobe = ''; /** * @param wardrobe * @param costume */ constructor(costume, category = 'costume') { this.costume = costume; this.wardrobe = path.dirname(path.dirname(costume)); this.log = path.dirname(this.wardrobe) + '/wardrobe.log'; this.category = category; } /** * * @param packages * @param verbose * @param section * @returns */ async packagesExists(wanted) { Utils.warning(`checking packages exists ${this.costume}`); wanted.sort(); const distro = new Distro(); let cmd = ''; switch (distro.familyId) { case 'alpine': { cmd = `apk search | awk -F'-[0-9]' '{print $1}' | sort -u`; break; } case 'archlinux': { cmd = `pacman -S --list | awk '{print $2}'`; break; } case 'debian': { // cmd=`apt-cache --no-generate pkgnames` cmd = `apt-cache pkgnames`; break; } case 'fedora': { cmd = `dnf list --available | awk '{print $1}' | sed 's/\.[^.]*$//'`; break; } case 'opensuse': { // controllare // questo funziona diretto cmd = `zypper --non-interactive packages | cut -d '|' -f 3 | sed '1,2d' | sed '/^$/d' | sort -u`; break; } // No default } let available = []; const result = await exec(cmd, { capture: true, echo: false, ignore: false }); // trim di tutto per eseguire il confronto available = result.data.split('\n').map((line) => line.trim()); // precedente // available = (await exec(cmd, { capture: true, echo: false, ignore: false })).data.split('\n') available.sort(); wanted.sort(); const exists = []; const not_exists = []; for (const elem of wanted) { if (available.includes(elem)) { exists.push(elem); } else { not_exists.push(elem); fs.appendFileSync(this.log, `- ${elem}\n`); } } if (not_exists.length > 0) { console.log(`${this.materials.name}, ${not_exists.length} following packages was not found:`); for (const elem of not_exists) { console.log(`-${elem}`); } console.log(); console.log('Wait 3 seconds'); await sleep(3000); } return exists; } /** * - check if every package if installed * - if find any packages to install, install it */ async packagesInstall(packages, comment = 'packages', cmd = 'apt-get install -yqq ') { Utils.warning(`installing existing packages ${this.costume}`); if (packages[0] !== null) { const elements = []; let strElements = ''; for (const elem of packages) { elements.push(elem); cmd += ` ${elem}`; strElements += `, ${elem}`; } if (elements.length > 0) { let step = `installing ${comment}: `; if (!this.verbose) { step += strElements.slice(2); } /** * prova 3 volte */ const limit = 3; for (let tempts = 1; tempts < limit; tempts++) { Utils.titles(step); Utils.warning(`tempts ${tempts} of ${limit}`); if (await this.tryCheckSuccess(cmd, this.echo)) { break; } } } } } /** * */ async prepare(verbose = true, no_accessories = false, no_firmwares = false) { if (verbose) { this.verbose = true; this.toNull = ''; } this.echo = Utils.setEcho(verbose); Utils.warning(`preparing ${this.costume}`); if (!fs.existsSync(this.log)) { fs.writeFileSync(this.log, '# eggs wardrobe wear\n\n'); } fs.appendFileSync(this.log, `## ${this.costume}\n`); fs.appendFileSync(this.log, `Packages not found:\n`); /** * check curl presence */ if (!Pacman.packageIsInstalled('curl')) { Utils.pressKeyToExit('In this tailoring shop we use curl. Install it with your package manager'); process.exit(); } // Analyze distro const distro = new Distro(); let tailorList = ''; switch (distro.distroLike) { case 'Almalinux': case 'Fedora': case 'Rocky': { tailorList = `${this.costume}/fedora.yaml`; if (!fs.existsSync(tailorList)) { tailorList = `${this.costume}/debian.yaml`; if (!fs.existsSync(tailorList)) { console.log(`no costume definition found compatible Fedora`); process.exit(); } } break; } case 'Alpine': { tailorList = `${this.costume}/alpine.yaml`; if (!fs.existsSync(tailorList)) { tailorList = `${this.costume}/debian.yaml`; if (!fs.existsSync(tailorList)) { console.log(`no costume definition found compatible Alpine`); process.exit(); } } break; } case 'Arch': { tailorList = `${this.costume}/arch.yaml`; if (!fs.existsSync(tailorList)) { tailorList = `${this.costume}/debian.yaml`; if (!fs.existsSync(tailorList)) { console.log(`no costume definition found compatible Arch`); process.exit(); } } break; } case 'Debian': { tailorList = `${this.costume}/debian.yaml`; if (!fs.existsSync(tailorList)) { tailorList = `${this.costume}/devuan.yaml`; if (!fs.existsSync(tailorList)) { tailorList = `${this.costume}/ubuntu.yaml`; if (!fs.existsSync(tailorList)) { console.log(`no costume definition found compatible Debian`); process.exit(); } } } break; } case 'Devuan': { tailorList = `${this.costume}/devuan.yaml`; if (!fs.existsSync(tailorList)) { tailorList = `${this.costume}/debian.yaml`; if (!fs.existsSync(tailorList)) { tailorList = `${this.costume}/ubuntu.yaml`; if (!fs.existsSync(tailorList)) { console.log(`no costume definition found compatible Devuan`); process.exit(); } } } break; } case 'Opensuse': { tailorList = `${this.costume}/opensuse.yaml`; if (!fs.existsSync(tailorList)) { tailorList = `${this.costume}/debian.yaml`; if (!fs.existsSync(tailorList)) { console.log(`no costume definition found compatible opensuse`); process.exit(); } } break; } case 'Ubuntu': { tailorList = `${this.costume}/ubuntu.yaml`; if (!fs.existsSync(tailorList)) { tailorList = `${this.costume}/debian.yaml`; console.log(`trying ` + tailorList); if (!fs.existsSync(tailorList)) { tailorList = `${this.costume}/devuan.yaml`; console.log(`trying ` + tailorList); if (!fs.existsSync(tailorList)) { console.log(`no costume definition found compatible Ubuntu`); process.exit(); } } } break; } } // end analyze /** * find materials */ if (fs.existsSync(tailorList)) { this.materials = yaml.load(fs.readFileSync(tailorList, 'utf8')); } else { switch (this.category) { case 'accessory': { Utils.titles(`${this.category}: ${this.costume}`); console.log("Tailor's list " + chalk.cyan(tailorList) + ' is not found \non your wardrobe ' + chalk.cyan(this.wardrobe) + '.\n'); console.log('vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv'); console.log('Accessory will not be installed,'); console.log('operations will continue.'); console.log('^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^'); sleep(3000); return; } case 'costume': { Utils.titles(`${this.category}: ${this.costume}`); console.log("Tailor's list " + chalk.cyan(tailorList) + ' is not found \non your wardrobe ' + chalk.cyan(this.wardrobe) + '.\n'); console.log('Costume will not be installed, operations will abort.\n'); Utils.pressKeyToExit(); process.exit(); break; } } } /** * sequence */ const sources_list = new SourcesList(); let step = ''; if (this.materials.sequence !== undefined) { step = 'analyzing sequence'; Utils.warning(step); /** * sequence/repositories */ if (this.materials.sequence.repositories !== undefined) { if (distro.familyId === 'debian') { /** * sequence/repositories/sources_list */ // evito di fallire se sources_list non è presente if (this.materials.sequence.repositories.sources_list !== undefined) { step = 'analyzing repositories'; Utils.warning(step); if (distro.distroLike === 'Debian') { await sources_list.components(this.materials.sequence.repositories.sources_list); } } /** * sequence/repositories/sources_list_d */ if (this.materials.sequence.repositories.sources_list_d !== undefined && this.materials.sequence.repositories.sources_list_d[0] !== null) { step = 'adding repositories to /etc/apt/sources_list_d'; Utils.warning(step); for (const cmd of this.materials.sequence.repositories.sources_list_d) { try { // repeat 3 times if fail curl or others commands for (let i = 0; i < 2; i++) { const result = await exec(cmd, this.echo); if (result.code === 0) { break; } } } catch (error) { await Utils.pressKeyToExit(JSON.stringify(error)); } } } } /** * sequence/repositories/update */ if (this.materials.sequence.repositories.update === undefined) { console.log('repositiories, and repositories.update MUST be defined on sequence'); process.exit(); } step = 'repositories update'; Utils.warning(step); if (this.materials.sequence.repositories.update) { switch (distro.familyId) { case 'alpine': { await exec('apk update', Utils.setEcho(false)); break; } case 'archlinux': { await exec('pacman -Sy', Utils.setEcho(false)); break; } case 'debian': { await exec('apt-get update', Utils.setEcho(false)); break; } case 'fedora': { await exec('dnf check-update --refresh', Utils.setEcho(false)); break; } case 'opensuse': { await exec('zypper refresh', Utils.setEcho(true)); break; } } } /** * sequence/repositories/upgrade */ if (this.materials.sequence.repositories.upgrade !== undefined) { step = 'repositories upgrade'; Utils.warning(step); if (this.materials.sequence.repositories.upgrade) { switch (distro.familyId) { case 'alpine': { await exec('apk upgrade', Utils.setEcho(false)); break; } case 'archlinux': { await exec('pacman -Su', Utils.setEcho(false)); break; } case 'debian': { await exec('apt-get full-upgrade', Utils.setEcho(false)); break; } case 'fedora': { await exec('dnf upgrade', Utils.setEcho(false)); break; } case 'opensuse': { await exec('zypper update', Utils.setEcho(true)); break; } } } // upgrade true } // undefined upgrade } // end sequence/repositories /** * sequence/cmds */ if (this.materials.sequence.cmds !== undefined && Array.isArray(this.materials.sequence.cmds)) { step = 'sequence commands'; Utils.warning(step); for (const cmd of this.materials.sequence.cmds) { if (fs.existsSync(`${this.costume}/${cmd}`)) { // exec ./costume/cmd await exec(`${this.costume}/${cmd} `, Utils.setEcho(true)); } else { // exec cmd await exec(`${cmd}`, Utils.setEcho(true)); } } } /** * install packages */ if (this.materials.sequence.packages !== undefined) { const packages = await this.packagesExists(this.materials.sequence.packages); console.log(this.materials.sequence.packages); if (packages.length > 1) { switch (distro.familyId) { case 'alpine': { await this.packagesInstall(this.materials.sequence.packages, 'packages', `apk add`); break; } case 'archlinux': { await this.packagesInstall(this.materials.sequence.packages, 'packages', `pacman -Sy --noconfirm`); break; } case 'debian': { await this.packagesInstall(packages); break; } case 'fedora': { await this.packagesInstall(this.materials.sequence.packages, 'packages', `dnf install -y`); break; } case 'opensuse': { // Quanto mi è costato // await this.packagesInstall(this.materials.sequence.packages, 'packages', `zypper install --no-confirm`) await this.packagesInstall(this.materials.sequence.packages, 'packages', `zypper install --no-confirm`); break; } } } } /** * sequence/packages_python */ if (this.materials.sequence.packages_python !== undefined && Array.isArray(this.materials.sequence.packages_python)) { let cmd = 'pip install '; let pip = ''; for (const elem of this.materials.sequence.packages_python) { cmd += ` ${elem}`; pip += `, ${elem}`; } step = `installing python packages pip ${pip.slice(2)}`; Utils.warning(step); await exec(cmd, this.echo); } /** * sequence/accessories */ if (!no_accessories && this.materials.sequence.accessories !== undefined && Array.isArray(this.materials.sequence.accessories)) { step = 'wearing accessories'; for (const elem of this.materials.sequence.accessories) { if ((elem === 'firmwares' || elem === './firmwares') && no_firmwares) { continue; } if (elem.slice(0, 2) === './') { // local accessory const tailor = new Tailor(`${this.costume}/${elem.slice(2)}`, 'accessory'); await tailor.prepare(verbose); } else { // global accessory const tailor = new Tailor(`${this.wardrobe}/accessories/${elem}`, 'accessory'); await tailor.prepare(verbose); } } } // no-accessories } // end sequence /** * customize */ if (this.materials.finalize !== undefined) { /** * finalize/customize */ if (this.materials.finalize.customize && fs.existsSync(`/${this.costume}/sysroot`)) { step = `finalize/customize: copying ${this.costume}/sysroot/ to /`; Utils.warning(step); let cmd = `rsync -avx ${this.costume}/sysroot/* / ${this.toNull}`; await exec(cmd, this.echo); // chown cmd = `chown root:root /etc/sudoers.d /etc/skel -R`; await exec(cmd, this.echo); /** * Copyng skel in /home/user */ if (fs.existsSync(`${this.costume}/sysroot/etc/skel`)) { const user = await Utils.getPrimaryUser(); step = `finalize/customize: copying skel in /home/${user}/`; Utils.warning(step); cmd = `rsync -avx ${this.costume}/sysroot/etc/skel/.config /home/${user}/ ${this.toNull}`; await exec(cmd, this.echo); await exec(`chown ${user}:${user} /home/${user}/ -R`); } } /** * finalize/cmds */ if (this.materials.finalize.cmds !== undefined && Array.isArray(this.materials.finalize.cmds)) { step = 'finalize/commands'; Utils.warning(step); for (const cmd of this.materials.finalize.cmds) { if (fs.existsSync(`${this.costume}/${cmd}`)) { // Qui passiamo ${this.materials.name} await exec(`${this.costume}/${cmd} ${this.materials.name}`, Utils.setEcho(true)); } else { // exec cmd real env await exec(`${cmd}`, Utils.setEcho(true)); } } } } // show log if (fs.existsSync(this.log)) { await exec(`cat ${this.log}`); } /** * reboot */ if (this.materials.reboot) { Utils.warning('Reboot'); await Utils.pressKeyToExit('system need to reboot', true); await exec('reboot'); } else { console.log(`You look good with: ${this.materials.name}`); } } /** * * @param cmd * @param echo * @returns */ async tryCheckSuccess(cmd, echo) { let success = false; try { await exec(cmd, Utils.setEcho(true)); success = true; } catch { success = false; } return success; } } /** * * @param ms * @returns */ function sleep(ms = 0) { return new Promise((resolve) => { setTimeout(resolve, ms); }); }