diff --git a/deploy/DEPLOY-NEW-SESSION-PROMPT.md b/deploy/DEPLOY-NEW-SESSION-PROMPT.md index 9ec3541..d6a87b4 100644 --- a/deploy/DEPLOY-NEW-SESSION-PROMPT.md +++ b/deploy/DEPLOY-NEW-SESSION-PROMPT.md @@ -20,15 +20,24 @@ The v1 build is complete (8 slices shipped, 125 passing integration tests). New The RFC app runs on its own dedicated GCP VM in a separate project from the Gitea VM. The two coexist under `wiggleverse.org` but are otherwise unrelated infrastructure. +> **⚠️ Infrastructure was realigned (GCP name-alignment, ~2026-05).** The +> GCP project, VM, install path, system user, and systemd unit were all +> renamed, the static IP changed, and SSH is now **IAP-only** (direct +> port-22 connections time out). The tables below are the current truth; +> if you find an older clone of this doc naming `wiggleverse-rfc` / +> `rfc-app` / `/opt/rfc-app` / `34.132.29.41`, it predates the alignment. + | Property | Value | |----------|-------| -| GCP project | `wiggleverse-rfc` | -| VM name | `rfc-app` | +| GCP project | `wiggleverse-ohm` | +| VM name | `ohm-rfc-app` | | VM type | e2-small | | Zone | us-central1-a | | OS | Debian 12 (bookworm) | -| Static IP | 34.132.29.41 | -| Linux user (OS Login) | `benstull` | +| External IP | `136.116.40.66` | +| SSH | **IAP-only** — `gcloud compute ssh … --tunnel-through-iap` (port 22 is firewalled off the public internet) | +| OS Login SSH user | `ben_wiggleverse_org` (auto-derived; you don't type it) | +| App system user | `ohm-rfc-app` | For reference, the separate Gitea VM is `wiggleverse` project / `gitea` VM / 34.55.46.221. @@ -36,7 +45,7 @@ For reference, the separate Gitea VM is `wiggleverse` project / `gitea` VM / 34. | Record | Type | Value | Proxy | |--------|------|-------|-------| -| `ohm.wiggleverse.org` | A | 34.132.29.41 | DNS-only (gray cloud) | +| `ohm.wiggleverse.org` | A | `136.116.40.66` | DNS-only (gray cloud) | | `_dmarc.wiggleverse.org` | TXT | `v=DMARC1; p=none; rua=mailto:ben@wiggleverse.org` | n/a | > Note: `ohm.wiggleverse.org` uses **Let's Encrypt via certbot** directly on the VM. Keep the A record **DNS only (gray cloud)** — Cloudflare Flexible SSL would conflict with certbot. @@ -48,24 +57,25 @@ SPF (`v=spf1 include:_spf.google.com ~all`) and DKIM (`google._domainkey`) for ` | Component | Details | |-----------|---------| | Backend | Python 3.11, FastAPI, uvicorn (single process) | -| Database | SQLite in WAL mode at `/opt/rfc-app/backend/data/rfc-app.db` | +| Database | SQLite in WAL mode at `/opt/ohm-rfc-app/backend/data/rfc-app.db` | | Frontend | React 19, Vite 8, Tiptap 3, React Router 7 | | Web server | nginx — serves `frontend/dist/` as static SPA, proxies `/api/` and `/auth/` to uvicorn on `127.0.0.1:8000` | -| Process manager | systemd unit `rfc-app.service`, runs as `rfc-app` system user | +| Process manager | systemd unit `ohm-rfc-app.service`, runs as `ohm-rfc-app` system user | | TLS | Let's Encrypt via certbot | -| Git backend | Gitea at `git.wiggleverse.org`, bot service account `rfc-bot` | +| Content Gitea (the bot's writes) | Gitea at `git.wiggleverse.org`, org `wiggleverse`, meta repo `ohm-content` (`wiggleverse/ohm-content`), bot service account `rfc-bot` | +| Code-deploy source (the VM's git origin) | **`https://git.benstull.org/benstull/rfc-app.git`** — a *different* Gitea from the content one. See the two-remote note under "Deploying a New Version." | | Email | Google Workspace SMTP relay (`smtp-relay.gmail.com:587`), AUTH'd as `ben@wiggleverse.org`, From `notifications@wiggleverse.org` | ### Key Paths on the VM | Path | Contents | |------|---------| -| `/opt/rfc-app/` | App root (owned by `rfc-app` user) | -| `/opt/rfc-app/backend/.env` | All secrets and config (mode 0600) | -| `/opt/rfc-app/backend/data/rfc-app.db` | SQLite database | -| `/opt/rfc-app/frontend/dist/` | Built React SPA (served by nginx) | +| `/opt/ohm-rfc-app/` | App root (owned by `ohm-rfc-app` user) | +| `/opt/ohm-rfc-app/backend/.env` | All secrets and config (mode 0600) | +| `/opt/ohm-rfc-app/backend/data/rfc-app.db` | SQLite database | +| `/opt/ohm-rfc-app/frontend/dist/` | Built React SPA (served by nginx) | | `/etc/nginx/sites-available/ohm.wiggleverse.org` | nginx vhost config | -| `/etc/systemd/system/rfc-app.service` | systemd unit | +| `/etc/systemd/system/ohm-rfc-app.service` | systemd unit | --- @@ -91,7 +101,7 @@ Created in Gitea as `rfc-bot`. Token scopes: `write:repository`, `write:user`, ` ### Meta repo -`wiggleverse/meta` — seeded by `scripts/seed_meta_repo.py`. Contains `PHILOSOPHY.md`, `README.md`, `CONTRIBUTING.md`, and `rfcs/` directory. Gitea webhook registered to `https://ohm.wiggleverse.org/api/webhooks/gitea`. +`wiggleverse/ohm-content` (the `META_REPO` value is `ohm-content`) — seeded by `scripts/seed_meta_repo.py`. Contains `PHILOSOPHY.md`, `README.md`, `CONTRIBUTING.md`, and `rfcs/` directory. Gitea webhook registered to `https://ohm.wiggleverse.org/api/webhooks/gitea`. ### OAuth2 app @@ -167,23 +177,52 @@ HYGIENE_TICK_SECONDS=3600 ## Deploying a New Version -SSH into the VM: +> **⚠️ Two-remote step — do this FIRST.** The framework's release flow +> (branches, PRs, version tags) happens on **`git.wiggleverse.org`** +> (`ben.stull/rfc-app`). But the VM's git origin is a *separate* Gitea, +> **`git.benstull.org/benstull/rfc-app`**, which does **not** auto-mirror +> from the release Gitea. So after a release is merged + tagged on +> `git.wiggleverse.org`, you must push `main` and the new tag to the +> `benstull` remote before the VM can fetch them: +> ```bash +> # from your local rfc-app clone, on main at the merged release tip: +> git push benstull main vX.Y.Z # benstull = git@git.benstull.org:benstull/rfc-app.git +> ``` +> If `git ls-remote benstull vX.Y.Z` comes back empty, the VM cannot see +> the release yet — push it first. (Wiring the two Gitea instances to +> mirror would remove this step; until then it's manual.) + +SSH into the VM (IAP-only — the `--tunnel-through-iap` flag is required): ```bash -gcloud compute ssh rfc-app --zone=us-central1-a --project=wiggleverse-rfc +gcloud compute ssh ohm-rfc-app --zone=us-central1-a --project=wiggleverse-ohm --tunnel-through-iap ``` -Pull the latest code, reinstall deps, restart: +Fetch + check out the release tag (the VM tracks a **detached** tag, not +a branch), then restart. Backend deps only need reinstalling when +`requirements.txt` changed: ```bash -sudo -u rfc-app git -C /opt/rfc-app pull -sudo -u rfc-app /opt/rfc-app/backend/.venv/bin/pip install \ - -r /opt/rfc-app/backend/requirements.txt -sudo systemctl restart rfc-app +sudo -u ohm-rfc-app git -C /opt/ohm-rfc-app fetch origin --tags +sudo -u ohm-rfc-app git -C /opt/ohm-rfc-app checkout vX.Y.Z +# only if backend deps changed: +sudo -u ohm-rfc-app /opt/ohm-rfc-app/backend/.venv/bin/pip install \ + -r /opt/ohm-rfc-app/backend/requirements.txt +sudo systemctl restart ohm-rfc-app ``` For frontend changes, build on the VM directly (Node 20+ is already there): ```bash -cd /opt/rfc-app/frontend && sudo -u rfc-app npm ci -sudo -u rfc-app npm run build +cd /opt/ohm-rfc-app/frontend && sudo -u ohm-rfc-app npm ci +sudo -u ohm-rfc-app npm run build +``` + +Smoke test (asset hashes change on every rebuild, so a match proves the +new bundle is live): +```bash +# public: +curl -s -o /dev/null -w '%{http_code} %{remote_ip}\n' https://ohm.wiggleverse.org/ +curl -s https://ohm.wiggleverse.org/ | grep -oE 'assets/index-[A-Za-z0-9_-]+\.(js|css)' +# backend startup line, on the VM: +sudo journalctl -u ohm-rfc-app -n 20 --no-pager | grep 'RFC app started' ``` `npm ci` installs strictly from the committed `package-lock.json` and @@ -192,7 +231,7 @@ to rewrite the lockfile in place (notably stripping `libc` fields on optional rollup native packages), which then conflicts with `git checkout ` on the next deploy. -The output lands in `/opt/rfc-app/frontend/dist/` owned by `rfc-app` — nginx serves it directly, no copy step needed. +The output lands in `/opt/ohm-rfc-app/frontend/dist/` owned by `ohm-rfc-app` — nginx serves it directly, no copy step needed. (Building locally and `gcloud compute scp`-ing the dist also works. Plain `rsync -e ssh` from the Mac fails because OS Login uses short-lived SSH certs that only the gcloud wrapper can mint interactively.) @@ -202,6 +241,15 @@ Schema migrations run automatically on restart (append-only, safe to re-run). ## First-Time Deployment (new server) +> **Note on names:** the command blocks in this section predate the GCP +> name-alignment and still spell the pre-alignment project/VM/path/user +> (`wiggleverse-rfc` / `rfc-app` / `/opt/rfc-app` / `rfc-app` / +> `34.132.29.41`). They're kept as the structural reference. When standing +> up a box today, substitute the current values from the Host/Paths tables +> at the top: project `wiggleverse-ohm`, VM `ohm-rfc-app`, install path +> `/opt/ohm-rfc-app`, system user `ohm-rfc-app`, service `ohm-rfc-app`, IP +> `136.116.40.66`, and SSH via `--tunnel-through-iap`. + ### 1. Add DNS record Add `ohm.wiggleverse.org` → 34.132.29.41 as an A record in Cloudflare, **DNS only (gray cloud)**. Do not proxy — certbot needs to reach the VM directly. @@ -307,7 +355,7 @@ Paste the following into a new Claude session to continue development: --- -> I'm working on the **Wiggleverse RFC App** — a FastAPI + SQLite + React + Vite application deployed at `ohm.wiggleverse.org` on a GCP e2-small VM (`rfc-app` in the `wiggleverse-rfc` project; separate from the `gitea` VM in `wiggleverse` that runs Gitea at `git.wiggleverse.org`). The app is the primary interface for the Open Human Model (OHM) RFC working group. +> I'm working on the **Wiggleverse RFC App** — a FastAPI + SQLite + React + Vite application deployed at `ohm.wiggleverse.org` on a GCP e2-small VM (`ohm-rfc-app` in the `wiggleverse-ohm` project; separate from the `gitea` VM in `wiggleverse` that runs Gitea at `git.wiggleverse.org`). The app is the primary interface for the Open Human Model (OHM) RFC working group. > > **Stack:** > - Backend: Python 3.11, FastAPI, uvicorn (single process), SQLite WAL mode @@ -323,11 +371,12 @@ Paste the following into a new Claude session to continue development: > - 10 append-only schema migrations in `backend/migrations/` > > **Deployment:** -> - SSH: `gcloud compute ssh rfc-app --zone=us-central1-a --project=wiggleverse-rfc` -> - Code at `/opt/rfc-app/` on the VM, owned by `rfc-app` system user -> - `.env` at `/opt/rfc-app/backend/.env` (mode 0600) -> - Frontend built **on the VM** (Node 20 is installed there) with `npm run build` directly into `/opt/rfc-app/frontend/dist/` -> - Restart to deploy: `sudo systemctl restart rfc-app` +> - SSH (IAP-only): `gcloud compute ssh ohm-rfc-app --zone=us-central1-a --project=wiggleverse-ohm --tunnel-through-iap` +> - Code at `/opt/ohm-rfc-app/` on the VM, owned by `ohm-rfc-app` system user; tracks a detached release **tag** +> - The VM's git origin is `git.benstull.org/benstull/rfc-app` — a *different* Gitea from the release one (`git.wiggleverse.org`); push `main` + the new tag to the `benstull` remote before deploying +> - `.env` at `/opt/ohm-rfc-app/backend/.env` (mode 0600) +> - Frontend built **on the VM** (Node 20 is installed there) with `npm run build` directly into `/opt/ohm-rfc-app/frontend/dist/` +> - Restart to deploy: `sudo systemctl restart ohm-rfc-app` > - Migrations run automatically on startup > > **Source is at `~/git/rfc-app/`.** diff --git a/deploy/RUNBOOK.md b/deploy/RUNBOOK.md index 830939f..211a6f5 100644 --- a/deploy/RUNBOOK.md +++ b/deploy/RUNBOOK.md @@ -1,9 +1,26 @@ # Runbook -Single-host deployment of the RFC app at `ohm.wiggleverse.org`, sharing -infrastructure with `git.wiggleverse.org` (same Gitea instance, same nginx, -same Let's Encrypt). The shape matches §4.2: one process, one SQLite file, -no separate worker. +Single-host deployment of the RFC app at `ohm.wiggleverse.org`. The shape +matches §4.2: one process, one SQLite file, no separate worker. + +> **⚠️ Current deployment names (post GCP name-alignment, ~2026-05).** +> The command blocks below were written with the original names and use +> `/opt/rfc-app`, system user `rfc-app`, and service `rfc-app`. The live +> OHM box uses the realigned names — substitute throughout: +> +> | Was | Now | +> | --- | --- | +> | GCP project `wiggleverse-rfc` | `wiggleverse-ohm` | +> | VM `rfc-app` | `ohm-rfc-app` | +> | install path `/opt/rfc-app` | `/opt/ohm-rfc-app` | +> | system user `rfc-app` | `ohm-rfc-app` | +> | service `rfc-app.service` | `ohm-rfc-app.service` | +> | SSH | IAP-only: `gcloud compute ssh ohm-rfc-app --zone=us-central1-a --project=wiggleverse-ohm --tunnel-through-iap` | +> | meta repo `wiggleverse/meta` | `wiggleverse/ohm-content` | +> +> The VM's git origin is **`git.benstull.org/benstull/rfc-app`** — a +> *different* Gitea from the release one (`git.wiggleverse.org`). See §2.5. +> The full current infra table lives in `DEPLOY-NEW-SESSION-PROMPT.md`. Bring-up order: host prep → Gitea side (bot, OAuth, meta repo) → app side (code, venv, build, .env) → web server side (nginx, certbot) → systemd → @@ -345,16 +362,34 @@ pinned = 1 WHERE rfc_slug = ? AND branch_name = ?`). ### 2.5 Updating after a push +The live OHM box uses the realigned names (see the callout at the top) +and deploys by checking out a **release tag** (detached HEAD), not by +pulling a branch. Its git origin is `git.benstull.org/benstull/rfc-app`, +which does **not** auto-mirror from the release Gitea +(`git.wiggleverse.org`) — so first push `main` + the new tag there: + ```sh -sudo -u rfc-app git -C /opt/rfc-app pull -sudo -u rfc-app /opt/rfc-app/backend/.venv/bin/pip install \ - -r /opt/rfc-app/backend/requirements.txt -# Rebuild the frontend locally and rsync dist/ as in 1.3.2. -sudo systemctl restart rfc-app +# from your local rfc-app clone, on main at the merged release tip: +git push benstull main vX.Y.Z # benstull = git@git.benstull.org:benstull/rfc-app.git +``` + +Then on the VM (SSH is IAP-only): + +```sh +gcloud compute ssh ohm-rfc-app --zone=us-central1-a --project=wiggleverse-ohm --tunnel-through-iap +sudo -u ohm-rfc-app git -C /opt/ohm-rfc-app fetch origin --tags +sudo -u ohm-rfc-app git -C /opt/ohm-rfc-app checkout vX.Y.Z +# only when backend deps changed: +sudo -u ohm-rfc-app /opt/ohm-rfc-app/backend/.venv/bin/pip install \ + -r /opt/ohm-rfc-app/backend/requirements.txt +# frontend changes: build on the VM (Node 20+ is there) — output is served directly by nginx: +cd /opt/ohm-rfc-app/frontend && sudo -u ohm-rfc-app npm ci && sudo -u ohm-rfc-app npm run build +sudo systemctl restart ohm-rfc-app ``` The §5 schema migrations run on startup and are append-only. A restart -is the entire deploy. +is the entire backend deploy; a frontend-only change is live as soon as +the new `dist/` is built (nginx serves it directly). ---