# 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: driven by infra/tofu/tf.sh, which decrypts the COMMITTED, SOPS/age-encrypted # secrets.sops.env and exports AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY (R2 S3 keys) plus builds # the TF_ENCRYPTION blob from the single TF_STATE_PASSPHRASE. The non-secret bucket/key/endpoint # live in the committed backend.r2.hcl (an R2 account id is not a credential). State bytes are # encrypted at rest via TF_ENCRYPTION (see REMOTE-STATE.md) — the passphrase lives ONLY inside the # encrypted secrets.sops.env (and your password manager), never in git plaintext or in the state. # # INIT (run via the wrapper, inside the dev container — non-secret bits from backend.r2.hcl, # secret bits decrypted from secrets.sops.env): # ./infra/devcontainer/dev.sh run ./infra/tofu/tf.sh init # # 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 } -> committed backend.r2.hcl, passed by tf.sh via -backend-config. # # 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). } }