wraps the self-hosted memos instance, same shape as transmission and slskd. no schema change was needed: service_connections already says `service` is text because "adding a service should not be a schema change", and memos is the one-instance-per-owner case that table was built for. the sidecar holds the url and the personal access token; the platform side is 16 lines of createSidecarProxy and holds neither. /_api/* is a pass-through onto the instance's own /api/v1 rather than a hand-written wrapper per endpoint — memos generates its rest api from protobufs and it moves between minor versions, so re-describing it here would be a second thing to keep in sync. the allow-list is the one piece of policy, and it keeps this from being a general ssrf hop. auth routes are excluded: signin/signout would mint sessions on the instance, and this authenticates with a stored token. probing is two calls on purpose. /healthz answers unauthenticated, so a bad url is distinguishable from a bad token — memos returns 200 and an empty list for unauthenticated reads rather than 401, so "the list came back" proves nothing. verified against the live container: unconfigured reports not-connected, a bad token is rejected WITH the reason and nothing is stored, and the platform mount 401s without a session. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
141 lines
5.1 KiB
JavaScript
141 lines
5.1 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,
|
|
},
|
|
{
|
|
name: 'officer-transmission',
|
|
script: 'bun',
|
|
args: 'run src/servers/sidecar/transmission/index.ts',
|
|
watch: false,
|
|
},
|
|
// The books. Wraps a self-hosted InvoiceShelf. Instances, their Sanctum tokens and the company each one
|
|
// is pinned to are set by the owner from /invoices/settings and stored encrypted in
|
|
// `invoiceshelf_accounts` — read here, never from the environment, because Bun auto-loads `.env` into
|
|
// every process in this directory and `officer` would hold the token too.
|
|
{
|
|
name: 'officer-invoiceshelf',
|
|
script: 'bun',
|
|
args: 'run src/servers/sidecar/invoiceshelf/index.ts',
|
|
watch: false,
|
|
},
|
|
// Notes. Wraps a self-hosted Memos. The instance URL and its personal access token are set by the
|
|
// owner from the UI and stored in `service_connections` — read here, never from the environment.
|
|
{
|
|
name: 'officer-memos',
|
|
script: 'bun',
|
|
args: 'run src/servers/sidecar/memos/index.ts',
|
|
watch: false,
|
|
},
|
|
// Calendar and contacts. Supervises Radicale (CalDAV/CardDAV) on a loopback port and owns the
|
|
// collections under DATA_PATH/dav. Two doors: /dav for phones (DAVx5, iOS, Thunderbird — HTTP Basic
|
|
// against a scoped app password) and /api/caldav for Officer's own UI. The protocol is Radicale's;
|
|
// the platform authenticates and forwards. See docs/nextcloud-replacement.md.
|
|
{
|
|
name: 'officer-caldav',
|
|
script: 'bun',
|
|
args: 'run src/servers/sidecar/caldav/index.ts',
|
|
watch: false,
|
|
},
|
|
// The photo library. Wraps a self-hosted Immich. The instance and its key are set by the owner from
|
|
// /photos/settings and stored encrypted in `photos_config` — read here, never from the environment,
|
|
// because Bun auto-loads `.env` into every process in this directory and `officer` would hold it too.
|
|
{
|
|
name: 'officer-photos',
|
|
script: 'bun',
|
|
args: 'run src/servers/sidecar/photos/index.ts',
|
|
watch: false,
|
|
},
|
|
// The bitcoin wallet. Holds seed material (sealed under an owner passphrase) and node credentials, so
|
|
// it is the one sidecar whose restart has a security-relevant side effect: every wallet relocks.
|
|
// The one place anything leaves this machine to tell the owner something: push (APNs + FCM) and the
|
|
// Discord webhook, behind one interface. A sidecar rather than platform code because the producers
|
|
// are spread across sidecars, and a platform-owned notifier would make every one of them call back in.
|
|
{
|
|
name: 'officer-notify',
|
|
script: 'bun',
|
|
args: 'run src/servers/sidecar/notify/index.ts',
|
|
watch: false,
|
|
},
|
|
{
|
|
name: 'officer-wallet',
|
|
script: 'bun',
|
|
args: 'run src/servers/sidecar/wallet/index.ts',
|
|
watch: false,
|
|
},
|
|
],
|
|
};
|