diff --git a/ecosystem.mac.config.cjs b/ecosystem.mac.config.cjs index a6bd235f..4927d7b3 100644 --- a/ecosystem.mac.config.cjs +++ b/ecosystem.mac.config.cjs @@ -1,18 +1,40 @@ // macOS process list — the laptop subset of ecosystem.config.cjs. // -// Only what a Mac install can actually run and what the laptop workflow needs (file browser + -// Claude/opencode chat + terminal). Deliberately omitted: -// officer-vnc — mirrors an Xorg :0 with x11vnc; there is no Xorg on macOS. -// officer-email — needs the mbsync/IMAP stack that setup_mac.sh does not install. -// officer-music — the indexer is ffprobe-driven and works, but it is not part of the laptop -// workflow and a full ~/Music index is an expensive thing to start by default. +// Only what a Mac install can actually run and what the laptop workflow needs: the file browser, the +// terminal, and Claude/opencode chat. Everything else on the Linux host is either tied to Xorg, tied to +// a container the laptop does not run, or holds material that has no business on a laptop. +// +// KEEP THIS IN STEP WITH ecosystem.config.cjs. It was written on 2026-07-28 and was broken by two +// architecture changes within days, which is the failure mode to watch for: +// - `officer-claude` was split into `officer-anthropic-proxy` (holds the credential) and +// `officer-agent` (spawns `claude`). This file ran only the first under the old name, so chat had a +// credential holder and nothing driving it. +// - the pty sidecar moved to src/servers/sidecar/pty/index.mjs. This file still pointed at +// src/servers/api/terminal/pty-sidecar.mjs, which no longer exists, so the terminal never started. +// +// Deliberately omitted, and why — so the next person can tell "not applicable" from "forgotten": +// officer-vnc mirrors an Xorg display with x11vnc; macOS has no Xorg to mirror. +// officer-email needs the mbsync/IMAP stack that setup_mac.sh does not install. +// officer-music the ffprobe indexer works, but a full ~/Music index is an expensive thing to +// start by default and it is not part of the laptop workflow. +// officer-vault a reverse proxy to a self-hosted Vaultwarden container. +// officer-slskd supervises the slskd daemon. +// officer-headscale fronts a headscale server. +// officer-transmission fronts a transmission daemon. +// officer-invoiceshelf fronts an InvoiceShelf container. +// officer-memos needs an owner-configured Memos instance URL and token. +// officer-photos needs an owner-configured Immich instance URL and API key. +// officer-caldav supervises Radicale, which setup_mac.sh does not install. +// officer-notify the outbound notifier; its producers are the queue and the email/agent +// sidecars, so on a laptop there is nothing to notify about. +// officer-wallet holds seed and node credentials. Not on a laptop. // // Start with: pm2 startOrRestart ecosystem.mac.config.cjs // The Linux host keeps using ecosystem.config.cjs; neither file references the other. // -// `cwd` is pinned on every app because Bun auto-loads .env from the working directory (and -// pty-sidecar.mjs does `import 'dotenv/config'`). Without it, starting pm2 from anywhere other than -// the repo root silently falls back to PORT=5000 with no POSTGRES_URL. +// `cwd` is pinned on every app because Bun auto-loads .env from the working directory (and the pty +// sidecar does `import 'dotenv/config'`). Without it, starting pm2 from anywhere other than the repo +// root silently falls back to PORT=5000 with no POSTGRES_URL. const cwd = __dirname; @@ -25,13 +47,25 @@ module.exports = { cwd, watch: false, }, + // Holds the proxy secret and forwards to api.anthropic.com. Does NOT run agents — the old + // `officer-claude` name covered both jobs and is exactly how this file ended up with half of chat. { - name: 'officer-claude', + name: 'officer-anthropic-proxy', script: 'bun', args: 'run src/servers/sidecar/claude/index.ts', cwd, watch: false, }, + // The process that actually spawns `claude`. Resolves the owner from the database and the proxy + // secret from disk, so it needs no startup ordering against the proxy above: if the secret is not + // written yet it warns and re-reads before the next spawn. + { + name: 'officer-agent', + script: 'bun', + args: 'run src/servers/sidecar/claude/user-instance.ts', + cwd, + watch: false, + }, { name: 'officer-opencode', script: 'bun', @@ -39,10 +73,12 @@ module.exports = { cwd, watch: false, }, + // node, not bun — same as the Linux host. The pty sidecar is a .mjs that binds node-pty's native + // addon, which is built against node's ABI. { name: 'officer-pty', script: 'node', - args: 'src/servers/api/terminal/pty-sidecar.mjs', + args: 'src/servers/sidecar/pty/index.mjs', cwd, watch: false, }, diff --git a/scripts/setup_mac.sh b/scripts/setup_mac.sh index b4c76900..ac18e07c 100755 --- a/scripts/setup_mac.sh +++ b/scripts/setup_mac.sh @@ -442,6 +442,23 @@ elif confirm SETUP_SERVICES "Start Officer and its sidecars with pm2?" y; then note_failure "pm2 could not start the services — check 'pm2 logs'" fi if pm2 save >/dev/null 2>&1; then ok "process list saved"; else warn "pm2 save failed"; fi + + # `pm2 save` on its own writes a process list that nothing ever reads. The Linux setup pairs it with + # `pm2 startup`, which installs the boot unit that resurrects that list; without the pair, a reboot + # silently leaves the machine with nothing running. On macOS the equivalent is a launchd agent rather + # than a systemd unit. Kept optional because a laptop is not a server — you may not want the whole + # stack coming back at every login — and non-fatal, because pm2's launchd integration can want an + # elevated prompt that a scripted run should not force. + if confirm SETUP_BOOT "Start Officer automatically at login (pm2 + launchd)?" y; then + if pm2 startup launchd -u "$(whoami)" --hp "$HOME" >/dev/null 2>&1; then + pm2 save >/dev/null 2>&1 + ok "services will start at login" + else + warn "Could not install the launchd agent — run: pm2 startup (and follow its instructions)" + fi + else + skip "login startup (declined — 'pm2 startup' enables it later)" + fi else skip "services (declined)" fi @@ -504,7 +521,9 @@ echo "Notes:" echo " • Open ${ENV_PUBLIC_URL:-http://localhost:9010} — the first-run screen creates the owner account" echo " • Logs: pm2 logs Restart: pm2 restart ecosystem.mac.config.cjs" echo " • Postgres must be running before the services start, or db:push and boot will fail" -echo " • Not installed on macOS: VNC desktop, email sync, music indexer, cliamp audio" +echo " • Not run on macOS: VNC desktop, email sync, music indexer, cliamp audio, and the sidecars" +echo " that front a container or an external service — vault, slskd, headscale, transmission," +echo " invoiceshelf, memos, photos, caldav, notify, wallet. See ecosystem.mac.config.cjs." echo " • Pin a specific Claude CLI with CLAUDE_BIN=/path/to/claude in .env if you need to" echo " • Re-run any single step with e.g. SETUP_OPENCODE=1 bash scripts/setup_mac.sh" echo ""