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) <noreply@anthropic.com>
This commit is contained in:
@@ -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,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -54,46 +54,50 @@ export async function startBastion(overrides: Partial<BastionConfig> = {}): 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<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 {
|
||||
|
||||
Reference in New Issue
Block a user