officer-headscale owns the whole Headscale contract: the registered servers and their admin api keys, the >=0.29 version floor, and every multi-call composition the ui needs. the platform side is auth+forward only and holds no headscale credentials, so the existing /api/vpn/enroll route and its HEADSCALE_* env vars are untouched and unrelated. officer manages many servers rather than one. the owner registers each with a url and a key generated on that server and switches between them; exactly one is active, enforced by a partial unique index rather than by convention. keys are encrypted at rest and never leave the sidecar — the list projection cannot return one. registration validates before it saves: an unauthenticated GET /version to prove something headscale-shaped is there and meets the floor, then an authenticated call to prove the key works. an edit that moves either half re-validates. there is deliberately no transparent /api/v1/* passthrough. headscale serialises every uint64 as a json string and its rest shape moved repeatedly below 0.29; proxying raw would push all of that into the browser, which is the mistake the soulseek panels made with 37 raw upstream calls. the /headscale workspace is nav + view over the panel system. only the servers section is implemented — nodes, users and pre-auth keys say so plainly rather than rendering an empty table that reads as a failed fetch. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
81 lines
2.3 KiB
JavaScript
81 lines
2.3 KiB
JavaScript
module.exports = {
|
|
apps: [
|
|
{
|
|
name: 'officer',
|
|
script: 'bun',
|
|
args: 'start',
|
|
watch: false,
|
|
},
|
|
// The Anthropic credential proxy. Despite the old name (`officer-claude`) this process does NOT
|
|
// run agents — it holds the proxy secret and forwards to api.anthropic.com. The process that runs
|
|
// agents is `officer-agent` below.
|
|
{
|
|
name: 'officer-anthropic-proxy',
|
|
script: 'bun',
|
|
args: 'run src/servers/sidecar/claude/index.ts',
|
|
watch: false,
|
|
},
|
|
// The process that actually runs `claude`. It used to be spawned on demand by the main server,
|
|
// which made every agent session a grandchild of `officer` and killed it on every restart. As a PM2
|
|
// peer it survives them. It resolves the owner from the database and the proxy secret from the
|
|
// proxy's state file, so it needs nothing from `officer` in order to start.
|
|
{
|
|
name: 'officer-agent',
|
|
script: 'bun',
|
|
args: 'run src/servers/sidecar/claude/user-instance.ts',
|
|
watch: false,
|
|
},
|
|
{
|
|
name: 'officer-opencode',
|
|
script: 'bun',
|
|
args: 'run src/servers/sidecar/opencode/index.ts',
|
|
watch: false,
|
|
},
|
|
{
|
|
name: 'officer-email',
|
|
script: 'bun',
|
|
args: 'run src/servers/sidecar/email/index.ts',
|
|
watch: false,
|
|
},
|
|
// The only sidecar run by `node` rather than `bun`, and the only one that is not TypeScript: node-pty
|
|
// is a native addon. It also does not use sidecar/connect.ts, and carries its own copy of the
|
|
// reconnect loop.
|
|
{
|
|
name: 'officer-pty',
|
|
script: 'node',
|
|
args: 'src/servers/sidecar/pty/index.mjs',
|
|
watch: false,
|
|
},
|
|
{
|
|
name: 'officer-vnc',
|
|
script: 'bun',
|
|
args: 'run src/servers/sidecar/vnc/index.ts',
|
|
watch: false,
|
|
},
|
|
{
|
|
name: 'officer-music',
|
|
script: 'bun',
|
|
args: 'run src/servers/sidecar/music/index.ts',
|
|
watch: false,
|
|
},
|
|
{
|
|
name: 'officer-vault',
|
|
script: 'bun',
|
|
args: 'run src/servers/sidecar/vault/index.ts',
|
|
watch: false,
|
|
},
|
|
{
|
|
name: 'officer-slskd',
|
|
script: 'bun',
|
|
args: 'run src/servers/sidecar/slskd/index.ts',
|
|
watch: false,
|
|
},
|
|
{
|
|
name: 'officer-headscale',
|
|
script: 'bun',
|
|
args: 'run src/servers/sidecar/headscale/index.ts',
|
|
watch: false,
|
|
},
|
|
],
|
|
};
|