mirror of
https://github.com/qwibitai/nanoclaw.git
synced 2026-06-04 10:14:47 +08:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1512e3f19e | |||
| fa945a1d0c | |||
| bec10fe4e3 |
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "nanoclaw",
|
||||
"version": "2.0.61",
|
||||
"version": "2.0.62",
|
||||
"description": "Personal Claude assistant. Lightweight, secure, customizable.",
|
||||
"type": "module",
|
||||
"packageManager": "pnpm@10.33.0",
|
||||
|
||||
@@ -22,6 +22,7 @@ import {
|
||||
isWSL,
|
||||
} from './platform.js';
|
||||
import { emitStatus } from './status.js';
|
||||
import { computeUserSystemdEnv } from './systemd-user-env.js';
|
||||
|
||||
export async function run(_args: string[]): Promise<void> {
|
||||
const projectRoot = process.cwd();
|
||||
@@ -291,12 +292,34 @@ function setupSystemd(
|
||||
systemctlPrefix = 'systemctl';
|
||||
log.info('Running as root — installing system-level systemd unit');
|
||||
} else {
|
||||
// pam_systemd normally exports XDG_RUNTIME_DIR / DBUS_SESSION_BUS_ADDRESS
|
||||
// on login, but invocations via `su -`, `pct enter`, and other non-pam
|
||||
// entry points skip that step. The user systemd manager is still running
|
||||
// (linger keeps it alive across sessions), but the daemon-reload probe
|
||||
// below can't reach it without the env vars. Re-derive them from on-disk
|
||||
// state before probing so we don't false-negative into the nohup path
|
||||
// when a real systemd user session is available. See #2482.
|
||||
const envResult = computeUserSystemdEnv({
|
||||
uid: process.getuid?.(),
|
||||
user: process.env.USER || process.env.LOGNAME,
|
||||
env: { XDG_RUNTIME_DIR: process.env.XDG_RUNTIME_DIR },
|
||||
exists: fs.existsSync,
|
||||
});
|
||||
if (envResult.reason === 'populated') {
|
||||
process.env.XDG_RUNTIME_DIR = envResult.XDG_RUNTIME_DIR;
|
||||
process.env.DBUS_SESSION_BUS_ADDRESS = envResult.DBUS_SESSION_BUS_ADDRESS;
|
||||
log.info('Populated systemd user env from linger state', {
|
||||
XDG_RUNTIME_DIR: envResult.XDG_RUNTIME_DIR,
|
||||
});
|
||||
}
|
||||
|
||||
// Check if user-level systemd session is available
|
||||
try {
|
||||
execSync('systemctl --user daemon-reload', { stdio: 'pipe' });
|
||||
} catch {
|
||||
log.warn(
|
||||
'systemd user session not available — falling back to nohup wrapper',
|
||||
{ envProbeReason: envResult.reason },
|
||||
);
|
||||
setupNohupFallback(projectRoot, nodePath, homeDir);
|
||||
return;
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
|
||||
import { computeUserSystemdEnv } from './systemd-user-env.js';
|
||||
|
||||
function existsFrom(paths: Set<string>) {
|
||||
return (p: string) => paths.has(p);
|
||||
}
|
||||
|
||||
describe('computeUserSystemdEnv', () => {
|
||||
it('populates env when linger is on and the runtime dir exists (#2482 repro)', () => {
|
||||
const result = computeUserSystemdEnv({
|
||||
uid: 1000,
|
||||
user: 'nanoclaw',
|
||||
env: {},
|
||||
exists: existsFrom(
|
||||
new Set(['/var/lib/systemd/linger/nanoclaw', '/run/user/1000']),
|
||||
),
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
reason: 'populated',
|
||||
XDG_RUNTIME_DIR: '/run/user/1000',
|
||||
DBUS_SESSION_BUS_ADDRESS: 'unix:path=/run/user/1000/bus',
|
||||
});
|
||||
});
|
||||
|
||||
it('no-ops when XDG_RUNTIME_DIR is already set (SSH login path)', () => {
|
||||
const result = computeUserSystemdEnv({
|
||||
uid: 1000,
|
||||
user: 'nanoclaw',
|
||||
env: { XDG_RUNTIME_DIR: '/run/user/1000' },
|
||||
exists: existsFrom(
|
||||
new Set(['/var/lib/systemd/linger/nanoclaw', '/run/user/1000']),
|
||||
),
|
||||
});
|
||||
|
||||
expect(result.reason).toBe('already_set');
|
||||
expect(result.XDG_RUNTIME_DIR).toBeUndefined();
|
||||
expect(result.DBUS_SESSION_BUS_ADDRESS).toBeUndefined();
|
||||
});
|
||||
|
||||
it('returns no_linger when the linger marker is absent', () => {
|
||||
const result = computeUserSystemdEnv({
|
||||
uid: 1000,
|
||||
user: 'nanoclaw',
|
||||
env: {},
|
||||
exists: existsFrom(new Set(['/run/user/1000'])),
|
||||
});
|
||||
|
||||
expect(result.reason).toBe('no_linger');
|
||||
expect(result.XDG_RUNTIME_DIR).toBeUndefined();
|
||||
});
|
||||
|
||||
it('returns no_runtime_dir when linger is on but /run/user/<uid> is missing', () => {
|
||||
// The defensive guard from #2482: without this we would point env vars
|
||||
// at a non-existent socket and the daemon-reload probe would fail with a
|
||||
// less recoverable error than the bare "No medium found".
|
||||
const result = computeUserSystemdEnv({
|
||||
uid: 1000,
|
||||
user: 'nanoclaw',
|
||||
env: {},
|
||||
exists: existsFrom(new Set(['/var/lib/systemd/linger/nanoclaw'])),
|
||||
});
|
||||
|
||||
expect(result.reason).toBe('no_runtime_dir');
|
||||
expect(result.XDG_RUNTIME_DIR).toBeUndefined();
|
||||
});
|
||||
|
||||
it('returns no_user when USER and LOGNAME are both missing', () => {
|
||||
const result = computeUserSystemdEnv({
|
||||
uid: 1000,
|
||||
user: undefined,
|
||||
env: {},
|
||||
exists: existsFrom(new Set()),
|
||||
});
|
||||
|
||||
expect(result.reason).toBe('no_user');
|
||||
});
|
||||
|
||||
it('returns no_uid when process.getuid is unavailable', () => {
|
||||
const result = computeUserSystemdEnv({
|
||||
uid: undefined,
|
||||
user: 'nanoclaw',
|
||||
env: {},
|
||||
exists: existsFrom(new Set()),
|
||||
});
|
||||
|
||||
expect(result.reason).toBe('no_uid');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,38 @@
|
||||
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`,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user