UNPKG

penguins-eggs

Version:

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

397 lines (396 loc) 16.4 kB
/** * Sequence - Simple Refactoring * ./src/krill/sequence.ts * penguins-eggs v.10.0.0 / ecmascript 2020 * author: Piero Proietti * email: piero.proietti@gmail.com * license: MIT */ import Settings from '../../classes/settings.js'; import React from 'react'; import { render, Box, Text } from 'ink'; import Install from '../components/install.js'; import Finished from '../components/finished.js'; import fs from 'fs'; import yaml from 'js-yaml'; import { installer } from '../../classes/incubation/installer.js'; import CliAutologin from '../../classes/cli-autologin.js'; import Distro from '../../classes/distro.js'; import Pacman from '../../classes/pacman.js'; import Utils from '../../classes/utils.js'; import Xdg from '../../classes/xdg.js'; import { exec } from '../../lib/utils.js'; // Import all modules (unchanged) import partition from './sequence.d/partition.js'; import biosStandard from './sequence.d/partition.d/bios_standard.js'; import biosLuks from './sequence.d/partition.d/bios_luks.js'; import uefiStandard from './sequence.d/partition.d/uefi_standard.js'; import uefiLuks from './sequence.d/partition.d/uefi_luks.js'; import { mountFs, umountFs } from './sequence.d/mount_fs.js'; import { mountVfs, umountVfs } from './sequence.d/mount_vfs.js'; import unpackfs from './sequence.d/unpackfs.js'; import machineId from './sequence.d/machine_id.js'; import fstab from './sequence.d/fstab.js'; import locale from './sequence.d/locale.js'; import mKeyboard from './sequence.d/m_keyboard.js'; import localeCfg from './sequence.d/locale_cfg.js'; import addUser from './sequence.d/add_user.js'; import changePassword from './sequence.d/change_password.js'; import networkCfg from './sequence.d/network_cfg.js'; import bootloaderConfig from './sequence.d/bootloader_config.js'; import grubcfg from './sequence.d/grubcfg.js'; import bootloader from './sequence.d/bootloader.js'; import packages from './sequence.d/packages.js'; import removeInstallerLink from './sequence.d/remove_installer_link.js'; import initramfsCfg from './sequence.d/initramfs_cfg.js'; import initramfs from './sequence.d/initramfs.js'; import delLiveUser from './sequence.d/del_live_user.js'; import umount from './sequence.d/umount.js'; import mkfs from './sequence.d/mkfs.js'; import hostname from './sequence.d/hostname.js'; import CFS from './cfs.js'; import Title from '../components/title.js'; import cliCursor from 'cli-cursor'; import { spawnSync } from 'child_process'; /** * Main Sequence class - Simple Refactoring */ export default class Sequence { // All module references (unchanged) partition = partition; partitionBiosStandard = biosStandard; partitionUefiStandard = uefiStandard; partitionBiosLuks = biosLuks; partitionUefiLuks = uefiLuks; mountFs = mountFs; mountVfs = mountVfs; unpackfs = unpackfs; machineId = machineId; fstab = fstab; locale = locale; keyboard = mKeyboard; localeCfg = localeCfg; addUser = addUser; changePassword = changePassword; networkCfg = networkCfg; bootloaderConfig = bootloaderConfig; grubcfg = grubcfg; bootloader = bootloader; packages = packages; removeInstallerLink = removeInstallerLink; initramfsCfg = initramfsCfg; initramfs = initramfs; delLiveUser = delLiveUser; umountFs = umountFs; umountVfs = umountVfs; umount = umount; mkfs = mkfs; hostname = hostname; // All properties (unchanged) installer = {}; installTarget = '/tmp/calamares-krill-root'; verbose = false; echo = {}; efi = false; devices = {}; users = {}; network = {}; partitions = {}; swapSize = 0; language = ''; region = ''; zone = ''; keyboardModel = ''; keyboardLayout = ''; keyboardVariant = ''; toNull = ' > /dev/null 2>&1'; spinner = true; settings = {}; remix = {}; distro = {}; luksName = 'luks-volume'; luksFile = ``; luksDevice = `/dev/mapper/${this.luksName}`; luksMountpoint = `/mnt`; is_clone = fs.existsSync('/etc/penguins-eggs.d/is_clone'); is_crypted_clone = fs.existsSync('/etc/penguins-eggs.d/is_crypted_clone'); unattended = false; nointeractive = false; chroot = false; halt = false; cliAutologin = new CliAutologin(); /** * Constructor (unchanged) */ constructor(location, keyboard, partitions, users, network) { this.installer = installer(); this.settings = new Settings(); this.language = location.language; this.region = location.region; this.zone = location.zone; this.keyboardModel = keyboard.keyboardModel; this.keyboardLayout = keyboard.keyboardLayout; this.keyboardVariant = keyboard.keyboardVariant; this.network = network; this.partitions = partitions; this.users = users; this.devices.efi = {}; this.devices.boot = {}; this.devices.root = {}; this.devices.data = {}; this.devices.swap = {}; this.distro = new Distro(); this.efi = fs.existsSync('/sys/firmware/efi/efivars'); this.luksFile = `${this.distro.liveMediumPath}live/${this.luksName}`; } /** * Helper method to execute a step with standard error handling */ async executeStep(message, percent, action) { await redraw(React.createElement(Install, { message: message, percent: percent, spinner: this.spinner })); try { await action(); } catch (error) { await this.showProblem(message, error); } } /** * Main start method - Much cleaner sequence */ async start(domain = '', unattended = false, nointeractive = false, chroot = false, halt = false, verbose = false) { // Setup (unchanged) await this.setupInstallation(domain, unattended, nointeractive, chroot, halt, verbose); // Installation sequence - each step clearly visible await this.runInstallationSequence(); // Handle chroot if requested if (chroot) { const message = `You are in chroot mode under ${this.installTarget}, type "exit" to exit.`; await this.emergencyShell(message); } // Completion await this.completeInstallation(); } /** * Setup installation parameters and environment */ async setupInstallation(domain, unattended, nointeractive, chroot, halt, verbose) { // Set domain if (domain !== '') { if (domain.at(0) !== '.') { domain = '.' + domain; } this.network.domain = domain; } // Arch-specific setup if (this.distro.familyId === 'archlinux') { if (this.distro.distroId === 'Manjarolinux') { await exec(`ln -s /run/miso/bootmnt/live/ /live`); } else { await exec(`ln -s /run/archiso/bootmnt/live/ /live`); } } // OpenSUSE-specific setup if (this.distro.familyId === 'opensuse') { await exec('dmsetup remove_all'); } // Set flags this.unattended = unattended; this.nointeractive = nointeractive; this.chroot = chroot; this.halt = halt; this.verbose = verbose; this.echo = Utils.setEcho(this.verbose); if (this.verbose) { this.toNull = ''; this.spinner = false; } await this.settings.load(); } /** * Main installation sequence - Linear and clear */ async runInstallationSequence() { // 1. Partitioning and formatting let isPartitioned = false; await this.executeStep("Creating partitions", 0, async () => { isPartitioned = await this.partition(); }); if (!isPartitioned) return; await this.executeStep("Formatting file system", 6, () => this.mkfs()); // 2. Mounting await this.executeStep("Mounting target file system", 9, async () => { await this.mountFs(); await sleep(500); // Give time to mount }); await this.executeStep("Mounting on target VFS", 12, () => this.mountVfs()); // 3. System setup await this.executeStep("Unpacking filesystem", 15, () => this.unpackfs()); // 4. Debian-specific steps if (this.distro.familyId === 'debian') { await this.executeStep("Force dpkg-unsafe-io", 40, () => this.execCalamaresModule('dpkg-unsafe-io')); await this.executeStep("Add sources-yolk", 43, () => this.execCalamaresModule('sources-yolk')); } // 5. Core system configuration await this.executeStep("machineid", 46, () => this.machineId()); await this.executeStep("Creating fstab", 49, () => this.fstab(this.partitions.installationDevice)); // 6. Crypted clone restoration if (this.is_crypted_clone) { await this.executeStep("Restore private data from crypted clone", 55, async () => { if (fs.existsSync(this.luksFile)) { const cmd = `eggs syncfrom --rootdir /tmp/calamares-krill-root/ --file ${this.luksFile}`; await exec(cmd, Utils.setEcho(true)); this.is_clone = true; } else { await Utils.pressKeyToExit(`Cannot find luks-volume file ${this.luksFile}`); } }); } // 7. Network and hostname await this.executeStep("Network configuration", 61, () => this.networkCfg()); await this.executeStep("Create hostname", 64, () => this.hostname(this.network.domain)); // 8. Debian cleanup if (this.distro.familyId === 'debian') { await this.executeStep("Remove dpkg-unsafe-io", 65, () => this.execCalamaresModule('dpkg-unsafe-io-undo')); } // 9. User configuration (only if not clone) if (!this.is_clone) { // Locale await this.executeStep("Locale", 70, async () => { if (this.distro.familyId === 'alpine' || this.distro.familyId === 'archlinux' || this.distro.familyId === 'debian') { await this.locale(); } }); await this.executeStep("Settings keyboard", 71, () => this.keyboard()); // Locale configuration if (this.distro.familyId === 'archlinux' || this.distro.familyId === 'debian') { await this.executeStep("Locale Configuration", 72, async () => { await this.localeCfg(); await exec("chroot " + this.installTarget + " locale-gen" + this.toNull); }); } await this.executeStep("Remove live user", 73, () => this.delLiveUser()); await this.executeStep(`Add user ${this.users.username}`, 74, () => this.addUser(this.users.username, this.users.password, this.users.fullname, '', '', '')); await this.executeStep("Add root password", 75, () => this.changePassword('root', this.users.rootPassword)); // GUI autologin if (Pacman.isInstalledGui()) { await this.executeStep("Autologin GUI", 78, async () => { if (this.users.autologin) { await Xdg.autologin(await Utils.getPrimaryUser(), this.users.username, this.installTarget); if (this.distro.distroLike === 'Arch') { await exec(`chroot ${this.installTarget} groupadd autologin ${this.toNull}`); await exec(`chroot ${this.installTarget} gpasswd -a ${this.users.username} autologin ${this.toNull}`); } } }); } } // 10. Always remove CLI autologin await this.executeStep("Remove autologin CLI", 80, () => this.cliAutologin.remove(this.installTarget)); // 11. Bootloader configuration await this.executeStep("bootloader-config", 81, () => this.bootloaderConfig()); await this.executeStep("grubcfg", 82, () => this.grubcfg()); await this.executeStep("bootloader", 83, () => this.bootloader()); // 12. Final system setup if (this.distro.familyId === 'debian') { await this.executeStep("Remove sources-yolk", 84, () => this.execCalamaresModule('sources-yolk-undo')); } await this.executeStep("Add/remove packages", 85, () => this.packages()); await this.executeStep("initramfs configure", 86, () => this.initramfsCfg(this.partitions.installationDevice)); await this.executeStep("initramfs", 87, () => this.initramfs()); await this.executeStep("Remove GUI installer link", 88, () => this.removeInstallerLink()); await this.executeStep("Cleanup", 89, async () => { await exec(`rm -f ${this.installTarget}/etc/penguins-eggs.d/is_clone`); await exec(`rm -f ${this.installTarget}/etc/penguins-eggs.d/is_crypted_clone`); }); // 13. Custom final steps const cfs = new CFS(); const steps = await cfs.steps(); if (steps.length > 0) { for (const step of steps) { await this.executeStep(`running ${step}`, 90, () => this.execCalamaresModule(step)); } } // 14. Unmounting await this.executeStep("umount Virtual File System", 96, () => this.umountVfs()); await this.executeStep("umount File system", 99, () => this.umountFs()); } /** * Complete installation with reboot/halt */ async completeInstallation() { await sleep(500); const cmd = this.halt ? "poweroff" : "reboot"; let message = `Press a key to ${cmd}`; if (this.unattended && this.nointeractive) { message = `System will ${cmd} in 5 seconds...`; } await redraw(React.createElement(Finished, { installationDevice: this.partitions.installationDevice, hostName: this.users.hostname, userName: this.users.username, message: message })); if (this.unattended && this.nointeractive) { await sleep(5000); } else { spawnSync('read _ ', { shell: true, stdio: [0, 1, 2] }); } await exec(cmd, { echo: false }); } // Keep all existing methods unchanged async execCalamaresModule(name) { const moduleName = this.installer.multiarchModules + name + '/module.desc'; if (fs.existsSync(moduleName)) { const calamaresModule = yaml.load(fs.readFileSync(moduleName, 'utf8')); let command = calamaresModule.command; if (command !== '' || command !== undefined) { command += this.toNull; await exec(command, this.echo); } } } async emergencyShell(message) { try { await redraw(React.createElement(React.Fragment, null, React.createElement(Title, null), React.createElement(Box, null, React.createElement(Text, null, message)))); cliCursor.show(); await exec(`chroot ${this.installTarget} /bin/bash`); cliCursor.hide(); } catch (error) { await Utils.pressKeyToExit(JSON.stringify(error)); } } async showProblem(message, currErr) { message = `We was on "${message}", get error: ${JSON.stringify(currErr)}, type "exit" to exit from krill emergency shell.`; try { await redraw(React.createElement(React.Fragment, null, React.createElement(Title, null), React.createElement(Box, null, React.createElement(Text, null, message)))); cliCursor.show(); await exec("/bin/bash"); cliCursor.hide(); } catch (error) { await Utils.pressKeyToExit(JSON.stringify(error)); } } } // Helper functions (unchanged) async function redraw(elem) { let opt = {}; opt.patchConsole = false; opt.debug = true; console.clear(); render(elem, opt); } function sleep(ms = 0) { return new Promise((resolve) => { setTimeout(resolve, ms); }); }