prospector/docs/features/deploy.md

5.9 KiB

Deploy — prod backend on the DO droplet (lime / lilith-store-backend)

Target (probed 2026-06-29): lime = lilith-store-backend, Ubuntu 24.04, public 209.38.51.98 · wg 10.9.0.5 · VPC 10.20.0.2. Co-tenant: mac-sync-server (Bun). Postgres 16 + pgbouncer (socket/pooler, not TCP). Node 18 present — NestJS 11 needs Node 20+ (the one box change). 74G free. SSH alias lime (root, ~/.ssh/id_ed25519_1984).

Dev UI reaches prod over the WG mesh (10.9.0.5:3210) — no public TLS/DNS needed.

⚠️ These steps sudo-write a SHARED prod host. They were blocked under auto mode (correctly). Run them in a non-auto session, or grant a Bash(ssh lime *) permission rule, or run them yourself.

1. Node 20 on the droplet

ssh lime 'curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash - && sudo apt-get install -y nodejs && node -v'

(mac-sync uses Bun, so a system Node bump is safe for it.)

2. Create the two DBs — on the DO Managed Postgres cluster

There is no local Postgres. The droplet's pgbouncer (:6432) fronts a DO Managed Postgres cluster: private-lilith-store-pg-do-user-28217120-0.l.db.ondigitalocean.com:25060 (holds the live quinn DB). So people + prospector are new databases on that managed cluster (additive — does NOT touch quinn):

  • Via Terraform IaC (the DO infra is Terraform-managed in uvlava/terraform/do). The DBs + dedicated users are already declared (pg_databases += people/prospector; digitalocean_database_user.{people,prospector}). Just apply:
    cd ~/Code/@projects/uvlava/terraform/do
    TF_VAR_do_token=<your DO token> terraform apply   # additive: +2 dbs, +2 users, 0 destroy
    terraform output -raw people_db_password
    terraform output -raw prospector_db_password
    terraform output -raw pg_host        # private cluster host for the .env
    
  • Services connect directly to the managed endpoint over SSL (skip the shared pgbouncer to avoid touching live pooling): *_DB_HOST=private-lilith-store-pg-..., *_DB_PORT=25060, *_DB_SSL=true. (Optionally add [databases] entries to /etc/pgbouncer/pgbouncer.ini + reload to pool them, but that touches shared infra.)

3. Apply migrations

# prospector
for f in 0001_prospector 0002_drafts 0003_corrections; do
  ssh lime "sudo -u postgres psql -d prospector" < migrations/$f.sql ; done
# people (from the cocottetech repo)
ssh lime "sudo -u postgres psql -d people" < <people-service>/migrations/0001_people.sql

4. Ship the built code

Build locally, rsync dist + manifests, install prod deps on the droplet:

npm run build && npm run build -w @prospector/mcp-prospector
rsync -az --delete dist package.json package-lock.json migrations lime:/opt/prospector/
ssh lime 'cd /opt/prospector && npm ci --omit=dev'
# people-service likewise to /opt/people-service

5. Env on the droplet (/opt/prospector/.env)

NODE_ENV=production
PROSPECTOR_API_PORT=3210
PROSPECTOR_DB_HOST=private-lilith-store-pg-do-user-28217120-0.l.db.ondigitalocean.com
PROSPECTOR_DB_PORT=25060          # DO managed cluster (direct, SSL)
PROSPECTOR_DB_SSL=true
PROSPECTOR_DB_NAME=prospector
PROSPECTOR_DB_USER=prospector
PROSPECTOR_DB_PASSWORD=<from doctl databases user create>
PROSPECTOR_SERVICE_TOKEN=<strong-token>
PEOPLE_BASE_URL=http://127.0.0.1:3061
PEOPLE_SERVICE_TOKEN=<people-token>
MACSYNC_BASE_URL=http://127.0.0.1:3201   # mac-sync runs on this same droplet
MACSYNC_SERVICE_TOKEN=<macsync-token>
MACSYNC_DEVICE_ID=<device>
MRNUMBER_BASE_URL=https://my.transquinnftw.com
MRNUMBER_SERVICE_TOKEN=<mr-token>

(people-service gets its own /opt/people-service/.env with PEOPLE_DB_* + PEOPLE_SERVICE_TOKEN.)

6. systemd units (/etc/systemd/system/{prospector,people-service}.service)

[Service]
WorkingDirectory=/opt/prospector
EnvironmentFile=/opt/prospector/.env
ExecStart=/usr/bin/node dist/main.js
Restart=always
User=root
[Install]
WantedBy=multi-user.target

sudo systemctl enable --now people-service prospectorcurl localhost:3061/health, curl localhost:3210/health.

7. Wire mac-sync → prospector webhook

In the @mac-sync server (same droplet): on a new inbound, fire-and-forget POST http://127.0.0.1:3210/internal/inbound with Authorization: Bearer $PROSPECTOR_SERVICE_TOKEN, body {handle, channel:'imessage', text, occurredAt, hasCallSignal?}. Env-gated (PROSPECTOR_WEBHOOK_URL/token) so macsync runs standalone if unset. (Redo cleanly — the earlier agent left partial edits in @mac-sync.)

8. Point the dev UI at prod (over the mesh)

web/.env.local:

PROSPECTOR_API_URL=http://10.9.0.5:3210
PROSPECTOR_SERVICE_TOKEN=<the prod PROSPECTOR_SERVICE_TOKEN>

Restart npm run dev -w @prospector/web. The vite proxy injects the token; the panel now shows real prod decisions.

Verify (go-live)

/health both services → real inbound (or prospector_submit_inbound) → appears in prospector/activity → kill-switch flip persists → dev UI shows it over the mesh.

Post-migration notes (2026-06-29 unification)

  • Run new migrations: for f in migrations/0006_bilingual.sql ; do ssh lime "sudo -u postgres psql -d prospector" < $f ; done
  • Bilingual now in prospect_drafts (original/translated/detected_lang); Triage/Detail/Reports use dual when present (data from macsync inbound + future classifier trans).
  • MCP (@packages/mcp-prospector) now exposes full tools (prospector_* + legacy mappings for cockpit parity): list, thread, draft, send, mr, pastebin, reports, markets, classify, submit, held, activity, etc. Use with PROSPECTOR_BASE_URL + TOKEN. Replaces LP mcp-prospector.
  • UI fused: Triage = designs/main-view + inbox-ops + LP Stream; Reports = 4 reports + engine subs (Experiments/Patterns/Actions); Queue = queued-tasks + owed/backfill; etc. PWA install in Control.
  • LP can now drop prospector (see MIGRATION-PLAN in session plan file for removal list + proxies during cutover).
  • Rebuild/redeploy mcp + app after changes.