# Copy to terraform.tfvars and fill in real 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 boxes, keyed by HOST (vps1, vps2) — NOT by environment, because vps2 # alone carries two (prod AND beta). Each host lists its SSH identity once, then # an `environments` map for whatever runs on it. (The `dev` branch deploys to # vps1 the same way in reality, but the LAN-laptop `make dev-up` rehearsal is NOT # managed by Terraform at all.) hosts = { # vps1: Forgejo (git+CI+registry), Grafana/Loki/Alloy, emigriffith.dev, and the # `dev` environment. One environment today; the shape still nests it so a # second one (there is none planned) would never have to fight this host's # SSH identity. vps1 = { host = "75.119.132.91" ssh_user = "agent" ssh_private_key_path = "~/.ssh/thermograph_agent_ed25519" environments = { dev = { role = "dev" git_branch = "dev" # the only environment that tracks `dev`; beta/prod track `main` backend_image_tag = "REPLACE_WITH_BACKEND_IMAGE_TAG" frontend_image_tag = "REPLACE_WITH_FRONTEND_IMAGE_TAG" # Mesh-only: no public domain, no Caddy/TLS. Reachable at # http://10.10.0.2:8137 from the WireGuard mesh only. domain = "" compose_files = ["docker-compose.yml"] app_dir = "/opt/thermograph-dev" workers = 4 app_cpus = 4 db_cpus = 2 db_memory = "8g" } } } # vps2: prod (thermograph.org) AND beta (beta.thermograph.org) as two separate # Swarm stacks on the SAME 48 GB / 12-core box — plus Centralis, Postfix and # the backups (not Terraform-managed). ONE shared TimescaleDB instance serves # both app_dirs below, on separate databases/roles (deploy/db/provision-env-db.sh); # each environment's db_cpus/db_memory here sizes only that environment's own # app-container caps, not a second database. vps2 = { host = "169.58.46.181" ssh_user = "agent" ssh_private_key_path = "~/.ssh/thermograph_agent_ed25519" environments = { prod = { role = "prod" git_branch = "main" # this INFRA repo's branch -- see *_image_tag for the app versions backend_image_tag = "REPLACE_WITH_BACKEND_IMAGE_TAG" # e.g. "sha-abcdef012345" -- from thermograph-backend build-push.yml frontend_image_tag = "REPLACE_WITH_FRONTEND_IMAGE_TAG" # e.g. "sha-012345abcdef" -- from thermograph-frontend build-push.yml domain = "thermograph.org" # Caddy TLS in front, app on loopback compose_files = ["docker-compose.yml"] # MUST differ from beta's app_dir below -- see variables.tf's file header. 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 = { role = "beta" git_branch = "main" backend_image_tag = "REPLACE_WITH_BACKEND_IMAGE_TAG" frontend_image_tag = "REPLACE_WITH_FRONTEND_IMAGE_TAG" # "" ON PURPOSE, unlike prod above: modules/thermograph-host installs a # FULL /etc/caddy/Caddyfile per environment with a domain set, and only # ONE environment on a box can own that file. Prod already claims it on # this host, so beta.thermograph.org's public reverse-proxy has to be a # site block in vps2's shared, hand-maintained Caddy instead (the same # pattern deploy/forgejo/docker-stack.yml uses for git.thermograph.org # on vps1) -- not something this module can render for a second # environment on the same host. domain = "" compose_files = ["docker-compose.yml"] # MUST differ from prod's app_dir above -- a SECOND checkout on the same # box, so a `git reset --hard` in one deploy can never yank the tree out # from under the other's running deploy. app_dir = "/opt/thermograph-beta" # Explicit numbers, not a size tier — both styles work on any environment; 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" # environments = { # uat = { # role = "uat" # git_branch = "main" # backend_image_tag = "REPLACE_WITH_BACKEND_IMAGE_TAG" # frontend_image_tag = "REPLACE_WITH_FRONTEND_IMAGE_TAG" # domain = "" # compose_files = ["docker-compose.yml"] # app_dir = "/opt/thermograph" # size = "nano" # } # } # } } # GCP-created hosts — SCAFFOLD ONLY, empty by default, and still keyed one entry # per ENVIRONMENT (see variables.tf's TODO(cutover) note on gcp_hosts — these are # hypothetical single-purpose boxes, not vps1/vps2, so they don't need the # host/environment nesting above). 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" # backend_image_tag = "REPLACE_WITH_BACKEND_IMAGE_TAG" # frontend_image_tag = "REPLACE_WITH_FRONTEND_IMAGE_TAG" # size = "nano" # } # } # Optional overrides (shown with their defaults): # repo_url = "https://git.thermograph.org/admin_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/admin_emi/thermograph-infra.git" # --------------------------------------------------------------------------------- # Self-hosted Open-Meteo archive (only used by environments 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