Add Terraform to provision the VPS hosts (compose keeps running the app) (#223)
Terraform config under terraform/ manages the two existing VPS hosts and hands the
app to docker-compose, with local state:
- prod: the new 48GB/12-core VPS (release branch, thermograph.org), sized larger.
- beta: the old VPS 75.119.132.91 (main branch, testing tier), no public domain.
- The LAN dev box stays on deploy/deploy-dev.sh (dev branch) — out of Terraform.
A reusable module (modules/thermograph-host) SSHes each host to install docker/
compose/ufw (+ Caddy when a domain is set), sync the checkout to the host's branch,
render /etc/thermograph.env from Terraform variables (secrets pushed via provisioner
content, never on local disk), `docker compose up -d`, and health-check. Named
volumes are preserved on re-apply, so the Postgres data is never recreated.
Container resources are now env-driven in docker-compose.yml (APP_CPUS/DB_CPUS/
DB_MEMORY/WORKERS) with unchanged defaults, so Terraform can size each host.
2026-07-20 07:42:15 +00:00
|
|
|
# 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"
|
Have Terraform generate its own internal secrets, with sizing tiers (#239)
Terraform generates the secrets that have no external meaning
(POSTGRES_PASSWORD, AUTH_SECRET, METRICS_TOKEN, INDEXNOW_KEY) via the random
provider instead of requiring the operator to hand-generate and paste each
into terraform.tfvars. Each is pinned with a static keepers value (secrets.tf)
so apply never regenerates a value already in use - the exact incident class
this guards against: every session invalidated, the app<->DB password
mismatched. Rotation is now a deliberate keepers edit, never a side effect.
postgres_password/auth_secret move from required inputs to optional (default
"") - explicit var wins when supplied (seeding an EXISTING live secret during
a migration onto Terraform, hop-1 cutover runbook Stage 0), else Terraform
generates and owns it. metrics_token/indexnow_key are new: neither existed in
Terraform before, both previously left for the app's own fallback generation.
VAPID deliberately stays a required, non-generated input - an EC keypair
where regeneration breaks every existing push subscription outright, unlike
an opaque token.
Sizing tiers: a locals.sizes t-shirt map (nano/small/medium/large ->
{workers, app_cpus, db_cpus, db_memory}), toward the target Proxmox
sizing-tier model (architecture doc SS6) ahead of actually provisioning VMs -
Proxmox itself stays deferred; today a tier just sizes container caps on the
existing SSH-managed hosts. A host can reference one by name (hosts.<name>.
size) or keep hand-picking the four fields, so existing tfvars are
unaffected; prod's example now uses size = "large" (identical numbers),
beta keeps explicit numbers, and a commented uat example demonstrates the
shortcut for a future ephemeral host.
Strengthened terraform/README.md's local-state caveat: more Terraform-
generated secrets landing in tfstate raises the stakes of the existing
never-commit-cleartext-state guidance, not just the sizing.
Verified: terraform validate + fmt clean. A real `terraform plan` against
fake hosts (prod/beta/uat, mixing size="large"/explicit-numbers/size="nano")
resolved every sizing correctly (prod 8/8/4/16g, beta 4/4/2/8g, uat
1/1/1/1g) and planned exactly one instance of each random_password/random_id
resource. Applied just those four resources (real generation, -target to
avoid touching the fake SSH-only host resources) and re-planned: "No
changes" - confirming the keepers pinning holds. Adding an explicit
postgres_password override afterward left the random_password resource
itself completely untouched (0 replace/destroy), confirming the override
path never disturbs the generated resource.
2026-07-21 01:08:57 +00:00
|
|
|
# "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"
|
2026-07-20 13:16:56 +00:00
|
|
|
# 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"
|
Add Terraform to provision the VPS hosts (compose keeps running the app) (#223)
Terraform config under terraform/ manages the two existing VPS hosts and hands the
app to docker-compose, with local state:
- prod: the new 48GB/12-core VPS (release branch, thermograph.org), sized larger.
- beta: the old VPS 75.119.132.91 (main branch, testing tier), no public domain.
- The LAN dev box stays on deploy/deploy-dev.sh (dev branch) — out of Terraform.
A reusable module (modules/thermograph-host) SSHes each host to install docker/
compose/ufw (+ Caddy when a domain is set), sync the checkout to the host's branch,
render /etc/thermograph.env from Terraform variables (secrets pushed via provisioner
content, never on local disk), `docker compose up -d`, and health-check. Named
volumes are preserved on re-apply, so the Postgres data is never recreated.
Container resources are now env-driven in docker-compose.yml (APP_CPUS/DB_CPUS/
DB_MEMORY/WORKERS) with unchanged defaults, so Terraform can size each host.
2026-07-20 07:42:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# 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"
|
Have Terraform generate its own internal secrets, with sizing tiers (#239)
Terraform generates the secrets that have no external meaning
(POSTGRES_PASSWORD, AUTH_SECRET, METRICS_TOKEN, INDEXNOW_KEY) via the random
provider instead of requiring the operator to hand-generate and paste each
into terraform.tfvars. Each is pinned with a static keepers value (secrets.tf)
so apply never regenerates a value already in use - the exact incident class
this guards against: every session invalidated, the app<->DB password
mismatched. Rotation is now a deliberate keepers edit, never a side effect.
postgres_password/auth_secret move from required inputs to optional (default
"") - explicit var wins when supplied (seeding an EXISTING live secret during
a migration onto Terraform, hop-1 cutover runbook Stage 0), else Terraform
generates and owns it. metrics_token/indexnow_key are new: neither existed in
Terraform before, both previously left for the app's own fallback generation.
VAPID deliberately stays a required, non-generated input - an EC keypair
where regeneration breaks every existing push subscription outright, unlike
an opaque token.
Sizing tiers: a locals.sizes t-shirt map (nano/small/medium/large ->
{workers, app_cpus, db_cpus, db_memory}), toward the target Proxmox
sizing-tier model (architecture doc SS6) ahead of actually provisioning VMs -
Proxmox itself stays deferred; today a tier just sizes container caps on the
existing SSH-managed hosts. A host can reference one by name (hosts.<name>.
size) or keep hand-picking the four fields, so existing tfvars are
unaffected; prod's example now uses size = "large" (identical numbers),
beta keeps explicit numbers, and a commented uat example demonstrates the
shortcut for a future ephemeral host.
Strengthened terraform/README.md's local-state caveat: more Terraform-
generated secrets landing in tfstate raises the stakes of the existing
never-commit-cleartext-state guidance, not just the sizing.
Verified: terraform validate + fmt clean. A real `terraform plan` against
fake hosts (prod/beta/uat, mixing size="large"/explicit-numbers/size="nano")
resolved every sizing correctly (prod 8/8/4/16g, beta 4/4/2/8g, uat
1/1/1/1g) and planned exactly one instance of each random_password/random_id
resource. Applied just those four resources (real generation, -target to
avoid touching the fake SSH-only host resources) and re-planned: "No
changes" - confirming the keepers pinning holds. Adding an explicit
postgres_password override afterward left the random_password resource
itself completely untouched (0 replace/destroy), confirming the override
path never disturbs the generated resource.
2026-07-21 01:08:57 +00:00
|
|
|
# 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).
|
Add Terraform to provision the VPS hosts (compose keeps running the app) (#223)
Terraform config under terraform/ manages the two existing VPS hosts and hands the
app to docker-compose, with local state:
- prod: the new 48GB/12-core VPS (release branch, thermograph.org), sized larger.
- beta: the old VPS 75.119.132.91 (main branch, testing tier), no public domain.
- The LAN dev box stays on deploy/deploy-dev.sh (dev branch) — out of Terraform.
A reusable module (modules/thermograph-host) SSHes each host to install docker/
compose/ufw (+ Caddy when a domain is set), sync the checkout to the host's branch,
render /etc/thermograph.env from Terraform variables (secrets pushed via provisioner
content, never on local disk), `docker compose up -d`, and health-check. Named
volumes are preserved on re-apply, so the Postgres data is never recreated.
Container resources are now env-driven in docker-compose.yml (APP_CPUS/DB_CPUS/
DB_MEMORY/WORKERS) with unchanged defaults, so Terraform can size each host.
2026-07-20 07:42:15 +00:00
|
|
|
workers = 4
|
|
|
|
|
app_cpus = 4
|
|
|
|
|
db_cpus = 2
|
|
|
|
|
db_memory = "8g"
|
|
|
|
|
}
|
Have Terraform generate its own internal secrets, with sizing tiers (#239)
Terraform generates the secrets that have no external meaning
(POSTGRES_PASSWORD, AUTH_SECRET, METRICS_TOKEN, INDEXNOW_KEY) via the random
provider instead of requiring the operator to hand-generate and paste each
into terraform.tfvars. Each is pinned with a static keepers value (secrets.tf)
so apply never regenerates a value already in use - the exact incident class
this guards against: every session invalidated, the app<->DB password
mismatched. Rotation is now a deliberate keepers edit, never a side effect.
postgres_password/auth_secret move from required inputs to optional (default
"") - explicit var wins when supplied (seeding an EXISTING live secret during
a migration onto Terraform, hop-1 cutover runbook Stage 0), else Terraform
generates and owns it. metrics_token/indexnow_key are new: neither existed in
Terraform before, both previously left for the app's own fallback generation.
VAPID deliberately stays a required, non-generated input - an EC keypair
where regeneration breaks every existing push subscription outright, unlike
an opaque token.
Sizing tiers: a locals.sizes t-shirt map (nano/small/medium/large ->
{workers, app_cpus, db_cpus, db_memory}), toward the target Proxmox
sizing-tier model (architecture doc SS6) ahead of actually provisioning VMs -
Proxmox itself stays deferred; today a tier just sizes container caps on the
existing SSH-managed hosts. A host can reference one by name (hosts.<name>.
size) or keep hand-picking the four fields, so existing tfvars are
unaffected; prod's example now uses size = "large" (identical numbers),
beta keeps explicit numbers, and a commented uat example demonstrates the
shortcut for a future ephemeral host.
Strengthened terraform/README.md's local-state caveat: more Terraform-
generated secrets landing in tfstate raises the stakes of the existing
never-commit-cleartext-state guidance, not just the sizing.
Verified: terraform validate + fmt clean. A real `terraform plan` against
fake hosts (prod/beta/uat, mixing size="large"/explicit-numbers/size="nano")
resolved every sizing correctly (prod 8/8/4/16g, beta 4/4/2/8g, uat
1/1/1/1g) and planned exactly one instance of each random_password/random_id
resource. Applied just those four resources (real generation, -target to
avoid touching the fake SSH-only host resources) and re-planned: "No
changes" - confirming the keepers pinning holds. Adding an explicit
postgres_password override afterward left the random_password resource
itself completely untouched (0 replace/destroy), confirming the override
path never disturbs the generated resource.
2026-07-21 01:08:57 +00:00
|
|
|
|
|
|
|
|
# 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"
|
|
|
|
|
# }
|
Add Terraform to provision the VPS hosts (compose keeps running the app) (#223)
Terraform config under terraform/ manages the two existing VPS hosts and hands the
app to docker-compose, with local state:
- prod: the new 48GB/12-core VPS (release branch, thermograph.org), sized larger.
- beta: the old VPS 75.119.132.91 (main branch, testing tier), no public domain.
- The LAN dev box stays on deploy/deploy-dev.sh (dev branch) — out of Terraform.
A reusable module (modules/thermograph-host) SSHes each host to install docker/
compose/ufw (+ Caddy when a domain is set), sync the checkout to the host's branch,
render /etc/thermograph.env from Terraform variables (secrets pushed via provisioner
content, never on local disk), `docker compose up -d`, and health-check. Named
volumes are preserved on re-apply, so the Postgres data is never recreated.
Container resources are now env-driven in docker-compose.yml (APP_CPUS/DB_CPUS/
DB_MEMORY/WORKERS) with unchanged defaults, so Terraform can size each host.
2026-07-20 07:42:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# Optional overrides (shown with their defaults):
|
|
|
|
|
# repo_url = "https://github.com/griffemi/thermograph.git"
|
|
|
|
|
# app_port = 8137
|
2026-07-21 20:01:30 +00:00
|
|
|
# frontend_port = 8080
|
Add Terraform to provision the VPS hosts (compose keeps running the app) (#223)
Terraform config under terraform/ manages the two existing VPS hosts and hands the
app to docker-compose, with local state:
- prod: the new 48GB/12-core VPS (release branch, thermograph.org), sized larger.
- beta: the old VPS 75.119.132.91 (main branch, testing tier), no public domain.
- The LAN dev box stays on deploy/deploy-dev.sh (dev branch) — out of Terraform.
A reusable module (modules/thermograph-host) SSHes each host to install docker/
compose/ufw (+ Caddy when a domain is set), sync the checkout to the host's branch,
render /etc/thermograph.env from Terraform variables (secrets pushed via provisioner
content, never on local disk), `docker compose up -d`, and health-check. Named
volumes are preserved on re-apply, so the Postgres data is never recreated.
Container resources are now env-driven in docker-compose.yml (APP_CPUS/DB_CPUS/
DB_MEMORY/WORKERS) with unchanged defaults, so Terraform can size each host.
2026-07-20 07:42:15 +00:00
|
|
|
|
2026-07-20 13:16:56 +00:00
|
|
|
# ---------------------------------------------------------------------------------
|
|
|
|
|
# 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
|
|
|
|
|
|
Add Terraform to provision the VPS hosts (compose keeps running the app) (#223)
Terraform config under terraform/ manages the two existing VPS hosts and hands the
app to docker-compose, with local state:
- prod: the new 48GB/12-core VPS (release branch, thermograph.org), sized larger.
- beta: the old VPS 75.119.132.91 (main branch, testing tier), no public domain.
- The LAN dev box stays on deploy/deploy-dev.sh (dev branch) — out of Terraform.
A reusable module (modules/thermograph-host) SSHes each host to install docker/
compose/ufw (+ Caddy when a domain is set), sync the checkout to the host's branch,
render /etc/thermograph.env from Terraform variables (secrets pushed via provisioner
content, never on local disk), `docker compose up -d`, and health-check. Named
volumes are preserved on re-apply, so the Postgres data is never recreated.
Container resources are now env-driven in docker-compose.yml (APP_CPUS/DB_CPUS/
DB_MEMORY/WORKERS) with unchanged defaults, so Terraform can size each host.
2026-07-20 07:42:15 +00:00
|
|
|
# ---------------------------------------------------------------------------------
|
Have Terraform generate its own internal secrets, with sizing tiers (#239)
Terraform generates the secrets that have no external meaning
(POSTGRES_PASSWORD, AUTH_SECRET, METRICS_TOKEN, INDEXNOW_KEY) via the random
provider instead of requiring the operator to hand-generate and paste each
into terraform.tfvars. Each is pinned with a static keepers value (secrets.tf)
so apply never regenerates a value already in use - the exact incident class
this guards against: every session invalidated, the app<->DB password
mismatched. Rotation is now a deliberate keepers edit, never a side effect.
postgres_password/auth_secret move from required inputs to optional (default
"") - explicit var wins when supplied (seeding an EXISTING live secret during
a migration onto Terraform, hop-1 cutover runbook Stage 0), else Terraform
generates and owns it. metrics_token/indexnow_key are new: neither existed in
Terraform before, both previously left for the app's own fallback generation.
VAPID deliberately stays a required, non-generated input - an EC keypair
where regeneration breaks every existing push subscription outright, unlike
an opaque token.
Sizing tiers: a locals.sizes t-shirt map (nano/small/medium/large ->
{workers, app_cpus, db_cpus, db_memory}), toward the target Proxmox
sizing-tier model (architecture doc SS6) ahead of actually provisioning VMs -
Proxmox itself stays deferred; today a tier just sizes container caps on the
existing SSH-managed hosts. A host can reference one by name (hosts.<name>.
size) or keep hand-picking the four fields, so existing tfvars are
unaffected; prod's example now uses size = "large" (identical numbers),
beta keeps explicit numbers, and a commented uat example demonstrates the
shortcut for a future ephemeral host.
Strengthened terraform/README.md's local-state caveat: more Terraform-
generated secrets landing in tfstate raises the stakes of the existing
never-commit-cleartext-state guidance, not just the sizing.
Verified: terraform validate + fmt clean. A real `terraform plan` against
fake hosts (prod/beta/uat, mixing size="large"/explicit-numbers/size="nano")
resolved every sizing correctly (prod 8/8/4/16g, beta 4/4/2/8g, uat
1/1/1/1g) and planned exactly one instance of each random_password/random_id
resource. Applied just those four resources (real generation, -target to
avoid touching the fake SSH-only host resources) and re-planned: "No
changes" - confirming the keepers pinning holds. Adding an explicit
postgres_password override afterward left the random_password resource
itself completely untouched (0 replace/destroy), confirming the override
path never disturbs the generated resource.
2026-07-21 01:08:57 +00:00
|
|
|
# Shared secrets (keep this file out of git)
|
Add Terraform to provision the VPS hosts (compose keeps running the app) (#223)
Terraform config under terraform/ manages the two existing VPS hosts and hands the
app to docker-compose, with local state:
- prod: the new 48GB/12-core VPS (release branch, thermograph.org), sized larger.
- beta: the old VPS 75.119.132.91 (main branch, testing tier), no public domain.
- The LAN dev box stays on deploy/deploy-dev.sh (dev branch) — out of Terraform.
A reusable module (modules/thermograph-host) SSHes each host to install docker/
compose/ufw (+ Caddy when a domain is set), sync the checkout to the host's branch,
render /etc/thermograph.env from Terraform variables (secrets pushed via provisioner
content, never on local disk), `docker compose up -d`, and health-check. Named
volumes are preserved on re-apply, so the Postgres data is never recreated.
Container resources are now env-driven in docker-compose.yml (APP_CPUS/DB_CPUS/
DB_MEMORY/WORKERS) with unchanged defaults, so Terraform can size each host.
2026-07-20 07:42:15 +00:00
|
|
|
# ---------------------------------------------------------------------------------
|
Have Terraform generate its own internal secrets, with sizing tiers (#239)
Terraform generates the secrets that have no external meaning
(POSTGRES_PASSWORD, AUTH_SECRET, METRICS_TOKEN, INDEXNOW_KEY) via the random
provider instead of requiring the operator to hand-generate and paste each
into terraform.tfvars. Each is pinned with a static keepers value (secrets.tf)
so apply never regenerates a value already in use - the exact incident class
this guards against: every session invalidated, the app<->DB password
mismatched. Rotation is now a deliberate keepers edit, never a side effect.
postgres_password/auth_secret move from required inputs to optional (default
"") - explicit var wins when supplied (seeding an EXISTING live secret during
a migration onto Terraform, hop-1 cutover runbook Stage 0), else Terraform
generates and owns it. metrics_token/indexnow_key are new: neither existed in
Terraform before, both previously left for the app's own fallback generation.
VAPID deliberately stays a required, non-generated input - an EC keypair
where regeneration breaks every existing push subscription outright, unlike
an opaque token.
Sizing tiers: a locals.sizes t-shirt map (nano/small/medium/large ->
{workers, app_cpus, db_cpus, db_memory}), toward the target Proxmox
sizing-tier model (architecture doc SS6) ahead of actually provisioning VMs -
Proxmox itself stays deferred; today a tier just sizes container caps on the
existing SSH-managed hosts. A host can reference one by name (hosts.<name>.
size) or keep hand-picking the four fields, so existing tfvars are
unaffected; prod's example now uses size = "large" (identical numbers),
beta keeps explicit numbers, and a commented uat example demonstrates the
shortcut for a future ephemeral host.
Strengthened terraform/README.md's local-state caveat: more Terraform-
generated secrets landing in tfstate raises the stakes of the existing
never-commit-cleartext-state guidance, not just the sizing.
Verified: terraform validate + fmt clean. A real `terraform plan` against
fake hosts (prod/beta/uat, mixing size="large"/explicit-numbers/size="nano")
resolved every sizing correctly (prod 8/8/4/16g, beta 4/4/2/8g, uat
1/1/1/1g) and planned exactly one instance of each random_password/random_id
resource. Applied just those four resources (real generation, -target to
avoid touching the fake SSH-only host resources) and re-planned: "No
changes" - confirming the keepers pinning holds. Adding an explicit
postgres_password override afterward left the random_password resource
itself completely untouched (0 replace/destroy), confirming the override
path never disturbs the generated resource.
2026-07-21 01:08:57 +00:00
|
|
|
# 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.
|
Add Terraform to provision the VPS hosts (compose keeps running the app) (#223)
Terraform config under terraform/ manages the two existing VPS hosts and hands the
app to docker-compose, with local state:
- prod: the new 48GB/12-core VPS (release branch, thermograph.org), sized larger.
- beta: the old VPS 75.119.132.91 (main branch, testing tier), no public domain.
- The LAN dev box stays on deploy/deploy-dev.sh (dev branch) — out of Terraform.
A reusable module (modules/thermograph-host) SSHes each host to install docker/
compose/ufw (+ Caddy when a domain is set), sync the checkout to the host's branch,
render /etc/thermograph.env from Terraform variables (secrets pushed via provisioner
content, never on local disk), `docker compose up -d`, and health-check. Named
volumes are preserved on re-apply, so the Postgres data is never recreated.
Container resources are now env-driven in docker-compose.yml (APP_CPUS/DB_CPUS/
DB_MEMORY/WORKERS) with unchanged defaults, so Terraform can size each host.
2026-07-20 07:42:15 +00:00
|
|
|
# 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 <no-reply@thermograph.org>"
|
|
|
|
|
# mail_reply_to = ""
|
|
|
|
|
|
|
|
|
|
# ---- Optional: Discord (leave "" to omit) ---------------------------------------
|
|
|
|
|
# discord_webhook = ""
|
|
|
|
|
# discord_public_key = ""
|
|
|
|
|
# discord_app_id = ""
|
|
|
|
|
# discord_bot_token = ""
|
|
|
|
|
# discord_client_secret = ""
|