122 lines
3.4 KiB
TypeScript
122 lines
3.4 KiB
TypeScript
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<boolean>;
|
|
note: (message: string, title?: string) => Promise<void> | void;
|
|
};
|
|
|
|
export async function ensureSystemdUserLingerInteractive(params: {
|
|
runtime: RuntimeEnv;
|
|
prompter?: LingerPrompter;
|
|
env?: NodeJS.ProcessEnv;
|
|
title?: string;
|
|
reason?: string;
|
|
prompt?: boolean;
|
|
requireConfirm?: boolean;
|
|
}): Promise<void> {
|
|
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<void> {
|
|
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}`,
|
|
);
|
|
}
|