feat(install): auto-start the dashboard on boot via systemd, no Claude Code needed

Adds ccam service install/uninstall/status for checkout installs and wires
the same systemd --user unit into plugin-bootstrap.js, so plugin-installed
colleagues get boot persistence too instead of needing a Claude Code
session to (re)start the server after every reboot.
This commit is contained in:
2026-08-28 16:15:45 +07:00
parent 8bd74a57e6
commit 1cb7ac0a4b
5 changed files with 303 additions and 7 deletions
+36 -3
View File
@@ -8,7 +8,10 @@
*
* The worker installs runtime dependencies, removes hook entries left behind by
* an older `npm run install-hooks` (they would double-count every event), puts
* the `ccam` CLI on PATH, and starts the dashboard server detached.
* the `ccam` CLI on PATH, and starts the dashboard server — as a systemd
* `--user` service when available (see `systemd-service.js`), so it survives
* reboots without Claude Code being opened first; otherwise a detached spawn
* scoped to this session, same as before.
*
* Everything writable lives under `~/.claude/agent-dashboard/runtime/` — NOT in
* the plugin cache, which Claude Code garbage-collects and replaces wholesale
@@ -466,8 +469,38 @@ function bootstrap(rt = runtimeDir(), { force = false } = {}) {
}
if (movedRoot || !serverIsLive()) {
const pid = startDashboard(rt);
log(rt, `started the dashboard server (pid ${pid})`);
const svc = require("./systemd-service");
const result = svc.systemctlAvailable()
? svc.installUserService({
name: "ccam",
description: "CCAM dashboard server (Claude Code plugin)",
workingDir: PLUGIN_ROOT,
execStart: `${process.execPath} ${path.join(PLUGIN_ROOT, "server", "index.js")}`,
env: {
NODE_ENV: "production",
NODE_PATH: path.join(rt, "node_modules"),
DASHBOARD_CLIENT_DIST: path.join(rt, "client-dist"),
},
})
: "unavailable";
if (result === "written" || result === "unchanged") {
if (result === "written") svc.enableLinger();
log(
rt,
result === "written"
? "installed a systemd --user service — the dashboard now survives reboots without Claude Code"
: "systemd --user service already up to date"
);
} else {
// No systemd (macOS/Windows/WSL without it), or the unit exists but
// wasn't ours to touch — same detached-spawn fallback as before,
// scoped to this session only.
const pid = startDashboard(rt);
log(
rt,
`started the dashboard server (pid ${pid})${result === "foreign" ? " — a foreign systemd unit named 'ccam' already exists" : ""}`
);
}
}
writeState(