fix: PXE boot debugging — bisect root cause, syslog logging, serial console #3

Merged
michal merged 31 commits from wip/ks-debugging into main 2026-03-29 00:50:05 +00:00
3 changed files with 73 additions and 50 deletions
Showing only changes of commit 937c01f5d9 - Show all commits

View File

@@ -16,6 +16,8 @@ export function registerServeCommand(program: Command): void {
.option("--arch <arch>", "Architecture", "x86_64")
.option("--timezone <tz>", "Timezone", "Europe/London")
.option("--locale <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,
});
});
}

View File

@@ -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<BastionConfig> = {}): BastionConfi
gateway: overrides.gateway ?? "",
sshKeys: overrides.sshKeys ?? [],
adminUser: overrides.adminUser ?? "",
skipDnsmasq: overrides.skipDnsmasq,
skipArtifacts: overrides.skipArtifacts,
fedoraMirror,
tftpDir,
httpDir,

View File

@@ -54,6 +54,7 @@ export async function startBastion(overrides: Partial<BastionConfig> = {}): Prom
mkdirSync(config.httpDir, { recursive: true });
// Prepare boot artifacts
if (!config.skipArtifacts) {
logger.info(`Preparing boot artifacts (Fedora ${config.fedoraVersion} ${config.arch})...`);
copyIfMissing(
@@ -95,6 +96,9 @@ export async function startBastion(overrides: Partial<BastionConfig> = {}): Prom
symlinkSafe(src, dest);
}
}
} else {
logger.info("Skipping boot artifacts (--skip-artifacts)");
}
// Write discovery kickstart
const discoverKs = generateDiscoverKickstart(config);
@@ -115,8 +119,25 @@ export async function startBastion(overrides: Partial<BastionConfig> = {}): 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<BastionConfig> = {}): 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<BastionConfig> = {}): 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 {