penguins-eggs
Version:
A remaster system tool, compatible with Almalinux, Alpine, Arch, Debian, Devuan, Fedora, Manjaro, Opensuse, Ubuntu and derivatives
218 lines (213 loc) • 9.82 kB
JavaScript
/**
* ./src/classes/ovary.d/luks-root-initrd.ts
* penguins-eggs v.25.10.x / ecmascript 2020
* author: Piero Proietti
* email: piero.proietti@gmail.com
* license: MIT
*/
// packages
import fs from 'fs';
import path from 'node:path';
// classes
import { exec } from '../../lib/utils.js';
import Utils from '../utils.js';
// _dirname
const __dirname = path.dirname(new URL(import.meta.url).pathname);
const noop = () => { };
/**
* Creates a streamlined initrd image for Debian/Ubuntu with LUKS support using mkinitramfs within a temporary chroot.
* Copies the necessary unlock script, ensures losetup is included via a hook, and wraps /scripts/live for debugging.
* Assumes live-boot and cryptsetup packages are installed in the chroot.
* No cleanup of /etc modifications is performed as the chroot is temporary.
* @param this - Ovary instance context
* @param verbose - Whether to show verbose output
*/
export async function luksRootInitrd(verbose = false) {
const loggers = {
info: this.hidden ? noop : Utils.info,
log: this.hidden ? noop : console.log,
success: this.hidden ? noop : Utils.success,
warning: this.hidden ? noop : Utils.warning
};
const { info, log, success, warning } = loggers;
if (this.hidden) {
Utils.warning('intentionally blank. System is working, please wait');
}
log();
log('===========================================================================');
log(`Creating ${this.initrd} for LUKS using mkinitramfs`);
log('===========================================================================');
// --- Core Paths & Config ---
/*
* Refactored chrootPath to use this.liveRoot
* while we need to chroot into liveroot
*/
const chrootPath = this.liveRoot;
const kernelVersion = this.kernel;
const initrdBaseName = path.basename(this.initrd);
const logBaseName = `${this.settings.config.snapshot_prefix}mkinitramfs.log.txt`;
const finalInitrdDestPath = path.join(this.settings.iso_work, 'live', initrdBaseName); // Define final destination early for logging
try {
// --- 2. Prepare Chroot Environment ---
warning('Preparing chroot environment...');
// --- 2.1 Copy boot-encrypted-root.sh
{
const bootEncryptedRootName = 'boot-encrypted-root.sh'; // Corrected to lowercase zz-
const srcbootEncryptedRootPath = path.join(__dirname, '../../../scripts', bootEncryptedRootName);
const chrootbootEncryptedRootDir = '/etc/initramfs-tools/scripts/live-premount';
const chrootbootEncryptedRootPath = path.join(chrootbootEncryptedRootDir, bootEncryptedRootName);
const hostDestbootEncryptedRootDir = path.join(chrootPath, chrootbootEncryptedRootDir);
const hostDestbootEncryptedRootPath = path.join(chrootPath, chrootbootEncryptedRootPath);
if (!fs.existsSync(srcbootEncryptedRootPath)) {
throw new Error(`Unlock script source not found: ${srcbootEncryptedRootPath}`);
}
await exec(`mkdir -p "${hostDestbootEncryptedRootDir}"`);
await exec(`cp "${srcbootEncryptedRootPath}" "${hostDestbootEncryptedRootPath}"`);
await exec(`chmod +x "${hostDestbootEncryptedRootPath}"`);
success(`Copied ${bootEncryptedRootName} script to ${hostDestbootEncryptedRootDir}`);
}
// --- 2.2 Create dummy /etc/crypttab if needed ---
{
const chrootCrypttabPath = '/etc/crypttab';
const hostCrypttabPath = path.join(chrootPath, chrootCrypttabPath);
if (fs.existsSync(hostCrypttabPath)) {
info(`${hostCrypttabPath} already exists.`);
}
else {
const crypttabContent = '# Dummy entry to ensure cryptsetup is included\ncryptroot UUID=none none luks\n';
fs.writeFileSync(hostCrypttabPath, crypttabContent, 'utf-8');
success(`Created crypttab on ${path.dirname(hostCrypttabPath)}`);
}
}
// --- 2.3 Add hook ---
// Utils.warning(`Generating required hook scripts`)
await addHook('/usr/sbin/losetup', chrootPath, loggers);
await addHook('/usr/bin/rsync', chrootPath, loggers);
// --- 2.4 Add modules ---
// addModules('overlay', chrootPath)
// --- 3. Execute mkinitramfs INSIDE Chroot ---
{
warning(`Running mkinitramfs for kernel ${kernelVersion} inside chroot...`);
const chrootTmpInitrdPath = `/tmp/${initrdBaseName}`;
let logFilePath = path.join(this.settings.iso_work, logBaseName);
if (this.hidden) {
logFilePath = '/dev/null';
}
const mkinitramfsCmd = `mkinitramfs -v -o "${chrootTmpInitrdPath}" ${kernelVersion}`;
const chrootCmd = `chroot "${chrootPath}" /bin/bash -c "${mkinitramfsCmd}" > "${logFilePath}" 2>&1`;
await exec(chrootCmd, this.echo);
success(`mkinitramfs completed. Log: ${logFilePath}`);
}
// --- 4. Move Result ---
{
const chrootTmpInitrdPath = `/tmp/${initrdBaseName}`; // Re-declare for scope
const hostTmpInitrdPath = path.join(chrootPath, chrootTmpInitrdPath);
if (fs.existsSync(hostTmpInitrdPath)) {
info(`Moving generated initrd to ${finalInitrdDestPath}`);
await exec(`mv "${hostTmpInitrdPath}" "${finalInitrdDestPath}"`);
success(`Initrd moved successfully.`);
}
else {
Utils.error(`Generated initrd not found at ${hostTmpInitrdPath}. Build failed!`);
throw new Error('mkinitramfs did not produce the expected output file.');
}
// process.exit()
}
}
catch (error) {
Utils.error(`Error during LUKS initrd generation: ${error}`);
const logFilePath = path.join(this.settings.iso_work, logBaseName); // Re-declare for scope
if (error instanceof Error && error.message.includes('mkinitramfs inside chroot failed')) {
Utils.warning(`Check mkinitramfs log: ${logFilePath}`);
}
throw error; // Re-throw error
}
success(`Finished creating LUKS initrd: ${finalInitrdDestPath}`);
}
/**
* Genera e installa uno script hook di initramfs-tools nel chroot.
* Copia il comando specificato nella sua directory di destinazione
* standard (/bin o /sbin) all'interno dell'initramfs.
* @param cmdPath Path assoluto del comando da copiare *nel chroot* (es. '/usr/sbin/losetup')
* @param chrootPath Path assoluto alla radice del chroot
*/
async function addHook(cmdPath, chrootPath, loggers) {
const { info, log, success, warning } = loggers;
const chrootHooksDirPath = '/etc/initramfs-tools/hooks';
const hostHooksDirPath = path.join(chrootPath, chrootHooksDirPath);
const cmd = path.basename(cmdPath); // es. 'losetup'
const hookScriptName = `add-${cmd}-hook.sh`; // es. 'add-losetup-hook.sh'
const hostHookPath = path.join(hostHooksDirPath, hookScriptName);
if (!fs.existsSync(cmdPath)) {
Utils.error(`Dont' exists ${cmdPath}`);
process.exit();
}
let destDir = '/sbin'; // Default a /sbin per i comandi di amministrazione
// Determina la destinazione corretta
if (cmdPath.includes('/usr/bin') || cmdPath.includes('/bin')) {
destDir = '/bin'; // Comandi utente
}
else if (cmdPath.includes('/usr/sbin') || cmdPath.includes('/sbin')) {
destDir = '/sbin'; // Comandi amministrativi
}
// Crea la riga di comando per l'hook
const copyLine = `copy_exec ${cmdPath} ${destDir} || echo "WARNING: Failed to copy ${cmdPath} to ${destDir}" >&2`;
// Crea il contenuto boilerplate dello script hook
const hookContent = `#!/bin/sh
# Hook: ${hookScriptName}
# Generato automaticamente da penguins-eggs
# Copia ${cmdPath} in ${destDir}
PREREQ=""
case $1 in prereqs) echo "\${PREREQ}"; exit 0;; esac
. /usr/share/initramfs-tools/hook-functions
echo "EGGS-HOOK: Esecuzione hook ${hookScriptName}..."
${copyLine}
exit 0
`;
try {
await exec(`mkdir -p "${hostHooksDirPath}"`);
// Scrive il file e lo rende eseguibile (mode 0o755)
fs.writeFileSync(hostHookPath, hookContent, { encoding: 'utf-8', mode: 0o755 });
// console.log(hookContent)
success(`Generated and set executable hook: ${hookScriptName}`);
}
catch (error) {
Utils.error(`Failed to write hook script ${hostHookPath}: ${error.message}`);
process.exit(1);
throw error; // Lancia l'errore per fermare il processo
}
}
/**
*
* @param module
* @param chrootPath
*/
function addModules(module, chrootPath, loggers) {
const { info, log, success, warning } = loggers;
const chrootModulesFilePath = '/etc/initramfs-tools/modules'; // Relative inside chroot
const hostModulesFilePath = path.join(chrootPath, chrootModulesFilePath); // Absolute on host fs
let modulesContent = '';
let needsUpdate = false;
if (fs.existsSync(hostModulesFilePath)) {
modulesContent = fs.readFileSync(hostModulesFilePath, 'utf8');
}
else {
// inizializzazione modules
modulesContent = '\n';
}
if (modulesContent.includes(`\n${module}\n`)) {
info(`'${module}' module already present.`);
}
else {
info(`Added '${module}' module on ${hostModulesFilePath}`);
if (!modulesContent.endsWith('\n')) {
modulesContent += '\n';
}
modulesContent += `${module}\n`;
needsUpdate = true;
}
if (needsUpdate) {
fs.writeFileSync(hostModulesFilePath, modulesContent, 'utf-8');
success(`Updated ${hostModulesFilePath} with 'overlay'.`);
}
}