The old name said nothing about what the process runs, and it sits directly beside officer-anthropic-proxy — a different process doing a different job — so "the agent" was ambiguous exactly where it mattered. CLAUDE.md already had to spend a paragraph insisting the two are not the same thing. It spawns `claude`; the name says so now. Only two references were functional: the generator's CORE_PROCESSES and the CORE list in catalogue.test.ts. Everything else was prose or comments. Left alone deliberately: `x-officer-agent-token`. It looks like the same string and is not — it is the agent-handoff HTTP header, naming a per-panel bearer token, unrelated to any pm2 process. Renaming it would have changed a wire protocol to tidy a label. Historical docs keep the old name. claude-sidecar-isolation.md and open-threads-after-per-user-claude.md are dated investigations that record the PREVIOUS rename, from officer-claude to officer-agent, and rewriting them would make that history unreadable. CLAUDE.md notes the change instead, where somebody reading those will be looking. Also worth recording, from the owner: merging this with officer-anthropic-proxy into one sidecar was investigated tonight and rejected. They stay separate. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
116 lines
4.6 KiB
Bash
116 lines
4.6 KiB
Bash
#!/bin/bash
|
|
# =============================================================================
|
|
# officer-setup — the pm2 ecosystem file, and starting the processes
|
|
# =============================================================================
|
|
#
|
|
# Definitions only.
|
|
#
|
|
# ── The ecosystem file is GENERATED, and is not in git ──
|
|
#
|
|
# There used to be four of them — ecosystem.config.cjs, .light., .mac.light. and
|
|
# a .profile. that the others derived from. A profile deriving from a full list
|
|
# means the full list has to exist, which means every plugin's process is
|
|
# described in the repository whether or not anybody installed it, and a test had
|
|
# to assert that the two files still agreed with each other.
|
|
#
|
|
# One generated file removes all of that. It describes exactly the processes this
|
|
# install runs, it is written once at setup, and nothing in git can drift from
|
|
# it. A plugin adds its own entry when it is installed.
|
|
#
|
|
# ── Why .cjs and not .js ──
|
|
#
|
|
# PM2's own convention is ecosystem.config.js, and it would be wrong here:
|
|
# package.json declares "type": "module", so a .js file in this directory is ESM
|
|
# and `module.exports` throws "module is not defined in ES module scope". PM2
|
|
# require()s the config, so the extension has to say CommonJS out loud.
|
|
|
|
[[ -n "${OFFICER_SETUP_SERVICES_LOADED:-}" ]] && return 0
|
|
OFFICER_SETUP_SERVICES_LOADED=1
|
|
|
|
ecosystem_file() { echo "$(platform_dir)/ecosystem.config.cjs"; }
|
|
|
|
# The processes a core install runs. Everything else is a plugin.
|
|
#
|
|
# `officer-pty` is node rather than bun, and that is not an oversight: it loads
|
|
# node-pty, a native module built against Node's ABI. Everything else is bun.
|
|
#
|
|
# `officer-claude-code` was `officer-agent` until 2026-08-13. The old name said
|
|
# nothing about what it runs, and it sits beside officer-anthropic-proxy — which
|
|
# is a different process doing a different job — so "the agent" was ambiguous
|
|
# exactly where it mattered. It spawns `claude`; the name says so now.
|
|
CORE_PROCESSES=(
|
|
"officer|bun|start"
|
|
"officer-anthropic-proxy|bun|run src/servers/sidecar/claude/index.ts"
|
|
"officer-claude-code|bun|run src/servers/sidecar/claude/user-instance.ts"
|
|
"officer-opencode|bun|run src/servers/sidecar/opencode/index.ts"
|
|
"officer-pty|node|src/servers/sidecar/pty/index.mjs"
|
|
"officer-headscale|bun|run src/servers/sidecar/headscale/index.ts"
|
|
)
|
|
|
|
write_ecosystem() {
|
|
local dest entry name script args
|
|
dest="$(ecosystem_file)"
|
|
|
|
{
|
|
cat <<'HEADER'
|
|
// Generated by officer-setup. Not in git, and not meant to be — it describes THIS
|
|
// install, and the next machine generates its own.
|
|
//
|
|
// `cwd` is pinned on every app for two reasons. Bun auto-loads .env from the
|
|
// working directory (and the pty sidecar does `import 'dotenv/config'`), so
|
|
// without it a process started from anywhere else comes up with no POSTGRES_URL.
|
|
// And src/servers/data-path.ts derives the install root as the PARENT of the
|
|
// working directory, so a wrong cwd does not fail — it relocates data/,
|
|
// capabilities/ and dockers/ somewhere else entirely. `assertInstallLayout`
|
|
// refuses to boot when that happens.
|
|
//
|
|
// To add a plugin later, add its entry here. Nothing derives this file from
|
|
// anything, so there is no second list to keep it agreeing with.
|
|
|
|
module.exports = {
|
|
apps: [
|
|
HEADER
|
|
for entry in "${CORE_PROCESSES[@]}"; do
|
|
IFS='|' read -r name script args <<<"$entry"
|
|
printf " { name: '%s', script: '%s', args: '%s', cwd: '%s', watch: false },\n" \
|
|
"$name" "$script" "$args" "$(platform_dir)"
|
|
done
|
|
cat <<'FOOTER'
|
|
],
|
|
};
|
|
FOOTER
|
|
} >"$dest"
|
|
|
|
chown "${USERNAME}:$(user_group)" "$dest"
|
|
return 0
|
|
}
|
|
|
|
pm2_start() {
|
|
sudo -u "$USERNAME" bash -c "cd '$(platform_dir)' && pm2 startOrRestart '$(ecosystem_file)' --update-env" 2>&1
|
|
}
|
|
|
|
pm2_save() { sudo -u "$USERNAME" pm2 save 2>&1; }
|
|
|
|
# Survive a reboot. `pm2 startup` PRINTS a command for root to run rather than
|
|
# doing it — so this runs what it prints, which is the whole point of already
|
|
# being root here.
|
|
pm2_enable_startup() {
|
|
local cmd
|
|
cmd="$(sudo -u "$USERNAME" bash -c "cd '$(platform_dir)' && pm2 startup systemd -u '$USERNAME' --hp '$USER_HOME'" 2>/dev/null | grep -E '^sudo ' | tail -1)"
|
|
[[ -z "$cmd" ]] && return 1
|
|
eval "${cmd#sudo }"
|
|
}
|
|
|
|
# One line per process: name, status, restarts.
|
|
pm2_status_lines() {
|
|
sudo -u "$USERNAME" pm2 jlist 2>/dev/null |
|
|
node -e '
|
|
let s = ""; process.stdin.on("data", (d) => (s += d)).on("end", () => {
|
|
let apps = []; try { apps = JSON.parse(s); } catch { }
|
|
for (const a of apps) {
|
|
const st = a.pm2_env?.status ?? "?";
|
|
console.log(`${a.name}|${st}|${a.pm2_env?.restart_time ?? 0}`);
|
|
}
|
|
});'
|
|
}
|