penguins-eggs
Version:
A remaster system tool, compatible with Arch, Debian, Devuan, Ubuntu and others
411 lines (409 loc) • 16.4 kB
JavaScript
/**
* ./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);
}
}
}