UNPKG

penguins-eggs

Version:

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

411 lines (409 loc) 16.4 kB
/** * ./src/classes/pxe.ts * penguins-eggs v.10.0.0 / ecmascript 2020 * author: Piero Proietti * email: piero.proietti@gmail.com * license: MIT */ import fs from 'node:fs'; import http from 'node:http'; import path from 'node:path'; import { dhcpd } from 'node-proxy-dhcpd'; import nodeStatic from 'node-static'; // @ts-ignore import tftp from 'tftp'; import { exec } from '../lib/utils.js'; import Distro from './distro.js'; import Settings from './settings.js'; import Utils from './utils.js'; // _dirname const __dirname = path.dirname(new URL(import.meta.url).pathname); /** * Pxe: */ export default class Pxe { bootLabel = ''; echo = {}; eggRoot = ''; initrdImg = ''; isos = []; // cuckoo's eggs nest = ''; pxeRoot = ''; settings = {}; verbose = false; vmlinuz = ''; /** * constructor * @param nest * @param pxeRoot */ constructor(nest = '', pxeRoot = '') { this.nest = nest; this.pxeRoot = pxeRoot; } /** * build */ async build() { if (fs.existsSync(this.pxeRoot)) { await this.tryCatch(`rm ${this.pxeRoot} -rf`); } await this.tryCatch(`mkdir ${this.pxeRoot} -p`); await this.tryCatch(`mkdir ${this.pxeRoot} -p`); await this.tryCatch(`ln -s ${this.eggRoot}live ${this.pxeRoot}/live`); await this.tryCatch(`ln -s ${this.nest}.disk ${this.pxeRoot}/.disk`); if (this.settings.distro.distroId === 'Manjaro') { await this.tryCatch(`ln -s ${this.eggRoot}manjaro ${this.pxeRoot}/manjaro`); } else if (this.settings.distro.distroId === 'Arch' || this.settings.distro.distroId === 'RebornOS') { await this.tryCatch(`ln -s ${this.eggRoot}arch ${this.pxeRoot}/arch`); } if (fs.existsSync(this.eggRoot)) { await this.tryCatch(`cp ${this.eggRoot}live/${this.vmlinuz} ${this.pxeRoot}/vmlinuz`, true); await this.tryCatch(`chmod 777 ${this.pxeRoot}/vmlinuz`); await this.tryCatch(`cp ${this.eggRoot}live/${this.initrdImg} ${this.pxeRoot}/initrd`, true); await this.tryCatch(`chmod 777 ${this.pxeRoot}/initrd`); } // link iso images in pxe for (const iso of this.isos) { await this.tryCatch(`ln -s ${this.nest}/${iso} ${this.pxeRoot}/${iso}`); } await this.bios(); await this.ipxe(); await this.http(); } /** * * @param dhcpOptions */ dhcpStart(dhcpOptions) { new dhcpd(dhcpOptions); } /** * fertilization() * * cuckoo's nest */ async fertilization() { this.settings = new Settings(); await this.settings.load(); if (Utils.isLive()) { this.eggRoot = this.settings.distro.liveMediumPath; if ( // ArchisoCompatibles this.settings.distro.distroId === 'Arch' || this.settings.distro.distroId === 'ArcoLinux' || this.settings.distro.distroId === 'blendOS' || this.settings.distro.distroId === 'EndeavourOS' || this.settings.distro.distroId === 'Garuda' || this.settings.distro.distroId === 'phyOS' || this.settings.distro.distroId === 'RebornOS') { this.eggRoot = '/run/archiso/bootmnt/'; await exec(`mkdir ${this.eggRoot} -p`); await exec(`mount /dev/sr0 ${this.eggRoot}`); } } else { this.eggRoot = this.settings.config.snapshot_mnt + 'iso/'; } if (!Utils.isLive() && !fs.existsSync(this.settings.config.snapshot_mnt)) { console.log('no image available, build an image with: sudo eggs produce'); process.exit(); } const settings = new Settings(); settings.load(); /** * se pxeRoot non esiste viene creato */ if (!fs.existsSync(this.pxeRoot)) { await exec(`mkdir ${this.pxeRoot} -p`); } /** * Ricerca delle ISOs */ const isos = []; /* if (!Utils.isLive()) { const isos = fs.readdirSync(this.nest) for (const iso of isos) { if (path.extname(iso) === '.iso') { this.isos.push(iso) } this.isos = this.isos.sort() } } */ /** * installed: /home/eggs/.mnt/iso/live * live: this.iso/live */ const pathFiles = this.eggRoot + 'live'; const files = fs.readdirSync(pathFiles); for (const file of files) { if (this.settings.distro.familyId === 'debian') { if (path.basename(file).slice(0, 7) === 'vmlinuz') { this.vmlinuz = path.basename(file); } if (path.basename(file).slice(0, 6) === 'initrd') { this.initrdImg = path.basename(file); } } else if (this.settings.distro.familyId === 'archlinux') { if (path.basename(file).slice(0, 7) === 'vmlinuz') { this.vmlinuz = path.basename(file); } if (path.basename(file).slice(0, 9) === 'initramfs') { this.initrdImg = path.basename(file); } } } /** * bootLabel */ this.bootLabel = 'not found'; if (fs.existsSync(this.eggRoot + '/.disk/mkisofs')) { const a = fs.readFileSync(this.eggRoot + '/.disk/mkisofs', 'utf8'); const b = a.slice(Math.max(0, a.indexOf('-o ') + 3)); const c = b.slice(0, Math.max(0, b.indexOf(' '))); this.bootLabel = c.slice(Math.max(0, c.lastIndexOf('/') + 1)); } console.log(`bootLabel: ${this.bootLabel}`); console.log(`vmlinuz: ${this.vmlinuz}`); console.log(`initrd: ${this.initrdImg}`); } /** * start http server for images * */ async httpStart() { const port = 80; const httpRoot = this.pxeRoot + '/'; console.log('http root: ' + httpRoot); console.log('http listening: 0.0.0.0:' + port); // const file = new nodeStatic.Server(httpRoot, { followSymlinks: true }) const file = new nodeStatic.Server(httpRoot); http .createServer((req, res) => { file.serve(req, res); }) .listen(port); } /** * start tftp */ async tftpStart(tftpOptions) { const tftpServer = tftp.createServer(tftpOptions); console.log('tftp listening: ' + tftpOptions.host + ':' + tftpOptions.port); tftpServer.on('error', (error) => { // Errors from the main socket // The current transfers are not aborted console.error(error); }); tftpServer.on('request', (req, res) => { req.on('error', (error) => { // Error from the request // The connection is already closed console.error('[' + req.stats.remoteAddress + ':' + req.stats.remotePort + '] (' + req.file + ') ' + error.message); }); }); tftpServer.listen(); } /** * Il resto PRIVATO */ /** * configure PXE bios */ async bios() { console.log('creating cuckoo configuration: BIOS'); await this.tryCatch(`cp ${__dirname}/../../addons/eggs/theme/livecd/isolinux.theme.cfg ${this.pxeRoot}/isolinux.theme.cfg`); await this.tryCatch(`cp ${__dirname}/../../addons/eggs/theme/livecd/splash.png ${this.pxeRoot}/splash.png`); /** * ipxe.efi */ await this.tryCatch(`ln -s ${__dirname}/../../ipxe/ipxe.efi ${this.pxeRoot}/ipxe.efi`); // pxe const distro = new Distro(); await this.tryCatch(`ln -s ${distro.syslinuxPath}pxelinux.0 ${this.pxeRoot}/pxelinux.0`); await this.tryCatch(`ln -s ${distro.syslinuxPath}lpxelinux.0 ${this.pxeRoot}/lpxelinux.0`); // syslinux await this.tryCatch(`ln -s ${distro.syslinuxPath}ldlinux.c32 ${this.pxeRoot}/ldlinux.c32`); await this.tryCatch(`ln -s ${distro.syslinuxPath}vesamenu.c32 ${this.pxeRoot}/vesamenu.c32`); await this.tryCatch(`ln -s ${distro.syslinuxPath}libcom32.c32 ${this.pxeRoot}/libcom32.c32`); await this.tryCatch(`ln -s ${distro.syslinuxPath}libutil.c32 ${this.pxeRoot}/libutil.c32`); await this.tryCatch(`ln -s ${distro.syslinuxPath}memdisk ${this.pxeRoot}/memdisk`); await this.tryCatch(`mkdir ${this.pxeRoot}/pxelinux.cfg`); let content = ''; content += '# eggs: pxelinux.cfg/default\n'; content += '# search path for the c32 support libraries (libcom32, libutil etc.)\n'; content += 'path\n'; content += 'include isolinux.theme.cfg\n'; content += 'UI vesamenu.c32\n'; content += '\n'; content += `menu title cuckoo: when you need a flying PXE server! ${Utils.address()}\n`; content += 'PROMPT 0\n'; content += 'TIMEOUT 200\n'; content += '\n'; content += 'label egg\n'; content += `menu label ${this.bootLabel.replace('.iso', '')}\n`; if (this.settings.distro.familyId === 'debian') { /** * DEBIAN */ const clid = this.settings.distro.codenameLikeId; if (clid === 'bionic' || clid === 'stretch' || clid === 'jessie') { content += 'kernel vmlinuz\n'; content += `append initrd=initrd boot=live config noswap noprompt fetch=http://${Utils.address()}/live/filesystem.squashfs\n`; } else { content += `kernel http://${Utils.address()}/vmlinuz\n`; content += `append initrd=http://${Utils.address()}/initrd boot=live config noswap noprompt fetch=http://${Utils.address()}/live/filesystem.squashfs\n`; } } else if (distro.familyId === 'archlinux') { /** * ARCH LINUX */ let tool = 'archiso'; if (distro.distroId === 'Manjarolinux') { tool = 'miso'; } content += `kernel http://${Utils.address()}/vmlinuz\n`; content += `append initrd=http://${Utils.address()}/initrd boot=live config noswap noprompt ${tool}_http_srv=http://${Utils.address()}/\n`; content += 'sysappend 3\n'; content += '\n'; } if (this.isos.length > 0) { content += 'menu separator\n'; for (const iso of this.isos) { content += '\n'; content += `label ${iso}\n`; content += `menu label ${iso}\n`; content += `kernel http://${Utils.address()}/memdisk\n`; content += `initrd http://${Utils.address()}/${iso}\n`; content += 'append iso raw sysappend 3\n'; } } const file = `${this.pxeRoot}/pxelinux.cfg/default`; fs.writeFileSync(file, content); } /** * configure PXE http server */ async http() { console.log('creating cuckoo configuration: html'); const file = `${this.pxeRoot}/index.html`; let content = ''; content += "<html><title>Penguin's eggs PXE server</title>"; content += '<div style="background-image:url(\'/splash.png\');background-repeat:no-repeat;width: 640;height:480;padding:5px;border:1px solid black;">'; content += '<h1>Cuckoo PXE server</h1>'; content += `<body>address: <a href=http://${Utils.address()}>${Utils.address()}</a><br/>`; if (Utils.isLive()) { content += 'started from live iso image<br/>'; } else { content += 'Serving:<li>'; for (const iso of this.isos) { content += `<ul><a href='http://${Utils.address()}/${iso}'>${iso}</a></ul>`; } content += '</li>'; } content += "source: <a href='https://github.com/pieroproietti/penguins-eggs'>https://github.com/pieroproietti/penguins-eggs</a><br/>"; content += "manual: <a href='https://penguins-eggs.net/book/italiano9.2.html'>italiano</a>, <a href='https://penguins--eggs-net.translate.goog/book/italiano9.2?_x_tr_sl=auto&_x_tr_tl=en&_x_tr_hl=en'>translated</a><br/>"; content += "discuss: <a href='https://t.me/penguins_eggs'>Telegram group<br/></body</html>"; fs.writeFileSync(file, content); } /** * */ async ipxe() { console.log('creating cuckoo configuration: UEFI'); let content = '#!ipxe\n'; content += 'dhcp\n'; content += 'set net0/ip=dhcp\n'; content += `console --picture http://${Utils.address()}/splash.png -x 1024 -y 768\n`; content += 'goto start ||\n'; content += '\n'; content += ':start\n'; content += `set server_root http://${Utils.address()}:80/\n`; const serverRootVars = '${server_root}'; content += `menu cuckoo: when you need a flying PXE server! ${Utils.address()}\n`; content += 'item --gap boot from ovarium\n'; content += `item egg-menu \${space} ${this.bootLabel.replaceAll('.iso', '')}\n\n`; if (this.isos.length > 0) { content += 'item --gap boot iso images\n'; for (const iso of this.isos) { const menu = iso; const label = menu; content += `item ${menu} \${space} ${label}\n\n`; } } content += 'item --gap boot from internet\n'; content += 'item netboot ${space} netboot\n'; content += 'choose target || goto start\n'; content += 'goto ${target}\n'; content += '\n'; content += ':egg-menu\n'; content += `kernel http://${Utils.address()}/vmlinuz\n`; content += `initrd http://${Utils.address()}/initrd\n`; /** * CORRECT: * content += `imgargs vmlinuz fetch=http://${Utils.address()}/live/filesystem.squashfs boot=live dhcp initrd=initrd ro\n` */ if (this.settings.distro.familyId === 'debian') { /** * DEBIAN */ content += `imgargs vmlinuz fetch=http://${Utils.address()}/live/filesystem.squashfs boot=live dhcp initrd=initrd ro\n`; } else if (this.settings.distro.familyId === 'archlinux') { /** * ARCH LINUX */ let tool = 'archiso'; if (this.settings.distro.codenameId === 'Qonos' || this.settings.distro.codenameId === 'Ruah' || this.settings.distro.codenameId === 'Sikaris' || this.settings.distro.codenameId === 'UltimaThule') { tool = 'miso'; } content += `imgargs vmlinuz ${tool}_http_srv=http://${Utils.address()}/ boot=live dhcp initrd=initrd ro\n`; // content += 'ipappend 3\n' } content += 'sleep 5\n'; content += 'boot || goto start\n\n'; if (this.isos.length > 0) { for (const iso of this.isos) { const menu = iso.replace('.iso', ''); content += `:${menu}\n`; content += `sanboot ${serverRootVars}/${iso}\n`; content += 'boot || goto start\n\n'; } } /** * netboot.xyz */ content += ':netboot\n'; content += 'ifopen net0\n'; content += 'set conn_type https\n'; content += 'chain --autofree https://boot.netboot.xyz/menu.ipxe || echo HTTPS failed... attempting HTTP...\n'; content += 'set conn_type http\n'; content += 'chain --autofree http://boot.netboot.xyz/menu.ipxe || echo HTTP failed, localbooting...\n'; content += 'goto start\n\n'; const file = `${this.pxeRoot}/autoexec.ipxe`; fs.writeFileSync(file, content); } /** * * @param cmd */ async tryCatch(cmd = '', echo = false) { try { if (echo) { console.log(cmd); } await exec(cmd, this.echo); } catch (error) { console.log(`Error: ${error}`); await Utils.pressKeyToExit(cmd); } } }