UNPKG

penguins-eggs

Version:

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

321 lines (320 loc) 12.9 kB
/** * Krill Installer - Simplified Refactoring * ./src/krill/prepare.ts * penguins-eggs v.25.7.x / ecmascript 2020 * author: Piero Proietti * email: piero.proietti@gmail.com * license: MIT */ import axios from 'axios'; import fs from 'fs'; import os from 'os'; 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, shx } from '../../lib/utils.js'; import { FsType, InstallationMode, SwapChoice } from './krill_enums.js'; import { keyboard } from './prepare.d/keyboard.js'; import { location } from './prepare.d/location.js'; import { network } from './prepare.d/network.js'; import { partitions } from './prepare.d/partitions.js'; import { summary } from './prepare.d/summary.js'; import { users } from './prepare.d/users.js'; // UI Components import { welcome } from './prepare.d/welcome.js'; import Sequence from './sequence.js'; const config_file = '/etc/penguins-eggs.d/krill.yaml'; /** * Main Krill installer class - Simplified refactoring */ export default class Krill { chroot = false; halt = false; keyboard = keyboard; keyboards = new Keyboards(); // Configuration krillConfig = {}; locales = new Locales(); location = location; network = network; nointeractive = false; partitions = partitions; summary = summary; // Installation flags unattended = false; users = users; // UI Components welcome = welcome; /** * 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, replace = '', 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, replace) : await this.runInteractiveMode(crypted, pve, btrfs, replace); // Install await this.performInstallation(finalConfigs, domain, testing, verbose); } catch (error) { await Utils.pressKeyToExit(`${error.message}\nkrill installer refuses to continue`); process.exit(); } } /** * Apply options for unattended installation */ applyUnattendedOptions(configs, crypted, pve, btrfs, replace = '') { const { oPartitions, oUsers } = configs; // Set defaults for unattended oPartitions.installationMode = InstallationMode.EraseDisk; if (replace != '') { oPartitions.installationMode = InstallationMode.Replace; oPartitions.replacedPartition = replace; oPartitions.filesystemType = 'ext4'; } // 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 cmd = `lsblk -d -n -p -o NAME,RM,RO,TYPE | awk '$2 == 0 && $3 == 0 && $4 == "disk" {print $1}'`; const result = shx.exec(cmd, { silent: true }).stdout.trim(); const drives = result ? result.split('\n') : []; if (drives.length > 0) { oPartitions.installationDevice = drives[0]; } else { console.error('[Krll] No suitable disc found for installation. Debug info:'); shx.exec('lsblk -o NAME,RM,RO,TYPE,SIZE,MODEL', { silent: false }); throw new Error('Unable to find installation drive'); } } return configs; } /** * 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.slice(0, Math.max(0, timeZone.indexOf('/'))); this.krillConfig.zone = timeZone.slice(Math.max(0, timeZone.indexOf('/') + 1)); } } catch (error) { console.error('Error auto-configuring timezone:', error); } } /** * 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 = { keyboardLayout: this.krillConfig.keyboardLayout, keyboardModel: this.krillConfig.keyboardModel, keyboardOption: this.krillConfig.keyboardOption, keyboardVariant: this.krillConfig.keyboardVariant }; let { userSwapChoice } = this.krillConfig; if (suspend) userSwapChoice = SwapChoice.Suspend; else if (small) userSwapChoice = SwapChoice.Small; else if (none) userSwapChoice = SwapChoice.None; const oPartitions = { filesystemType: this.krillConfig.filesystemType, installationDevice: this.krillConfig.installationDevice, installationMode: this.krillConfig.installationMode, replacedPartition: this.krillConfig.replacedPartition, userSwapChoice }; const oUsers = { autologin: this.krillConfig.autologin, fullname: this.krillConfig.fullname, hostname, password: this.krillConfig.password, rootPassword: this.krillConfig.rootPassword, username: this.krillConfig.name }; const oNetwork = { address: Utils.address(), addressType: this.krillConfig.addressType, dns: Utils.getDns(), domain: Utils.getDomain(), gateway: Utils.gateway(), iface: await Utils.iface(), netmask: Utils.netmask() }; return { oKeyboard, oLocation, oNetwork, oPartitions, oUsers, oWelcome }; } /** * 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.'); } } /** * 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}`); } /** * Generate hostname based on options */ generateHostname(ip, random) { let { hostname } = this.krillConfig; if (hostname === '') { hostname = shx.exec('cat /etc/hostname', { silent: true }).stdout.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 }).stdout.trim(); const n = shx.exec(`tr -dc 0-9 </dev/urandom | head -c 3 ; echo ''`, { silent: true }).stdout.trim(); const sl = shx.exec(`tr -dc a-z </dev/urandom | head -c 2 ; echo ''`, { silent: true }).stdout.trim(); hostname = `${os.hostname()}-${fl}${n}${sl}`; } return hostname; } /** * Perform installation or testing */ async performInstallation(configs, domain, testing, verbose) { const { oKeyboard, oLocation, oNetwork, oPartitions, oUsers } = 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); } } /** * 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'; } /** * Run interactive mode with UI */ async runInteractiveMode(crypted, pve, btrfs, replace) { 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, replace); const oUsers = await this.users(); const oNetwork = await this.network(); return { oKeyboard, oLocation, oNetwork, oPartitions, oUsers, oWelcome }; } /** * 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(); } /** * Stop system services */ async stopSystemServices(verbose, testing) { const systemdCtl = new Systemctl(verbose); if ((await systemdCtl.isActive('udisks2.service')) && !testing) { await systemdCtl.stop('udisks2.service'); } } } /** * 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'); }