docs(bastion): document multi-arch PXE and the vendor-OS classification

Records the two things that are easy to get wrong and expensive to
rediscover: the DHCP option 93 value table (19 is arm64 UEFI HTTP boot,
20 is PC/AT BIOS), and that arm64 needs iPXE with LoadFile2 or the
kernel panics with unknown-block(0,0) and looks like a disk fault.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015nRFZXpKwUVE4SRSHw6GjF
This commit is contained in:
Michal
2026-08-11 13:08:32 +01:00
parent 572afb2624
commit b75a4e0118
3 changed files with 92 additions and 5 deletions

View File

@@ -191,12 +191,20 @@ async function startHarness(vmName: string, httpPort: number, pubKey: string): P
});
// iPXE binaries. The arm64 one is the whole point: dnsmasq hands it out on DHCP
// option 93 = 11, and x86_64 is staged too so the config renders as it does in
// production.
// option 93 -- 11 for UEFI PXE (TFTP) and 19 for UEFI HTTP Boot.
//
// They go in BOTH directories, exactly as main.ts stages them. AAVMF prefers HTTP
// Boot, so it is served an http:// URL and fetches from httpDir; a firmware that
// takes the TFTP path reads the same file from tftpDir. Staging only tftpDir gives a
// 404 and "No bootable option or device was found" on the console.
log("Staging iPXE binaries...");
copyFileSync(IPXE_ARM64, join(config.tftpDir, "ipxe-arm64.efi"));
const ipxeX86 = "/usr/share/ipxe/ipxe-snponly-x86_64.efi";
if (existsSync(ipxeX86)) copyFileSync(ipxeX86, join(config.tftpDir, "ipxe.efi"));
copyFileSync(IPXE_ARM64, join(config.tftpDir, "ipxe-arm64.efi"));
copyFileSync(IPXE_ARM64, join(config.httpDir, "ipxe-arm64.efi"));
if (existsSync(ipxeX86)) {
copyFileSync(ipxeX86, join(config.tftpDir, "ipxe.efi"));
copyFileSync(ipxeX86, join(config.httpDir, "ipxe.efi"));
}
// Fedora kernel + initrd for both architectures, cached across runs.
const cacheDir = "/var/lib/libvirt/images/lab-pxe-cache";