# Copy to terraform.tfvars and fill in real IPs + secrets. # cp terraform.tfvars.example terraform.tfvars # terraform.tfvars is gitignored (it holds secrets, and those land in local state). # NEVER commit real values. # --------------------------------------------------------------------------------- # Hosts # --------------------------------------------------------------------------------- # Two VPS hosts. (The `dev` branch deploys to the LAN dev server via # deploy/deploy-dev.sh — that box is NOT managed by Terraform.) hosts = { # Production: the NEW 48 GB / 12-core VPS serving thermograph.org (branch `release`). prod = { host = "REPLACE_WITH_NEW_VPS_IP" # <-- the new prod VPS IP/hostname ssh_user = "deploy" ssh_private_key_path = "~/.ssh/id_ed25519" # key that can log in as deploy@ and sudo role = "prod" git_branch = "release" 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 (branch `main`). beta = { host = "75.119.132.91" ssh_user = "deploy" ssh_private_key_path = "~/.ssh/id_ed25519" role = "beta" git_branch = "main" # 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 = "deploy" # ssh_private_key_path = "~/.ssh/id_ed25519" # role = "uat" # git_branch = "release" # domain = "" # compose_files = ["docker-compose.yml"] # app_dir = "/opt/thermograph" # size = "nano" # } } # Optional overrides (shown with their defaults): # repo_url = "https://github.com/griffemi/thermograph.git" # app_port = 8137 # frontend_port = 8080 # --------------------------------------------------------------------------------- # 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 # --------------------------------------------------------------------------------- # Shared secrets (keep this file out of git) # --------------------------------------------------------------------------------- # postgres_password / auth_secret / metrics_token / indexnow_key are OPTIONAL — # left unset (or ""), Terraform generates and owns each one (see secrets.tf), # pinned so `apply` never regenerates a value already in use. Uncomment and set # one only to seed an EXISTING live secret when migrating onto Terraform (hop-1 # cutover runbook Stage 0) — `terraform plan` must then show no change to it. # postgres_password = "REPLACE_WITH_THE_EXISTING_LIVE_DB_PASSWORD" # auth_secret = "REPLACE_WITH_THE_EXISTING_LIVE_AUTH_SECRET" # metrics_token = "REPLACE_WITH_THE_EXISTING_LIVE_METRICS_TOKEN" # indexnow_key = "REPLACE_WITH_THE_EXISTING_LIVE_INDEXNOW_KEY" # VAPID is NOT Terraform-generated — an EC keypair, generate once out-of-band and # hold it as a stable input; regeneration breaks every existing push subscription. # cd backend && ../.venv/bin/python -c "import push,json;k=push._generate();print(k['private_key']);print(k['public_key'])" vapid_private_key = "REPLACE_WITH_VAPID_PRIVATE_KEY" vapid_public_key = "REPLACE_WITH_VAPID_PUBLIC_KEY" vapid_contact = "mailto:you@example.com" # ---- Optional: search-engine verification (leave "" to omit) -------------------- # google_verify = "" # bing_verify = "" # ---- Optional: outbound email (leave "" to omit) -------------------------------- # mail_backend = "smtp" # smtp_host = "127.0.0.1" # smtp_port = "25" # smtp_user = "" # smtp_password = "" # smtp_starttls = "" # mail_from = "Thermograph " # mail_reply_to = "" # ---- Optional: Discord (leave "" to omit) --------------------------------------- # discord_webhook = "" # discord_public_key = "" # discord_app_id = "" # discord_bot_token = "" # discord_client_secret = "" # ---- Required: registry pull credential (repo-split Stage 6) -------------------- # A Forgejo personal access token, read:package scope, for deploy.sh's # `docker login` + `docker compose pull` on every apply. No default -- apply # fails loud without it rather than deploying a host that can't redeploy itself. # registry_token = ""