From 937c01f5d9396513c8651ee7f114203961fc7448 Mon Sep 17 00:00:00 2001 From: Michal Date: Tue, 17 Mar 2026 03:11:29 +0000 Subject: [PATCH] fix: add --skip-dnsmasq/--skip-artifacts flags, fix config propagation Enables running the TS bastion without dnsmasq for testing. VM-tested: SSH works, partitions correct, k3s prereqs configured. Co-Authored-By: Claude Opus 4.6 (1M context) --- bastion/src/cli/commands/serve.ts | 6 ++ bastion/src/server/config.ts | 5 ++ bastion/src/server/main.ts | 112 +++++++++++++++++------------- 3 files changed, 73 insertions(+), 50 deletions(-) diff --git a/bastion/src/cli/commands/serve.ts b/bastion/src/cli/commands/serve.ts index ab3bb56..1922d1a 100644 --- a/bastion/src/cli/commands/serve.ts +++ b/bastion/src/cli/commands/serve.ts @@ -16,6 +16,8 @@ export function registerServeCommand(program: Command): void { .option("--arch ", "Architecture", "x86_64") .option("--timezone ", "Timezone", "Europe/London") .option("--locale ", "Locale", "en_GB.UTF-8") + .option("--skip-dnsmasq", "Skip starting dnsmasq (for testing)") + .option("--skip-artifacts", "Skip downloading boot artifacts (for testing)") .action(async (opts: { port: string; dir: string; @@ -25,6 +27,8 @@ export function registerServeCommand(program: Command): void { arch: string; timezone: string; locale: string; + skipDnsmasq?: boolean; + skipArtifacts?: boolean; }) => { await startBastion({ httpPort: parseInt(opts.port, 10), @@ -35,6 +39,8 @@ export function registerServeCommand(program: Command): void { arch: opts.arch, timezone: opts.timezone, locale: opts.locale, + skipDnsmasq: opts.skipDnsmasq, + skipArtifacts: opts.skipArtifacts, }); }); } diff --git a/bastion/src/server/config.ts b/bastion/src/server/config.ts index 068bc9f..771bf33 100644 --- a/bastion/src/server/config.ts +++ b/bastion/src/server/config.ts @@ -11,6 +11,9 @@ export interface BastionConfig { dhcpMode: "proxy" | "full"; dhcpRangeStart: string; dhcpRangeEnd: string; + // Flags + skipDnsmasq?: boolean; + skipArtifacts?: boolean; // Derived at runtime iface: string; serverIp: string; @@ -59,6 +62,8 @@ export function loadConfig(overrides: Partial = {}): BastionConfi gateway: overrides.gateway ?? "", sshKeys: overrides.sshKeys ?? [], adminUser: overrides.adminUser ?? "", + skipDnsmasq: overrides.skipDnsmasq, + skipArtifacts: overrides.skipArtifacts, fedoraMirror, tftpDir, httpDir, diff --git a/bastion/src/server/main.ts b/bastion/src/server/main.ts index b8183a5..4e29d27 100644 --- a/bastion/src/server/main.ts +++ b/bastion/src/server/main.ts @@ -54,46 +54,50 @@ export async function startBastion(overrides: Partial = {}): Prom mkdirSync(config.httpDir, { recursive: true }); // Prepare boot artifacts - logger.info(`Preparing boot artifacts (Fedora ${config.fedoraVersion} ${config.arch})...`); + if (!config.skipArtifacts) { + logger.info(`Preparing boot artifacts (Fedora ${config.fedoraVersion} ${config.arch})...`); - copyIfMissing( - "/usr/share/ipxe/undionly.kpxe", - `${config.tftpDir}/undionly.kpxe`, - "iPXE BIOS", - ); - copyIfMissing( - "/usr/share/ipxe/ipxe-snponly-x86_64.efi", - `${config.tftpDir}/ipxe.efi`, - "iPXE UEFI x86_64", - ); - try { copyIfMissing( - "/usr/share/ipxe/arm64-efi/snponly.efi", - `${config.tftpDir}/ipxe-arm64.efi`, - "iPXE UEFI arm64", + "/usr/share/ipxe/undionly.kpxe", + `${config.tftpDir}/undionly.kpxe`, + "iPXE BIOS", ); - } catch { - logger.warn("arm64 iPXE not available -- skipping"); - } - - download( - `${config.fedoraMirror}/images/pxeboot/vmlinuz`, - `${config.httpDir}/vmlinuz`, - "Fedora kernel", - ); - download( - `${config.fedoraMirror}/images/pxeboot/initrd.img`, - `${config.httpDir}/initrd.img`, - "Fedora initrd", - ); - - // Symlink iPXE binaries into HTTP dir for UEFI HTTP Boot - for (const name of ["ipxe.efi", "ipxe-arm64.efi"]) { - const src = `${config.tftpDir}/${name}`; - const dest = `${config.httpDir}/${name}`; - if (existsSync(src)) { - symlinkSafe(src, dest); + copyIfMissing( + "/usr/share/ipxe/ipxe-snponly-x86_64.efi", + `${config.tftpDir}/ipxe.efi`, + "iPXE UEFI x86_64", + ); + try { + copyIfMissing( + "/usr/share/ipxe/arm64-efi/snponly.efi", + `${config.tftpDir}/ipxe-arm64.efi`, + "iPXE UEFI arm64", + ); + } catch { + logger.warn("arm64 iPXE not available -- skipping"); } + + download( + `${config.fedoraMirror}/images/pxeboot/vmlinuz`, + `${config.httpDir}/vmlinuz`, + "Fedora kernel", + ); + download( + `${config.fedoraMirror}/images/pxeboot/initrd.img`, + `${config.httpDir}/initrd.img`, + "Fedora initrd", + ); + + // Symlink iPXE binaries into HTTP dir for UEFI HTTP Boot + for (const name of ["ipxe.efi", "ipxe-arm64.efi"]) { + const src = `${config.tftpDir}/${name}`; + const dest = `${config.httpDir}/${name}`; + if (existsSync(src)) { + symlinkSafe(src, dest); + } + } + } else { + logger.info("Skipping boot artifacts (--skip-artifacts)"); } // Write discovery kickstart @@ -115,8 +119,25 @@ export async function startBastion(overrides: Partial = {}): Prom await app.listen({ port: config.httpPort, host: "0.0.0.0" }); logger.info(`HTTP server listening on :${config.httpPort}`); - // Start dnsmasq - const dnsmasqProc = await startDnsmasq(config); + // Start dnsmasq (unless skipped) + if (!config.skipDnsmasq) { + const dnsmasqProc = startDnsmasq(config); + + // Monitor dnsmasq + void dnsmasqProc.then(() => { + logger.error("dnsmasq exited unexpectedly"); + logger.error("Check if another DHCP/TFTP service is running."); + process.exit(1); + }).catch((err: unknown) => { + const message = err instanceof Error ? err.message : String(err); + if (!message.includes("was killed")) { + logger.error(`dnsmasq error: ${message}`); + process.exit(1); + } + }); + } else { + logger.info("Skipping dnsmasq (--skip-dnsmasq)"); + } // Print banner printBanner(config); @@ -124,7 +145,7 @@ export async function startBastion(overrides: Partial = {}): Prom // Graceful shutdown const shutdown = async () => { logger.info("Shutting down..."); - stopDnsmasq(); + if (!config.skipDnsmasq) stopDnsmasq(); await app.close(); logger.info(`State preserved in ${config.stateFile}`); process.exit(0); @@ -133,17 +154,8 @@ export async function startBastion(overrides: Partial = {}): Prom process.on("SIGINT", () => void shutdown()); process.on("SIGTERM", () => void shutdown()); - // Wait for dnsmasq to exit - try { - await dnsmasqProc; - } catch (err) { - const message = err instanceof Error ? err.message : String(err); - if (!message.includes("was killed")) { - logger.error(`dnsmasq exited unexpectedly: ${message}`); - logger.error("Check if another DHCP/TFTP service is running."); - process.exit(1); - } - } + // Keep process alive + await new Promise(() => {}); } function printBanner(config: BastionConfig): void {