2026-07-25 07:08:54 +00:00
|
|
|
# infra/
|
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
|
|
|
|
|
|
|
|
Infrastructure for [Thermograph](https://thermograph.org): Terraform host
|
2026-07-25 07:08:54 +00:00
|
|
|
provisioning, the SOPS+age secrets vault, WireGuard/Swarm networking, Forgejo,
|
|
|
|
|
Caddy, mail, and the deploy scripts that run the already-built app images on each
|
|
|
|
|
host. This is a domain of the `emi/thermograph` monorepo — hosts' `/opt/thermograph`
|
|
|
|
|
is a checkout of the whole monorepo, and `infra/` never builds app source; it only
|
|
|
|
|
runs published images.
|
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
|
|
|
|
|
|
|
|
- **`terraform/`** — provisions/configures hosts (SSH-driven by default; an
|
2026-07-25 07:08:54 +00:00
|
|
|
optional GCP-creating module is scaffolded, no live resources yet). See
|
|
|
|
|
`terraform/README.md`. No tfstate is persisted anywhere — treat `apply` as
|
|
|
|
|
executable documentation, not a routine operation.
|
|
|
|
|
- **`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 WireGuard/Swarm cluster hosting
|
|
|
|
|
Forgejo (git + CI + registry). See `ACCESS.md`.
|
|
|
|
|
- **`deploy/deploy.sh`** — the single deploy entry point for beta and prod.
|
|
|
|
|
Takes `SERVICE=backend|frontend|all` plus `BACKEND_IMAGE_TAG`/`FRONTEND_IMAGE_TAG`,
|
|
|
|
|
resets the host checkout, renders secrets, and routes to the right orchestrator.
|
|
|
|
|
- **`deploy/stack/`** — the **Swarm** path, live on **prod**:
|
|
|
|
|
`thermograph-stack.yml` (db, web, worker, lake, daemon, frontend, autoscaler,
|
|
|
|
|
autoscaler-lake), `deploy-stack.sh`, `autoscale.sh`, and the LB. Rolling updates
|
|
|
|
|
are start-first, health-gated, with auto-rollback. `STACK_TEST=1` rehearses the
|
|
|
|
|
whole stack on throwaway volumes and ports.
|
|
|
|
|
- **`docker-compose*.yml`** — the **compose** path, live on **beta** and LAN dev
|
|
|
|
|
(db, backend, lake, daemon, frontend). `docker-compose.dev.yml` is the LAN
|
|
|
|
|
overlay; `docker-compose.openmeteo.yml` is the self-hosted Open-Meteo overlay.
|
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
|
|
|
|
2026-07-25 07:08:54 +00:00
|
|
|
Which path a host takes is decided by `/etc/thermograph/deploy-mode`: the string
|
|
|
|
|
`stack` makes `deploy.sh` exec `deploy/stack/deploy-stack.sh`; anything else is
|
|
|
|
|
compose. The workflows never need to know which mode a host runs.
|
2026-07-22 23:36:21 +00:00
|
|
|
|
|
|
|
|
## Branches & how changes reach each environment
|
|
|
|
|
|
2026-07-25 07:08:54 +00:00
|
|
|
- **`main`** — what **prod and beta** run. `infra-sync.yml` fires on a push to
|
|
|
|
|
`main` touching `infra/**`, fast-forwards each host's `/opt/thermograph` checkout
|
|
|
|
|
and re-renders `/etc/thermograph.env` from the vault. It deliberately does
|
|
|
|
|
**not** roll any service: image tags are the app domains' axis, not infra's. A
|
|
|
|
|
compose or stack change that must recreate containers takes effect on the next
|
|
|
|
|
app deploy, or a by-hand `SERVICE=all … deploy/deploy.sh`.
|
|
|
|
|
- **`dev`** — what LAN dev would run via `deploy/deploy-dev.sh`. The CI trigger
|
|
|
|
|
for this is currently inert (the LAN box still holds a split-era checkout); use
|
|
|
|
|
`make dev-up` locally.
|
|
|
|
|
- **`release`** — consumed by app deploys only. Both hosts track infra via `main`;
|
|
|
|
|
prod's *app images* are staged by `release`, but its checkout follows `main`.
|
2026-07-22 23:36:21 +00:00
|
|
|
|
2026-07-25 07:08:54 +00:00
|
|
|
Note the asymmetry with the app domains: app code IS environment-staged
|
|
|
|
|
(`dev`→`main`→`release` maps to LAN→beta→prod via image tags); infra is not.
|