# infra/ — agent instructions

How and where the already-built app images run: Terraform, the SOPS+age secrets
vault, compose and Swarm stack files, deploy scripts, host provisioning, mail,
and the ops cron (DB backup + IndexNow). Read the root `CLAUDE.md` first — it
owns the branch model, the deploy contract, and which environment runs which
orchestrator.

## The machines

Per `ACCESS.md`: **dev** (operator's box — LAN dev server and CI runner),
**prod** (`169.58.46.181`, thermograph.org, `agent` user, passwordless sudo),
**beta** (`75.119.132.91`, beta.thermograph.org + Forgejo + Grafana/Loki,
`agent` user). All four are on a WireGuard mesh. Hosts' `/opt/thermograph` is a
checkout of **this monorepo**, not an infra-only repo.

## Deploy paths

- **`deploy/deploy.sh`** — the single entry point for beta and prod. Resets the
  host checkout to `BRANCH` (default `main`), renders secrets, then routes:
  if `/etc/thermograph/deploy-mode` says `stack` it execs
  `deploy/stack/deploy-stack.sh`, otherwise it rolls compose services. Per-service
  tags persist in untracked `deploy/.image-tags.env` (compose) and
  `deploy/.stack-image-tags.env` (stack) so the two never mix.
- **`deploy/stack/deploy-stack.sh`** — the Swarm path, live on prod. `backend`
  rolls web **and** worker; `frontend` rolls frontend; `all` runs a full
  `docker stack deploy`. Start-first, health-gated, auto-rollback.
  `STACK_TEST=1` rehearses the whole thing under stack name `thermograph-test`
  on throwaway volumes and ports `18137`/`18080`.
- **`deploy/deploy-dev.sh`** — thin LAN-dev wrapper (dev compose overlay,
  `~/thermograph-dev`). Its CI trigger is inert; see root `CLAUDE.md`.
- **`Makefile`** — compose orchestration only: `up`, `down`, `db-up`, `dev-up`,
  `om-up`, `om-backfill`.

Volumes are the reason the compose project name is pinned: compose creates
`thermograph_pgdata`/`_appdata`/`_applogs`, and the Swarm stack declares those
same names as `external: true` at the same mount paths.

## Rules

- **Never run `terraform apply` casually.** No tfstate is persisted anywhere, so
  an apply would attempt full re-provisioning of live hosts. Terraform here is
  executable documentation until state is bootstrapped.
- **Secrets: SOPS vault only** (`deploy/secrets/*.yaml`, `sops edit` → commit →
  deploy). Never hand-edit `/etc/thermograph.env` — it is a rendered artifact.
  `secrets-guard` CI rejects plaintext. `deploy/secrets/seed-from-live.sh` reads
  production secrets and is **not** for an agent to run.
- **The ops cron (`.forgejo/workflows/ops-cron.yml`, at the repo root) is THE
  prod backup.** It uses `PROD_SSH_*`, not `SSH_*` — an earlier revision reused
  `SSH_*` and silently dumped beta while prod had no backup at all. If you touch
  it, verify a dump actually lands in `agent@prod:~/thermograph-backups/`.
- Shell here runs as root over SSH against live hosts with no test suite in
  front of it. `shell-lint` CI (pinned shellcheck) is the only guard — keep the
  tree at zero findings.

## Commits & PRs

Concise and technical. Never mention AI, assistants or automated authorship.
