Files
nanoclaw/setup/systemd-user-env.ts
T
glifocat 1512e3f19e fix(setup): re-probe systemd user session with derived env on su- entry
`setup/service.ts` decides between a real systemd user unit and the
nohup fallback by running `systemctl --user daemon-reload` and watching
for the call to succeed. In contexts that bypass pam_systemd —
`su -`, `pct enter`, headless containers — `XDG_RUNTIME_DIR` and
`DBUS_SESSION_BUS_ADDRESS` are not exported, the probe fails with
`Failed to connect to bus: No medium found`, and we install nohup
despite the user manager being healthy on disk.

Before the probe, check whether linger is enabled for the current user
and `/run/user/<uid>` exists; if so, re-derive the env vars from disk
and let them propagate to subsequent `systemctl --user` calls via
`process.env` (execSync inherits by default). If the probe still fails
after that, the existing nohup fallback runs unchanged — and the
warning log now records *which* precondition failed so the cause is
visible without grepping setup.log.

The pure decision function lives in `setup/systemd-user-env.ts` so it
can be tested without execSync. New regression test in
`setup/systemd-user-env.test.ts` covers the #2482 repro plus the
already_set / no_linger / no_runtime_dir / no_user / no_uid branches.

Closes #1981
Closes #2482
2026-05-15 17:16:12 +02:00

39 lines
1.0 KiB
TypeScript

export interface SystemdUserEnvDeps {
uid: number | undefined;
user: string | undefined;
env: { XDG_RUNTIME_DIR?: string };
exists: (path: string) => boolean;
}
export type SystemdUserEnvReason =
| 'already_set'
| 'no_user'
| 'no_uid'
| 'no_linger'
| 'no_runtime_dir'
| 'populated';
export interface SystemdUserEnvResult {
reason: SystemdUserEnvReason;
XDG_RUNTIME_DIR?: string;
DBUS_SESSION_BUS_ADDRESS?: string;
}
export function computeUserSystemdEnv(
deps: SystemdUserEnvDeps,
): SystemdUserEnvResult {
if (deps.env.XDG_RUNTIME_DIR) return { reason: 'already_set' };
if (!deps.user) return { reason: 'no_user' };
if (typeof deps.uid !== 'number') return { reason: 'no_uid' };
if (!deps.exists(`/var/lib/systemd/linger/${deps.user}`)) {
return { reason: 'no_linger' };
}
const runtimeDir = `/run/user/${deps.uid}`;
if (!deps.exists(runtimeDir)) return { reason: 'no_runtime_dir' };
return {
reason: 'populated',
XDG_RUNTIME_DIR: runtimeDir,
DBUS_SESSION_BUS_ADDRESS: `unix:path=${runtimeDir}/bus`,
};
}