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).
121 lines
5.7 KiB
Text
121 lines
5.7 KiB
Text
# Copy to terraform.tfvars and fill in real IPs + credentials.
|
|
# cp terraform.tfvars.example terraform.tfvars
|
|
# terraform.tfvars is gitignored (repo_url may carry a credential, and om_rclone_conf
|
|
# always does — both land in local state). NEVER commit real values.
|
|
#
|
|
# App secrets (POSTGRES_PASSWORD, THERMOGRAPH_AUTH_SECRET, VAPID keys,
|
|
# REGISTRY_TOKEN, Discord/SMTP creds, ...) are NOT here anymore -- they live in
|
|
# the SOPS+age vault (../deploy/secrets/*.yaml), rendered at deploy time. See
|
|
# deploy/secrets/README.md to rotate or add one.
|
|
|
|
# ---------------------------------------------------------------------------------
|
|
# Hosts
|
|
# ---------------------------------------------------------------------------------
|
|
# Two VPS hosts. (The `dev` branch deploys to the LAN dev server via the app repo's
|
|
# deploy/deploy-dev.sh — that box is NOT managed by Terraform.)
|
|
hosts = {
|
|
# Production: the NEW 48 GB / 12-core VPS serving thermograph.org.
|
|
prod = {
|
|
host = "REPLACE_WITH_NEW_VPS_IP" # <-- the new prod VPS IP/hostname
|
|
ssh_user = "agent"
|
|
ssh_private_key_path = "~/.ssh/thermograph_agent_ed25519"
|
|
role = "prod"
|
|
git_branch = "main" # this INFRA repo's branch -- see app_image_tag for the app version
|
|
app_image_tag = "REPLACE_WITH_APP_IMAGE_TAG" # e.g. "sha-abcdef012345" -- from build-push.yml on the app repo
|
|
domain = "thermograph.org" # Caddy TLS in front, app on loopback
|
|
compose_files = ["docker-compose.yml"]
|
|
app_dir = "/opt/thermograph"
|
|
# "large" is the named size tier for this box (locals.sizes in main.tf) — same
|
|
# numbers as hand-picking workers=8/app_cpus=8/db_cpus=4/db_memory="16g" below,
|
|
# via the shortcut. The Postgres internal budget scales from db_memory
|
|
# automatically (deploy/db/init/20-tuning.sh); no separate tuning edit.
|
|
size = "large"
|
|
# Self-host the ERA5 archive here: layers docker-compose.openmeteo.yml and
|
|
# provisions the rclone mount of the object-storage bucket (om_* vars below).
|
|
openmeteo = true
|
|
om_data_dir = "/mnt/om-archive"
|
|
}
|
|
|
|
# Beta / testing: the OLD VPS, repurposed.
|
|
beta = {
|
|
host = "75.119.132.91"
|
|
ssh_user = "agent"
|
|
ssh_private_key_path = "~/.ssh/thermograph_agent_ed25519"
|
|
role = "beta"
|
|
git_branch = "main"
|
|
app_image_tag = "REPLACE_WITH_APP_IMAGE_TAG"
|
|
# No public domain by default: no Caddy/TLS, firewall opens the app port. NOTE:
|
|
# with compose_files = ["docker-compose.yml"] the app binds 127.0.0.1 only, so
|
|
# until you either set a domain (e.g. "beta.thermograph.org", which fronts it with
|
|
# Caddy) or add the 0.0.0.0-publishing dev overlay, reach it via an SSH tunnel.
|
|
domain = ""
|
|
compose_files = ["docker-compose.yml"]
|
|
app_dir = "/opt/thermograph"
|
|
# Explicit numbers, not a size tier — both styles work on any host; a tier is
|
|
# purely an opt-in shortcut (see prod's `size = "large"` above).
|
|
workers = 4
|
|
app_cpus = 4
|
|
db_cpus = 2
|
|
db_memory = "8g"
|
|
}
|
|
|
|
# UAT: an ephemeral, single-node environment — same images/topology shape as
|
|
# prod, not prod's scale (design doc §6/§9). Uncomment once a UAT box exists;
|
|
# not managed until then. "nano" keeps it cheap since it's destroyed when idle.
|
|
# uat = {
|
|
# host = "REPLACE_WITH_UAT_VM_IP"
|
|
# ssh_user = "agent"
|
|
# ssh_private_key_path = "~/.ssh/thermograph_agent_ed25519"
|
|
# role = "uat"
|
|
# git_branch = "main"
|
|
# app_image_tag = "REPLACE_WITH_APP_IMAGE_TAG"
|
|
# domain = ""
|
|
# compose_files = ["docker-compose.yml"]
|
|
# app_dir = "/opt/thermograph"
|
|
# size = "nano"
|
|
# }
|
|
}
|
|
|
|
# GCP-created hosts — SCAFFOLD ONLY, empty by default. Populate an entry to have
|
|
# Terraform actually create a GCP Compute Engine VM (see modules/gcp-host); until
|
|
# then no google_* resource is planned and no GCP credentials are needed. Example:
|
|
# gcp_hosts = {
|
|
# gcp-uat = {
|
|
# project = "REPLACE_WITH_GCP_PROJECT_ID"
|
|
# zone = "us-west1-a"
|
|
# machine_type = "e2-medium"
|
|
# ssh_user = "agent"
|
|
# ssh_public_key_path = "~/.ssh/thermograph_agent_ed25519.pub"
|
|
# ssh_private_key_path = "~/.ssh/thermograph_agent_ed25519"
|
|
# role = "uat"
|
|
# git_branch = "main"
|
|
# app_image_tag = "REPLACE_WITH_APP_IMAGE_TAG"
|
|
# size = "nano"
|
|
# }
|
|
# }
|
|
|
|
# Optional overrides (shown with their defaults):
|
|
# repo_url = "https://git.thermograph.org/emi/thermograph-infra.git"
|
|
# app_port = 8137
|
|
# frontend_port = 8080
|
|
#
|
|
# thermograph-infra is a PRIVATE repo, so a host cloning it for the first time
|
|
# needs read credentials embedded in repo_url, e.g. a Forgejo deploy token:
|
|
# repo_url = "https://deploy:REPLACE_WITH_TOKEN@git.thermograph.org/emi/thermograph-infra.git"
|
|
|
|
# ---------------------------------------------------------------------------------
|
|
# Self-hosted Open-Meteo archive (only used by hosts with openmeteo = true) --------
|
|
# ---------------------------------------------------------------------------------
|
|
# The ERA5 .om archive lives in an object-storage bucket, rclone-mounted on the host.
|
|
# om_rclone_conf holds bucket credentials (sensitive; lands in state — keep out of git).
|
|
# See deploy/openmeteo/README.md for the bucket + mount setup.
|
|
om_bucket_remote = "om-archive:REPLACE_WITH_BUCKET_NAME"
|
|
om_vfs_cache_max = "80G"
|
|
om_rclone_conf = <<-RCLONE
|
|
[om-archive]
|
|
type = s3
|
|
provider = Cloudflare
|
|
endpoint = https://REPLACE.r2.cloudflarestorage.com
|
|
access_key_id = REPLACE_WITH_ACCESS_KEY
|
|
secret_access_key = REPLACE_WITH_SECRET_KEY
|
|
RCLONE
|