penguins-eggs
Version:
A remaster system tool, compatible with Almalinux, Alpine, Arch, Debian, Devuan, Fedora, Manjaro, Opensuse, Ubuntu and derivatives
419 lines (418 loc) • 16.4 kB
JavaScript
/**
* ./src/classes/ovary.d/make-efi.ts
* penguins-eggs v.25.7.x / ecmascript 2020
* author: Piero Proietti
* email: piero.proietti@gmail.com
* license: MIT
*/
import mustache from 'mustache';
// packages
import fs from 'node:fs';
import path from 'node:path';
// classes
import { exec } from '../../lib/utils.js';
import Diversions from '../diversions.js';
import Utils from '../utils.js';
// _dirname
const __dirname = path.dirname(new URL(import.meta.url).pathname);
/**
* @param this
* @param theme
*/
export async function makeEfi(theme = 'eggs') {
const bootloaders = Diversions.bootloaders(this.familyId);
/**
* Define default paths based on arch
*/
let signed = false;
let grubEfi = '';
let shimEfi = '';
// Default paths based on architecture
switch (process.arch) {
case 'arm64': {
grubEfi = path.resolve(bootloaders, `grub/arm64-efi/monolithic/grubaa64.efi`);
shimEfi = path.resolve(bootloaders, `shim/shimaa64.efi`);
break;
}
case 'ia32': {
grubEfi = path.resolve(bootloaders, `grub/i386-efi/monolithic/grubia32.efi`);
shimEfi = path.resolve(bootloaders, `shim/shimia32.efi`); // raramente usato non firmato
break;
}
case 'riscv64': {
// Percorso per RISC-V (assumendo struttura simile)
// Nota: Assicurati che Diversions.bootloaders punti al posto giusto o che i file esistano lì
grubEfi = path.resolve(bootloaders, `grub/riscv64-efi/monolithic/grubriscv64.efi`);
shimEfi = ''; // Solitamente niente SHIM su RISC-V per ora
break;
}
case 'x64': {
grubEfi = path.resolve(bootloaders, `grub/x86_64-efi/monolithic/grubx64.efi`);
shimEfi = path.resolve(bootloaders, `shim/shimx64.efi`);
break;
}
// No default
}
/**
* Gestione Secure Boot (Debian/Ubuntu/Devuan)
*/
if (this.familyId === 'debian') {
signed = true;
switch (process.arch) {
case 'arm64': {
grubEfi = path.resolve(bootloaders, `grub/arm64-efi-signed/grubaa64.efi.signed`);
shimEfi = path.resolve(bootloaders, `shim/shimaa64.efi.signed`);
break;
}
case 'ia32': {
grubEfi = path.resolve(bootloaders, `grub/i386-efi-signed/grubia32.efi.signed`);
shimEfi = path.resolve(bootloaders, `shim/shimia32.efi.signed`);
break;
}
case 'riscv64': {
// Per ora niente firma su RISC-V Debian, fallback su unsigned
signed = false;
grubEfi = path.resolve(bootloaders, `grub/riscv64-efi/monolithic/grubriscv64.efi`);
shimEfi = '';
break;
}
case 'x64': {
grubEfi = path.resolve(bootloaders, `grub/x86_64-efi-signed/grubx64.efi.signed`);
shimEfi = path.resolve(bootloaders, `shim/shimx64.efi.signed`);
break;
}
// No default
}
}
// Safety check se i file non esistono (es. grub riscv non trovato)
if (!fs.existsSync(grubEfi) && process.arch === 'riscv64') {
// Fallback tentativo path alternativo o warning
Utils.warning(`Warning: ${grubEfi} not found. Checking alternate paths...`);
}
if (signed) {
Utils.warning(`Your live system ${this.distroId}/${process.arch} can boot with Secure Boot enabled`);
}
else {
Utils.warning(`You must disable Secure Boot to boot live system ${this.distroId}/${process.arch}`);
}
// 2 secondi per leggere...
await new Promise((resolve) => setTimeout(resolve, 2000));
const efiPath = this.settings.efi_work;
const efiWorkDir = path.join(efiPath, '/work/');
const efiMemdiskDir = path.join(efiPath, '/memdisk/');
const efiImgMnt = path.join(efiPath, 'mnt');
const isoDir = this.settings.iso_work;
// create (ISO)/boot/grub
await exec(`mkdir ${path.join(isoDir, `/boot/grub/${Utils.uefiFormat()}`)} -p`, this.echo);
// create (ISO)/EFI
await exec(`mkdir ${isoDir}/EFI/boot -p`, this.echo);
// Copy EFI binaries to ISO root
if (shimEfi && fs.existsSync(shimEfi)) {
await exec(`cp ${shimEfi} ${isoDir}/EFI/boot/${bootEfiName()}`, this.echo);
}
// Se non c'è shim (es. RISC-V), copiamo grub direttamente come boot<arch>.efi?
// Solitamente se c'è shim: bootx64.efi = shim, grubx64.efi = grub
// Se NO shim: bootx64.efi = grub
if (!shimEfi || !fs.existsSync(shimEfi)) {
await exec(`cp ${grubEfi} ${isoDir}/EFI/boot/${bootEfiName()}`, this.echo);
}
else {
await exec(`cp ${grubEfi} ${isoDir}/EFI/boot/${grubEfiName()}`, this.echo);
}
// clean/create all in efiPath
if (fs.existsSync(efiPath)) {
await exec(`rm -rf ${efiPath}`);
}
await exec(`mkdir ${efiPath}`, this.echo);
await exec(`mkdir ${efiMemdiskDir}`, this.echo);
await exec(`mkdir ${efiImgMnt}`, this.echo);
await exec(`mkdir ${efiWorkDir}`, this.echo);
/**
* create efi.img logic
*/
// Seeker GRUB config: cerca il file .disk/id/UUID
Utils.warning(`UUID used for boot search: ${this.uuid}`);
let seeker = '';
seeker += `search --file --set=root /.disk/id/${this.uuid}\n`;
seeker += 'set prefix=($root)/boot/grub\n';
seeker += 'source $prefix/${grub_cpu}-efi/grub.cfg\n';
// Fallback generico se la source fallisce (utile per debug)
seeker += 'configfile ($root)/boot/grub/grub.cfg\n';
/**
* creating grub.cfg (1) seeker for usb on (efi.img)/boot/grub/grub.cfg
*/
Utils.warning('creating grub.cfg seeker USB on (efi.img)/boot/grub');
await exec(`mkdir ${path.join(efiMemdiskDir, '/boot/grub')} -p`, this.echo);
const cfgSeekerUsb = `${efiMemdiskDir}/boot/grub/grub.cfg`;
let cfgSeekerUsbText = '';
cfgSeekerUsbText += `# grub.cfg seeker\n`;
cfgSeekerUsbText += `# created on ${efiMemdiskDir}, path ${cfgSeekerUsb}\n`;
cfgSeekerUsbText += `\n`;
cfgSeekerUsbText += seeker;
Utils.write(cfgSeekerUsb, cfgSeekerUsbText);
/**
* create grub.cfg (bridge) on (ISO)/boot/grub/x86_64-efi/grub.cfg
*/
Utils.warning(`creating grub.cfg bridge to main. (ISO)/boot/grub/${Utils.uefiFormat()}`);
const cfgBridge = path.join(isoDir, '/boot/grub/', Utils.uefiFormat(), '/grub.cfg');
let cfgBridgeText = `# grub.cfg bridge\n`;
if (!this.hidden) {
cfgBridgeText += `# created on ${cfgBridge}\n`;
}
cfgBridgeText += `\n`;
// Qui è dove l'architettura specifica punta al grub generico
cfgBridgeText += `source /boot/grub/grub.cfg\n`;
fs.writeFileSync(cfgBridge, cfgBridgeText);
/**
* grub bait
*/
let pathBait = path.join(isoDir, '/EFI/debian');
if (this.distroLike === 'Ubuntu') {
pathBait = path.join(isoDir, '/EFI/ubuntu');
}
await exec(`mkdir ${pathBait} -p`, this.echo);
Utils.warning(`creating grub.cfg seeker ISO/DVD on (ISO)/EFI/${path.basename(pathBait)}`);
const cfgBait = path.join(pathBait, '/grub.cfg');
let cfgBaitText = '';
cfgBaitText += `\n`;
cfgBaitText += seeker;
Utils.write(cfgBait, cfgBaitText);
/**
* creating grub.cfg configuration on (ISO)/EFI/boot/grub.cfg
* This ensures fallback bootloader finds the config being in the same dir
*/
const cfgEfiBoot = path.join(isoDir, 'EFI/boot/grub.cfg');
Utils.write(cfgEfiBoot, cfgBaitText);
/**
* README.md
*/
const baitReadme = path.join(pathBait, '/README.md');
let baitReadmeText = ``;
if (this.distroLike !== 'Debian' && this.distroLike !== 'Ubuntu') {
baitReadmeText += `# penguins-eggs\n`;
baitReadmeText += '\n';
baitReadmeText += `This is just an hack, to let ${this.distroId} boot using Debian trixie bootloaders\n`;
fs.writeFileSync(baitReadme, baitReadmeText);
}
/**
* creating structure efiWorkDir
*/
await exec(`mkdir -p ${efiWorkDir}/boot/grub`, this.echo); // qua va grub.cfg 2
await exec(`mkdir -p ${efiWorkDir}/EFI/boot`);
/**
* create tarred efiMemdiskDir (Legacy/Memdisk method)
*/
const currentDir = process.cwd();
process.chdir(efiMemdiskDir);
await exec('tar -cvf memdisk boot', this.echo);
process.chdir(currentDir);
/**
* Create boot image "boot/grub/efi.img"
*/
const efiImg = path.join(efiWorkDir, `boot/grub/efi.img`);
// Aumentato leggermente il size per sicurezza
if ((await exec(`dd if=/dev/zero of=${efiImg} bs=1M count=16`, this.echo)).code !== 0) {
Utils.error(`Error creating ${efiImg}`);
process.exit(1);
}
if ((await exec(`/sbin/mkdosfs -F 16 ${efiImg}`, this.echo)).code !== 0) {
Utils.error(`Error formatting ${efiImg}`);
process.exit(1);
}
await new Promise((resolve) => setTimeout(resolve, 500));
// Use mtools to populate efi.img without mounting
// 1. Create directories
if ((await exec(`mmd -i ${efiImg} ::/EFI`, this.echo)).code !== 0) {
Utils.error(`Error creating ::/EFI on ${efiImg}`);
process.exit(1);
}
await exec(`mmd -i ${efiImg} ::/EFI/boot`, this.echo);
await exec(`mmd -i ${efiImg} ::/boot`, this.echo);
await exec(`mmd -i ${efiImg} ::/boot/grub`, this.echo);
// 2. Copy grub.cfg to /boot/grub/grub.cfg
if ((await exec(`mcopy -i ${efiImg} ${cfgSeekerUsb} ::/boot/grub/grub.cfg`, this.echo)).code !== 0) {
Utils.error(`Error copying grub.cfg to ${efiImg}`);
process.exit(1);
}
// 3. Copy EFI binaries
if (shimEfi && fs.existsSync(shimEfi)) {
await exec(`mcopy -i ${efiImg} ${shimEfi} ::/EFI/boot/${bootEfiName()}`, this.echo);
await exec(`mcopy -i ${efiImg} ${grubEfi} ::/EFI/boot/${grubEfiName()}`, this.echo);
}
else {
// Se no shim (RISC-V), grub diventa il boot loader principale
if ((await exec(`mcopy -i ${efiImg} ${grubEfi} ::/EFI/boot/${bootEfiName()}`, this.echo)).code !== 0) {
Utils.error(`Error copying ${grubEfi} to ${efiImg}`);
process.exit(1);
}
}
// 4. FIX CRUCIALE PER RISC-V (e compatibilità x86):
// Copia il grub.cfg seeker ANCHE accanto al binario EFI in /EFI/boot/
if (process.arch === 'riscv64') {
await exec(`mcopy -i ${efiImg} ${cfgSeekerUsb} ::/EFI/boot/grub.cfg`, this.echo);
}
// Copy isoImg in ${${isoDir}/boot/grub
Utils.warning('copyng (efi.img) on (ISO)/boot/grub');
await exec(`cp ${efiImg} ${isoDir}/boot/grub`, this.echo);
/**
* creating grub.cfg (4) on (ISO)/boot/grub
*/
Utils.warning('creating grub.cfg main on (ISO)/boot/grub');
// splash.png
let splashSrc = '';
const splashDest = `${efiWorkDir}/boot/grub/splash.png`;
let themeSrc = '';
const themeDest = `${isoDir}/boot/grub/theme.cfg`;
let grubTemplate = '';
if (this.hidden) {
splashSrc = path.resolve(__dirname, `../../../addons/${theme}/theme/livecd/generic-splash.png`);
grubTemplate = path.resolve(__dirname, '../../../addons/eggs/theme/livecd/generic.grub.main.cfg');
themeSrc = path.resolve(__dirname, '../../../addons/eggs/theme/livecd/generic.grub.theme.cfg');
}
else {
// ... (resto della logica temi invariata)
splashSrc = path.resolve(__dirname, `../../../addons/${theme}/theme/livecd/splash.png`);
if (this.theme.includes('/')) {
splashSrc = `${theme}/theme/livecd/splash.png`;
}
if (!fs.existsSync(splashSrc)) {
Utils.warning(`warning: ${splashSrc} does not exists`);
process.exit(1);
}
// select themeSrc
themeSrc = path.resolve(__dirname, `../../../addons/${theme}/theme/livecd/grub.theme.cfg`);
if (this.theme.includes('/')) {
themeSrc = `${theme}/theme/livecd/grub.theme.cfg`;
}
if (!fs.existsSync(themeSrc)) {
Utils.error(`error: ${themeSrc} does not exist`);
process.exit(1);
}
// fonts... (invariato)
if (fs.existsSync('/usr/share/grub/font.pf2')) {
await exec(`cp /usr/share/grub/font.pf2 ${efiWorkDir}boot/grub/font.pf2`, this.echo);
}
else if (fs.existsSync('/usr/share/grub/unicode.pf2')) {
await exec(`cp /usr/share/grub/unicode.pf2 ${efiWorkDir}boot/grub/font.pf2`, this.echo);
}
else if (fs.existsSync('/usr/share/grub/ascii.pf2')) {
await exec(`cp /usr/share/grub/ascii.pf2 ${efiWorkDir}boot/grub/font.pf2`, this.echo);
}
// Copy workdir files to ISO/boot
await exec(`rsync -avx ${efiWorkDir}/boot ${isoDir}/`, this.echo);
/**
* prepare main grub.cfg from grub.main.cfg
*/
grubTemplate = `${theme}/theme/livecd/grub.main.cfg`;
if (!fs.existsSync(grubTemplate)) {
grubTemplate = path.resolve(__dirname, '../../../addons/eggs/theme/livecd/grub.main.cfg');
}
if (!fs.existsSync(grubTemplate)) {
Utils.error(`error: ${grubTemplate} does not exist`);
process.exit(1);
}
}
// splash.png
await exec(`cp ${splashSrc} ${splashDest}`, this.echo);
// grub.theme.png
fs.copyFileSync(themeSrc, themeDest);
// grub.main.png
const kernel_parameters = Diversions.kernelParameters(this.familyId, this.volid, this.fullcrypt);
const cfgMain = path.join(isoDir, '/boot/grub/grub.cfg');
const template = fs.readFileSync(grubTemplate, 'utf8');
let fullname = this.settings.remix.fullname.toUpperCase();
if (this.hidden) {
fullname = 'LINUX';
}
/**
* SMART KERNEL DETECTION FOR RISC-V
* Invece di forzare ciecamante 'vmlinux', controlliamo cosa c'è davvero
* nella cartella /live della ISO.
* Questo supporta sia Debian (spesso vmlinux) che Ubuntu (vmlinuz).
*/
let kernelName = path.basename(this.vmlinuz); // Nome originale rilevato dal sistema
if (process.arch === 'riscv64') {
const liveDir = path.join(isoDir, 'live');
// Generiamo le due possibili varianti del nome
const nameX = kernelName.replace('vmlinuz', 'vmlinux'); // variante uncompressed
const nameZ = kernelName.replace('vmlinux', 'vmlinuz'); // variante compressed
// Controllo esistenza fisica
if (fs.existsSync(path.join(liveDir, nameX))) {
kernelName = nameX; // Abbiamo trovato vmlinux (Debian style)
}
else if (fs.existsSync(path.join(liveDir, nameZ))) {
kernelName = nameZ; // Abbiamo trovato vmlinuz (Ubuntu style)
}
// Se non trova nulla, mantiene l'originale kernelName come fallback
}
const view = {
fullname,
initrdImg: `/live/${path.basename(this.initrd)}`,
kernel: this.kernel,
kernel_parameters,
vmlinuz: `/live/${kernelName}` // Usiamo il nome verificato
};
let cfgMainText = '';
cfgMainText += `# grub.cfg (4) main\n`;
if (!this.hidden) {
cfgMainText += `# created on ${cfgMain}`;
}
cfgMainText += `\n`;
cfgMainText += mustache.render(template, view);
fs.writeFileSync(cfgMain, cfgMainText);
}
/**
* FUNCTIONS
*/
/**
* @returns
*/
function bootEfiName() {
let ben = '';
switch (process.arch) {
case 'arm64': {
ben = 'bootaa64.efi';
break;
}
case 'ia32': {
ben = 'bootia32.efi';
break;
}
case 'riscv64': {
ben = 'bootriscv64.efi';
break;
}
case 'x64': {
ben = 'bootx64.efi';
break;
}
// No default
}
return ben;
}
/**
* @returns
*/
function grubEfiName() {
let gen = '';
switch (process.arch) {
case 'arm64': {
gen = 'grubaa64.efi';
break;
}
case 'ia32': {
gen = 'grubia32.efi'; // c'era uno spazio typo nel tuo codice originale "grub ia32", corretto qui
break;
}
case 'riscv64': {
gen = 'grubriscv64.efi';
break;
}
case 'x64': {
gen = 'grubx64.efi';
break;
}
// No default
}
return gen;
}