Some checks failed
Sync infra to hosts / sync-beta (push) Has been skipped
Sync infra to hosts / sync-prod (push) Has been skipped
Sync infra to hosts / sync-dev (push) Failing after 6s
secrets-guard / encrypted (push) Successful in 6s
shell-lint / shellcheck (push) Successful in 13s
Validate observability stack / validate (push) Successful in 17s
PR build (required check) / changes (pull_request) Successful in 6s
secrets-guard / encrypted (pull_request) Successful in 5s
PR build (required check) / build-backend (pull_request) Has been skipped
shell-lint / shellcheck (pull_request) Successful in 6s
PR build (required check) / build-frontend (pull_request) Has been skipped
PR build (required check) / validate-observability (pull_request) Successful in 18s
PR build (required check) / gate (pull_request) Successful in 2s
177 lines
8.4 KiB
Text
177 lines
8.4 KiB
Text
# 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/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 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
|