# Server deploy changes (box + terminal + dotfiles + webhooks + notifications) Everything the production host / compose / `.env` needs for the per-user box, the terminal, the personalized dev environment, GitHub webhooks, and notifications. Most items have safe defaults; the **Required** ones are the only must-dos. ## Required 1. **Build-time env for the Next image** — add to the build env file (the one CI / `scripts/build-next-app` passes as build args; e.g. `DOTENV_PROD`): ``` NEXT_PUBLIC_SPOON_AGENT_WORKER_WS_URL=wss://worker.spoon.gbrown.org ``` This is a `NEXT_PUBLIC` (build-time) var — it must be present **when the `spoon-next` image is built**, not just at runtime. Already wired into `docker/Dockerfile` + `docker/compose.yml` build args. Without it, the workspace **Terminal** tab shows "not configured". 2. **nginx: expose the worker for the terminal WebSocket.** Add a TLS server block proxying the worker domain to the worker on the shared network, with WS upgrade: ```nginx server { listen 443 ssl; server_name worker.spoon.gbrown.org; # + your ssl_certificate lines location / { proxy_pass http://spoon-agent-worker:3921; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header Host $host; proxy_read_timeout 86400s; proxy_send_timeout 86400s; } } ``` 3. **Rebuild + redeploy all three images** (CI does this on push to `main`): `spoon-next`, `spoon-agent-worker`, and `spoon-agent-job` (now **Fedora**). The worker auto-`docker pull`s the job image once per process, so a worker restart picks up the new Fedora job image. Make sure the prod registry has the new `spoon-agent-job:latest`. 4. **Deploy Convex functions** (new tables `userDotfiles`, `userEnvironment`, `notifications`, `notificationPreferences`). `SPOON_ENCRYPTION_KEY` (or `INSTANCE_SECRET`) is already required and is what encrypts dotfiles at rest — no change, just confirm it's set. 5. **Confirm `SPOON_AGENT_HOST_WORKDIR`** on the `spoon-agent-worker` service is the absolute host path backing `SPOON_AGENT_WORKDIR` (the fix from the terminal work). The per-user homes live under `${SPOON_AGENT_WORKDIR}/homes/{username}` and are bind-mounted into the box via the host daemon — this only resolves if the host-workdir translation is correct. (No new var; just verify.) 6. **Configure the GitHub App webhook.** Set the App's webhook URL to `/webhooks/github` and set `GITHUB_APP_WEBHOOK_SECRET` in the **Convex** production env (the signature-verified `httpAction` runs in Convex, not the worker). Without the secret the endpoint fails closed (HTTP 503) and drift only refreshes via the hourly cron. `push` refreshes affected Spoons; `installation` / `installation_repositories` update connection status. 7. **Notifications email (optional).** In-app notifications work with no config. To also send email, set `USESEND_API_KEY`, `USESEND_URL`, and `USESEND_FROM_EMAIL` in the Convex env. Per-user, per-kind email toggles live in `Settings → Notifications`; unset preferences default to on. 8. **Claude Code runtime.** The box image pins `@anthropic-ai/claude-code`, so Anthropic provider profiles (API key or `anthropic_oauth_json` credential snapshot) can drive the `claude` runtime with no extra host env. Anthropic API-key auth flows through `ANTHROPIC_API_KEY`; OAuth snapshots are written to `~/.claude/.credentials.json` in the box. ## Optional (safe defaults — only set to override) On the `spoon-agent-worker` service: | Var | Default | Purpose | | ----------------------------- | ------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- | | `SPOON_AGENT_TERMINAL_SECRET` | falls back to `SPOON_AGENT_WORKER_INTERNAL_TOKEN` | HMAC secret for terminal/box tokens (must match the Next app's, which also falls back). Leave unset to use the shared token. | | `SPOON_AGENT_BOX_IDLE_MS` | `1800000` (30m) | How long a per-user box survives idle before being reaped. | No new env is needed for dotfiles, the per-user home, or the Nerd Font. `SPOON_AGENT_TERMINAL_IMAGE` and `SPOON_AGENT_TERMINAL_IDLE_MS` were removed — the box uses `SPOON_AGENT_JOB_IMAGE` and `SPOON_AGENT_BOX_IDLE_MS`. ## Notes / one-time cleanup - **Layout change:** thread checkouts moved from `${WORKDIR}/{jobId}/repo` to `${WORKDIR}/homes/{username}/Code/{spoon}/{branch}` (persistent). Old per-job dirs are orphaned and safe to delete. - **Containers:** per-thread agent containers (`docker run --rm`) and per-job terminal containers (`spoon-agent-term-*`) are gone; everything runs in one `spoon-box-{username}` per user. Any lingering `spoon-agent-term-*` containers can be removed. - **Resources:** each active user holds one box (4 GB mem cap, `sleep infinity`) until 30m idle. Single-user = one box. - Compose already mounts `/var/run/docker.sock` into the worker (unchanged) — the box is created/exec'd through it. ## Quick post-deploy checks ```bash docker exec spoon-agent-worker docker --version # CLI present (29.x) docker run --rm git.gbrown.org/gib/spoon-agent-job:latest codex --version # 0.142 docker run --rm git.gbrown.org/gib/spoon-agent-job:latest claude --version # 2.1.207 docker run --rm git.gbrown.org/gib/spoon-agent-job:latest opencode --version docker run --rm git.gbrown.org/gib/spoon-agent-job:latest bash -lc 'eza --version; zoxide --version; oh-my-posh --version' # then: /machine → box status + terminal; open a thread → Terminal tab; # Settings → Dotfiles add a .bashrc alias; Settings → Notifications toggles. ```