Files
home-network-k3s-impl/infra/tofu/backend.tf
T
jakman85andClaude Opus 4.8 485b9e3888 infra(tofu): scaffold off-cluster R2 remote state (encrypted)
Local-only tfstate meant losing the workstation would strand a rebuild (apply
would try to recreate the 3 live nodes). Move state to Cloudflare R2, which sits
OUTSIDE the Proxmox->VM->cluster->Gitea chain, so a full cluster rebuild can still
read it; encrypted at rest via OpenTofu native AES-GCM.

Adds a partial s3 backend (R2 skip-flags), gitignored backend.hcl/.r2.env for the
account-specific + secret bits, their .example templates, and a migration+DR runbook.
Locking (use_lockfile) deferred to an OpenTofu >=1.10 bump (1.9.0 lacks it).
Migration (tofu init -migrate-state) runs once the operator provisions the R2 bucket.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-08-27 15:57:12 -05:00

38 lines
2.1 KiB
Terraform

---
# Remote state — Cloudflare R2 (S3-compatible), stored OFF the k3s cluster.
#
# WHY OFF-CLUSTER (the load-bearing decision): tofu state must live at or below the layer it
# manages. Its dependency chain is Proxmox host -> k3s VMs -> k3s cluster -> Gitea/apps, so state
# in Gitea (top of the chain) would be UNREADABLE in exactly the disaster it exists for — cluster
# gone, `tofu apply` to rebuild. R2 sits entirely outside that chain: a full cluster rebuild can
# still read state. It also survives total-site loss (off-site) and gives multi-machine access.
#
# SECRETS: the account-specific bucket/endpoint come from a gitignored backend.hcl; the S3 keys
# come from the environment (.r2.env: AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY). State bytes are
# encrypted at rest via the TF_ENCRYPTION env blob (see REMOTE-STATE.md) — the passphrase lives ONLY
# in your password manager and the gitignored .r2.env, never in git or in the state itself.
#
# INIT (partial config — non-secret bits here, secret bits from the gitignored file):
# cd infra/tofu && source .r2.env && tofu init -backend-config=backend.hcl -migrate-state
#
# NOTE: this `terraform {}` block is MERGED with the one in versions.tf (required_version +
# required_providers). OpenTofu allows multiple terraform blocks across files; `backend` appears once.
terraform {
backend "s3" {
region = "auto" # R2 ignores region, but the S3 backend requires a value.
# R2 is S3-compatible but not AWS: disable the AWS-only preflight/validation paths.
skip_credentials_validation = true
skip_region_validation = true
skip_requesting_account_id = true
skip_metadata_api_check = true
skip_s3_checksum = true # R2 rejects AWS trailing (streaming) checksums.
# bucket, key, endpoints{ s3 } -> backend.hcl (gitignored; keeps the R2 account id out of git).
#
# use_lockfile = true -> ENABLE after bumping OpenTofu to >= 1.10. Native S3 conditional-write
# locking (no DynamoDB); works on R2 via If-None-Match. 1.9.0 has no lockfile support, so state
# locking is OFF for now (single-operator workflow — see REMOTE-STATE.md follow-up).
}
}