UNPKG

penguins-eggs

Version:

A remaster system tool, compatible with Arch, Debian, Devuan, Ubuntu and others

315 lines (314 loc) 12.4 kB
/** * Krill Installer - Simplified Refactoring * ./src/krill/prepare.ts * penguins-eggs v.10.0.0 / ecmascript 2020 * author: Piero Proietti * email: piero.proietti@gmail.com * license: MIT */ import os from 'os'; import fs from 'fs'; import shx from 'shelljs'; import axios from 'axios'; import { SwapChoice, InstallationMode, FsType } from './krill_enums.js'; import Keyboards from '../../classes/keyboards.js'; import Locales from '../../classes/locales.js'; import Systemctl from '../../classes/systemctl.js'; import Utils from '../../classes/utils.js'; import { exec } from '../../lib/utils.js'; import Sequence from './sequence.js'; // UI Components import { welcome } from './prepare.d/welcome.js'; import { location } from './prepare.d/location.js'; import { keyboard } from './prepare.d/keyboard.js'; import { partitions } from './prepare.d/partitions.js'; import { users } from './prepare.d/users.js'; import { network } from './prepare.d/network.js'; import { summary } from './prepare.d/summary.js'; const config_file = '/etc/penguins-eggs.d/krill.yaml'; /** * Main Krill installer class - Simplified refactoring */ export default class Krill { // UI Components welcome = welcome; location = location; keyboard = keyboard; partitions = partitions; users = users; network = network; summary = summary; // Configuration krillConfig = {}; locales = new Locales(); keyboards = new Keyboards(); // Installation flags unattended = false; nointeractive = false; chroot = false; halt = false; /** * Constructor */ constructor(unattended = false, nointeractive = false, halt = false, chroot = false) { this.unattended = unattended; this.nointeractive = nointeractive; this.chroot = chroot; this.halt = halt; } /** * Main prepare method - Simplified and cleaner */ async prepare(krillConfig = {}, ip = false, random = false, domain = '', suspend = false, small = false, none = false, crypted = false, pve = false, btrfs = false, testing = false, verbose = false) { try { // System validation await this.checkSystemRequirements(); // Configuration setup this.krillConfig = krillConfig; await this.setupConfiguration(); // Stop services await this.stopSystemServices(verbose, testing); // Build configurations const configs = await this.buildConfigurations(ip, random, suspend, small, none); // Interactive or unattended mode const finalConfigs = this.unattended ? this.applyUnattendedOptions(configs, crypted, pve, btrfs) : await this.runInteractiveMode(crypted, pve, btrfs); // Install await this.performInstallation(finalConfigs, domain, testing, verbose); } catch (error) { await Utils.pressKeyToExit(`${error.message}\nkrill installer refuses to continue`); process.exit(); } } /** * Check system requirements (disks, LVM) */ async checkSystemRequirements() { // Check disk presence const drives = shx.exec('lsblk |grep disk|cut -f 1 "-d "', { silent: true }).stdout.trim().split('\n'); if (drives[0] === '') { throw new Error('No disk to install the system in this machine.'); } // Check LVM presence if (await this.pvExist()) { await this.createLvmRemovalScript(); throw new Error('There is a lvm2 volume in the system, remove it manually before installation.'); } } /** * Setup configuration files and auto-configure timezone */ async setupConfiguration() { // Check config file if (!fs.existsSync(config_file)) { throw new Error(`Cannot find configuration file ${config_file}`); } // Check calamares/krill configuration let configRoot = '/etc/penguins-eggs.d/krill/'; if (fs.existsSync('/etc/calamares/settings.conf')) { configRoot = '/etc/calamares/'; } if (!fs.existsSync(configRoot + 'settings.conf')) { throw new Error('Cannot find calamares/krill configuration file, please create it running: sudo eggs calamares'); } // Auto-configure timezone await this.autoConfigureTimezone(); } /** * Auto-configure timezone from internet */ async autoConfigureTimezone() { try { const response = await axios.get('https://geoip.kde.org/v1/calamares'); if (response.statusText === 'OK') { const timeZone = response.data.time_zone; this.krillConfig.region = timeZone.substring(0, timeZone.indexOf('/')); this.krillConfig.zone = timeZone.substring(timeZone.indexOf('/') + 1); } } catch (error) { console.error('Error auto-configuring timezone:', error); } } /** * Stop system services */ async stopSystemServices(verbose, testing) { const systemdCtl = new Systemctl(verbose); if (await systemdCtl.isActive('udisks2.service') && !testing) { await systemdCtl.stop('udisks2.service'); } } /** * Build default configurations */ async buildConfigurations(ip, random, suspend, small, none) { // Generate hostname const hostname = this.generateHostname(ip, random); // Build configuration objects const oWelcome = { language: this.krillConfig.language }; const oLocation = { language: this.krillConfig.language, region: this.krillConfig.region, zone: this.krillConfig.zone }; const oKeyboard = { keyboardModel: this.krillConfig.keyboardModel, keyboardLayout: this.krillConfig.keyboardLayout, keyboardVariant: this.krillConfig.keyboardVariant, keyboardOption: this.krillConfig.keyboardOption }; let userSwapChoice = this.krillConfig.userSwapChoice; if (suspend) userSwapChoice = SwapChoice.Suspend; else if (small) userSwapChoice = SwapChoice.Small; else if (none) userSwapChoice = SwapChoice.None; const oPartitions = { installationDevice: this.krillConfig.installationDevice, installationMode: this.krillConfig.installationMode, filesystemType: this.krillConfig.filesystemType, userSwapChoice, replacedPartition: this.krillConfig.replacedPartition }; const oUsers = { username: this.krillConfig.name, fullname: this.krillConfig.fullname, password: this.krillConfig.password, rootPassword: this.krillConfig.rootPassword, autologin: this.krillConfig.autologin, hostname }; const oNetwork = { iface: await Utils.iface(), addressType: this.krillConfig.addressType, address: Utils.address(), netmask: Utils.netmask(), gateway: Utils.gateway(), dns: Utils.getDns(), domain: Utils.getDomain() }; return { oWelcome, oLocation, oKeyboard, oPartitions, oUsers, oNetwork }; } /** * Generate hostname based on options */ generateHostname(ip, random) { let hostname = this.krillConfig.hostname; if (hostname === '') { hostname = shx.exec('cat /etc/hostname', { silent: true }).trim(); } if (ip) { hostname = 'ip-' + Utils.address().replaceAll('.', '-'); } if (random) { const fl = shx.exec(`tr -dc a-z </dev/urandom | head -c 2 ; echo ''`, { silent: true }).trim(); const n = shx.exec(`tr -dc 0-9 </dev/urandom | head -c 3 ; echo ''`, { silent: true }).trim(); const sl = shx.exec(`tr -dc a-z </dev/urandom | head -c 2 ; echo ''`, { silent: true }).trim(); hostname = `${os.hostname()}-${fl}${n}${sl}`; } return hostname; } /** * Apply options for unattended installation */ applyUnattendedOptions(configs, crypted, pve, btrfs) { const { oPartitions, oUsers } = configs; // Set defaults for unattended oPartitions.installationMode = InstallationMode.EraseDisk; // Apply options if (btrfs) oPartitions.filesystemType = FsType.btrfs; if (crypted || pve) oPartitions.installationMode = InstallationMode.Luks; // Set default installation device if empty if (oPartitions.installationDevice === '') { const drives = shx.exec('lsblk |grep disk|cut -f 1 "-d "', { silent: true }).stdout.trim().split('\n'); if (drives.length > 0) { oPartitions.installationDevice = `/dev/` + drives[0]; } else { throw new Error("Unable to find installation drive"); } } return configs; } /** * Run interactive mode with UI */ async runInteractiveMode(crypted, pve, btrfs) { const oWelcome = await this.welcome(); const oLocation = await this.location(oWelcome.language); const oKeyboard = await this.keyboard(); const oPartitions = await this.partitions(this.krillConfig.installationDevice, crypted, pve, btrfs); const oUsers = await this.users(); const oNetwork = await this.network(); return { oWelcome, oLocation, oKeyboard, oPartitions, oUsers, oNetwork }; } /** * Perform installation or testing */ async performInstallation(configs, domain, testing, verbose) { const { oLocation, oKeyboard, oPartitions, oUsers, oNetwork } = configs; await this.summary(oLocation, oKeyboard, oPartitions, oUsers); if (testing) { console.log(); Utils.titles("install --testing"); console.log("Just testing krill, the process will end!"); console.log("^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^"); console.log(oPartitions); process.exit(); } else { const sequence = new Sequence(oLocation, oKeyboard, oPartitions, oUsers, oNetwork); await sequence.start(domain, this.unattended, this.nointeractive, this.chroot, this.halt, verbose); } } /** * Create script to remove LVM partitions */ async createLvmRemovalScript() { const scriptName = "removeLvmPartitions"; let cmds = "#!/bin/bash\n"; cmds += `# remove LV (Logical Volumes)\n`; cmds += `vg=$(vgs --noheadings -o vg_name| awk '{$1=$1};1')\n`; cmds += `lvs -o lv_name --noheadings | awk '{$1=$1};1' | while read -r lv; do\n`; cmds += ` lvremove -y /dev/mapper/$vg-$lv\n`; cmds += `done\n`; cmds += `\n`; cmds += `# remove VG (Volume groups)\n`; cmds += `vgremove --force $(vgs --noheadings -o vg_name $vg)\n`; cmds += `\n`; cmds += `# remove PV (Physical Volumes) \n`; cmds += `pv=$(pvs --noheading -o pv_name | awk '{$1=$1};1')\n`; cmds += `pvremove --force --force $pv\n`; cmds += `# wipe PV (Physical Volumes) \n`; cmds += `wipefs -a $pv\n`; cmds += `# clean device\n`; cmds += `sgdisk --zap-all $pv\n`; cmds += `dd if=/dev/zero of=$pv bs=1M count=10\n`; fs.writeFileSync(scriptName, cmds); await exec(`chmod +x ${scriptName}`); } /** * Check if Physical Volumes exist */ async pvExist() { const check = `#!/bin/sh\npvdisplay |grep "PV Name" >/dev/null && echo 1|| echo 0`; return shx.exec(check, { silent: true }).stdout.trim() === '1'; } } /** * Utility function for netmask conversion */ function netmask2CIDR(mask) { const countCharOccurences = (string, char) => string.split(char).length - 1; const decimalToBinary = (dec) => (dec >>> 0).toString(2); const getNetMaskParts = (nmask) => nmask.split('.').map(Number); return countCharOccurences(getNetMaskParts(mask) .map(part => decimalToBinary(part)) .join(''), '1'); }