Decouple Terraform from the app repo; add a GCP host scaffold
Content-change pass following the extraction from the app monorepo (this repo
now stands alone, sourced via git filter-repo to preserve history):
- terraform/variables.tf, secrets.tf, modules/thermograph-host: remove every
app-secret Terraform variable (postgres_password, auth_secret, VAPID keys,
registry_token, Discord/SMTP creds, ...) and the random_password/random_id
generators. The SOPS+age vault (deploy/secrets/*.yaml) is now the sole
source of app secrets, rendered at deploy time by deploy/render-secrets.sh;
Terraform renders only a non-secret /etc/thermograph-topology.env (sizing,
routing) via the renamed thermograph-topology.env.tftpl template.
- hosts gains a required app_image_tag field: the host's own checkout is now
this infra repo, not the app repo, so there is no "current commit" to
derive an image tag from — every host pins one explicitly. repo_url now
points at this repo (private; typically needs an embedded read token).
- deploy.sh: IMAGE_TAG is now required from the environment instead of
derived via `git rev-parse HEAD` of the (now infra-repo) checkout, which
would have silently resolved to the wrong or a nonexistent tag.
- New terraform/modules/gcp-host: creates a GCE VM + minimal VPC/firewall
only, then feeds its IP into the same thermograph-host module every
SSH-managed host already uses — one provisioning path regardless of how a
host came to exist. var.gcp_hosts defaults to {}, so no google_* resource
is planned and the provider is never invoked without it (verified: plan
and validate succeed with no GCP credentials configured).
- terraform/README.md, ACCESS.md (renamed from INFRA.md), README.md: updated
for the new secrets model, the GCP scaffold, and this repo's own identity.
Verified: terraform fmt/validate/init clean; plan succeeds against realistic
dummy hosts (prod+beta shape) and against a populated gcp_hosts entry (plans
6 resources with no live credentials, confirming the composition wires
correctly end to end).
2026-07-22 04:46:05 +00:00
|
|
|
# thermograph-infra
|
|
|
|
|
|
|
|
|
|
Infrastructure for [Thermograph](https://thermograph.org): Terraform host
|
|
|
|
|
provisioning, the SOPS+age secrets vault, Docker Swarm/WireGuard networking,
|
|
|
|
|
Forgejo, Caddy, and the deploy scripts that run the already-built app image on
|
|
|
|
|
each host. Extracted from the app monorepo (`emi/thermograph`) — the app repo
|
|
|
|
|
owns building and testing the app; this repo owns running it.
|
|
|
|
|
|
|
|
|
|
- **`terraform/`** — provisions/configures hosts (SSH-driven by default; an
|
|
|
|
|
optional GCP-creating module is scaffolded, no live resources yet) and
|
|
|
|
|
triggers each deploy. See `terraform/README.md`.
|
|
|
|
|
- **`deploy/secrets/`** — the git-native SOPS+age secrets vault (every app
|
|
|
|
|
secret, encrypted at rest, rendered at deploy time). See
|
|
|
|
|
`deploy/secrets/README.md`.
|
|
|
|
|
- **`deploy/swarm/`, `deploy/forgejo/`** — the 3-node WireGuard/Swarm cluster
|
|
|
|
|
that hosts Forgejo (git + CI + registry); does not run the app itself. See
|
|
|
|
|
`ACCESS.md` and the READMEs under each directory.
|
|
|
|
|
- **`deploy/deploy.sh`** — pulls the pinned app image (`IMAGE_TAG`) and rolls
|
|
|
|
|
the compose stack; invoked by Terraform and by the app repo's
|
|
|
|
|
`.forgejo/workflows/deploy.yml` over SSH.
|
|
|
|
|
- **`docker-compose*.yml`, `docker-stack.yml`** — how the app image runs
|
|
|
|
|
(compose in production today; `docker-stack.yml` is a design record for a
|
|
|
|
|
possible future Swarm-based app deploy, not currently live).
|
|
|
|
|
|
|
|
|
|
The app's own source, `Dockerfile`, and build/test CI stay in the app repo —
|
|
|
|
|
this repo never checks out app source; hosts only pull tagged images from the
|
|
|
|
|
registry. See `ACCESS.md` for host access and the Swarm/Forgejo topology, and
|
|
|
|
|
`terraform/README.md` for the day-to-day `plan`/`apply` workflow.
|
2026-07-22 23:36:21 +00:00
|
|
|
|
|
|
|
|
## Branches & how changes reach each environment
|
|
|
|
|
|
|
|
|
|
- **`main`** — what **prod and beta** run: their `/opt/thermograph` checkouts
|
|
|
|
|
`git reset --hard origin/main` at the start of every deploy (`deploy/deploy.sh`).
|
|
|
|
|
A merge to `main` reaches those hosts on the next app deploy (or a by-hand
|
|
|
|
|
`deploy.sh` run); there is no separate infra deploy trigger.
|
|
|
|
|
- **`dev`** — what **LAN dev** runs: `~/thermograph-dev` resets to it via
|
|
|
|
|
`deploy/deploy-dev.sh`. Keep it fast-forwarded to `main` (infra changes are not
|
|
|
|
|
environment-staged today; the branches exist so LAN dev *can* trail or lead
|
|
|
|
|
when needed).
|
|
|
|
|
- **`release`** — currently consumed by nothing (prod tracks `main`, not
|
|
|
|
|
`release`). It exists to mirror the app repos' dev→main→release promotion
|
|
|
|
|
shape if per-environment infra staging is ever wanted; until then, treat
|
|
|
|
|
`main` as live-everywhere.
|
|
|
|
|
|
|
|
|
|
Note the asymmetry with the app repos: app code IS environment-staged
|
|
|
|
|
(dev→main→release maps to LAN→beta→prod via image tags), infra is not.
|