// Self-backgrounding for the headless commands: `--daemon` re-spawns this exact // command detached from the terminal (own session, stdio to a log file), writes // a pidfile plus a run descriptor, and exits the parent. You can then log out and // it keeps running. // // The run descriptor is what lets `torlnk update` relaunch a daemon on its exact // original command after rebuilding. // // NOTE: on a box with systemd, a `systemctl --user` service with linger is a // sturdier way to run these (auto-restart, boot-start). This is the no-systemd // convenience path. import { spawn } from "node:child_process"; import fs from "node:fs"; import path from "node:path"; import { logsDir } from "../config/paths"; const MARKER = "TORLINK_DAEMONIZED"; export function logPathFor(name: string): string { return path.join(logsDir, `${name}.log`); } export function pidPathFor(name: string): string { return path.join(logsDir, `${name}.pid`); } export function runPathFor(name: string): string { return path.join(logsDir, `${name}.run.json`); } // Records argv and cwd only, not env: a daemon relaunched after an update // inherits the updater's environment, so env-dependent behavior (proxies, // TORLINK_* overrides) follows the shell that ran `torlnk update`. export interface RunDescriptor { name: string; pid: number; argv: string[]; // args to node (script path + subcommand + flags) cwd: string; startedAt: number; } // Spawn `node ` detached with its own session and stdio pointed at the log, // then record the pid and enough to relaunch it later. Shared by the initial // --daemon fork and by a post-update restart, so both write the pidfile and // descriptor the same way. export function spawnDaemon(name: string, argv: string[], cwd: string): number { fs.mkdirSync(logsDir, { recursive: true }); const out = fs.openSync(logPathFor(name), "a"); const child = spawn(process.execPath, argv, { cwd, detached: true, stdio: ["ignore", out, out], env: { ...process.env, [MARKER]: "1" }, }); child.unref(); const pid = child.pid ?? 0; if (pid) { fs.writeFileSync(pidPathFor(name), `${pid}\n`); const desc: RunDescriptor = { name, pid, argv, cwd, startedAt: Date.now() }; fs.writeFileSync(runPathFor(name), `${JSON.stringify(desc, null, 2)}\n`); } return pid; } // In the parent: fork a detached child and exit. In the already-detached child // (marker set): return so the caller keeps running normally. export function daemonize(name: string): void { if (process.env[MARKER] === "1") return; const pid = spawnDaemon(name, process.argv.slice(1), process.cwd()); const logPath = logPathFor(name); const pidPath = pidPathFor(name); console.log(`torlink ${name} daemon started (pid ${pid}).`); console.log(` logs: ${logPath}`); console.log(` stop: kill ${pid} (or: kill $(cat ${pidPath}))`); process.exit(0); }