feat: daemonize bastion start, fix status for root-owned processes
- `lab init bastion standalone start` now runs in background by default - `--foreground` flag for running in foreground (debugging/containers) - Shows startup output then detaches with PID + log path - Status command uses /proc check instead of kill -0 (works cross-user) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
// CLI command: init bastion standalone start
|
||||
// Start the bastion server (HTTP + dnsmasq).
|
||||
// Start the bastion server (HTTP + dnsmasq), daemonized by default.
|
||||
|
||||
import { spawn, type ChildProcess } from "node:child_process";
|
||||
import { existsSync, readFileSync } from "node:fs";
|
||||
import type { Command } from "commander";
|
||||
import { startBastion } from "@lab/bastion";
|
||||
|
||||
@@ -18,6 +20,7 @@ export function registerStartCommand(parent: Command): void {
|
||||
.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)")
|
||||
.option("--foreground", "Run in foreground (default: daemonize)")
|
||||
.action(async (opts: {
|
||||
port: string;
|
||||
dir: string;
|
||||
@@ -29,18 +32,74 @@ export function registerStartCommand(parent: Command): void {
|
||||
locale: string;
|
||||
skipDnsmasq?: boolean;
|
||||
skipArtifacts?: boolean;
|
||||
foreground?: boolean;
|
||||
}) => {
|
||||
await startBastion({
|
||||
httpPort: parseInt(opts.port, 10),
|
||||
bastionDir: opts.dir,
|
||||
domain: opts.domain,
|
||||
dhcpMode: opts.dhcpMode as "proxy" | "full",
|
||||
fedoraVersion: opts.fedora,
|
||||
arch: opts.arch,
|
||||
timezone: opts.timezone,
|
||||
locale: opts.locale,
|
||||
skipDnsmasq: opts.skipDnsmasq,
|
||||
skipArtifacts: opts.skipArtifacts,
|
||||
if (opts.foreground === true) {
|
||||
// Run in foreground
|
||||
await startBastion({
|
||||
httpPort: parseInt(opts.port, 10),
|
||||
bastionDir: opts.dir,
|
||||
domain: opts.domain,
|
||||
dhcpMode: opts.dhcpMode as "proxy" | "full",
|
||||
fedoraVersion: opts.fedora,
|
||||
arch: opts.arch,
|
||||
timezone: opts.timezone,
|
||||
locale: opts.locale,
|
||||
skipDnsmasq: opts.skipDnsmasq,
|
||||
skipArtifacts: opts.skipArtifacts,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Daemonize: spawn ourselves with --foreground and detach
|
||||
const logFile = `${opts.dir}/bastion.log`;
|
||||
const args = process.argv.slice(1);
|
||||
// Add --foreground flag
|
||||
args.push("--foreground");
|
||||
|
||||
const child: ChildProcess = spawn(process.argv[0] ?? "lab", args, {
|
||||
detached: true,
|
||||
stdio: ["ignore", "pipe", "pipe"],
|
||||
});
|
||||
|
||||
// Collect initial output to confirm startup
|
||||
let output = "";
|
||||
const timeout = setTimeout(() => {
|
||||
child.stdout?.removeAllListeners();
|
||||
child.stderr?.removeAllListeners();
|
||||
child.unref();
|
||||
console.log(`Bastion starting in background (PID ${child.pid})`);
|
||||
console.log(`Log: ${logFile}`);
|
||||
process.exit(0);
|
||||
}, 3000);
|
||||
|
||||
child.stdout?.on("data", (data: Buffer) => {
|
||||
output += data.toString();
|
||||
process.stdout.write(data);
|
||||
if (output.includes("Waiting for PXE boot requests")) {
|
||||
clearTimeout(timeout);
|
||||
child.stdout?.removeAllListeners();
|
||||
child.stderr?.removeAllListeners();
|
||||
child.unref();
|
||||
|
||||
// Check PID file
|
||||
const pidFile = `${opts.dir}/bastion.pid`;
|
||||
const pid = existsSync(pidFile) ? readFileSync(pidFile, "utf-8").trim() : String(child.pid);
|
||||
console.log("");
|
||||
console.log(`Bastion running in background (PID ${pid})`);
|
||||
console.log(`Log: ${logFile}`);
|
||||
process.exit(0);
|
||||
}
|
||||
});
|
||||
|
||||
child.stderr?.on("data", (data: Buffer) => {
|
||||
process.stderr.write(data);
|
||||
});
|
||||
|
||||
child.on("exit", (code) => {
|
||||
clearTimeout(timeout);
|
||||
console.error(`Bastion exited with code ${code}`);
|
||||
process.exit(code ?? 1);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -5,9 +5,13 @@ import { readFileSync, existsSync, statSync } from "node:fs";
|
||||
import type { Command } from "commander";
|
||||
import type { BastionState } from "@lab/shared";
|
||||
|
||||
import { execSync } from "node:child_process";
|
||||
|
||||
function isProcessAlive(pid: number): boolean {
|
||||
try {
|
||||
process.kill(pid, 0);
|
||||
// process.kill(pid, 0) fails for root-owned processes when run as non-root
|
||||
// Use kill -0 which works across users, or check /proc
|
||||
execSync(`kill -0 ${pid} 2>/dev/null || test -d /proc/${pid}`, { stdio: "pipe" });
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
|
||||
Reference in New Issue
Block a user