import { enableSystemdUserLinger, isSystemdUserServiceAvailable, readSystemdUserLingerStatus, } from "../daemon/systemd.js"; import type { RuntimeEnv } from "../runtime.js"; import { note } from "../terminal/note.js"; export type LingerPrompter = { confirm?: (params: { message: string; initialValue?: boolean; }) => Promise; note: (message: string, title?: string) => Promise | void; }; export async function ensureSystemdUserLingerInteractive(params: { runtime: RuntimeEnv; prompter?: LingerPrompter; env?: NodeJS.ProcessEnv; title?: string; reason?: string; prompt?: boolean; requireConfirm?: boolean; }): Promise { if (process.platform !== "linux") return; if (params.prompt === false) return; const env = params.env ?? process.env; const prompter = params.prompter ?? { note }; const title = params.title ?? "Systemd"; if (!(await isSystemdUserServiceAvailable())) { await prompter.note( "Systemd user services are unavailable. Skipping lingering checks.", title, ); return; } const status = await readSystemdUserLingerStatus(env); if (!status) { await prompter.note( "Unable to read loginctl linger status. Ensure systemd + loginctl are available.", title, ); return; } if (status.linger === "yes") return; const reason = params.reason ?? "Systemd user services stop when you log out or go idle, which kills the Gateway."; const actionNote = params.requireConfirm ? "We can enable lingering now (may require sudo; writes /var/lib/systemd/linger)." : "Enabling lingering now (may require sudo; writes /var/lib/systemd/linger)."; await prompter.note(`${reason}\n${actionNote}`, title); if (params.requireConfirm && prompter.confirm) { const ok = await prompter.confirm({ message: `Enable systemd lingering for ${status.user}?`, initialValue: true, }); if (!ok) { await prompter.note( "Without lingering, the Gateway will stop when you log out.", title, ); return; } } const resultNoSudo = await enableSystemdUserLinger({ env, user: status.user, }); if (resultNoSudo.ok) { await prompter.note(`Enabled systemd lingering for ${status.user}.`, title); return; } const result = await enableSystemdUserLinger({ env, user: status.user, sudoMode: "prompt", }); if (result.ok) { await prompter.note(`Enabled systemd lingering for ${status.user}.`, title); return; } params.runtime.error( `Failed to enable lingering: ${result.stderr || result.stdout || "unknown error"}`, ); await prompter.note( `Run manually: sudo loginctl enable-linger ${status.user}`, title, ); } export async function ensureSystemdUserLingerNonInteractive(params: { runtime: RuntimeEnv; env?: NodeJS.ProcessEnv; }): Promise { if (process.platform !== "linux") return; const env = params.env ?? process.env; if (!(await isSystemdUserServiceAvailable())) return; const status = await readSystemdUserLingerStatus(env); if (!status || status.linger === "yes") return; const result = await enableSystemdUserLinger({ env, user: status.user, sudoMode: "non-interactive", }); if (result.ok) { params.runtime.log(`Enabled systemd lingering for ${status.user}.`); return; } params.runtime.log( `Systemd lingering is disabled for ${status.user}. Run: sudo loginctl enable-linger ${status.user}`, ); }