Skip to content

Terraform

Terraform manages what sits around the cluster: Proxmox, the Talos VMs, PBS, the Tailscale router, UniFi and Cloudflare. Each stack is its own root with its own state, run by hand from a laptop. Argo CD takes over once the Talos stack has installed it.

Stacks

Root Manages State key
azure/state the storage account that holds every other stack's state critical/do-not-delete-state-backend.tfstate
proxmox/hyper-cluster/datacenter Proxmox VE Datacenter and node level (proxmox.md) proxmox/hyper-cluster/datacenter.tfstate
proxmox/hyper-cluster/k8s/talos the six Talos VMs, machine config, Cilium and the first Argo CD install (talos.md) proxmox/hyper-cluster/talos-genesis.tfstate
proxmox/hyper-cluster/tailscale the subnet router VM and the tailnet policy (remote-access.md) proxmox/hyper-cluster/tailscale.tfstate
proxmox/pbs Proxmox Backup Server (pbs.md) proxmox/pbs.tfstate
unifi/network the UniFi site (unifi.md) unifi/network.tfstate
unifi/dns local.bigd.no records (dns.md) unifi/dns.tfstate
cloudflare/nordbye-it, bigd-no, logeverylift-com the three public zones (cloudflare.md) cloudflare/<stack>.tfstate
cloudflare/watchdog the watchdog Worker cloudflare/watchdog.tfstate

The Proxmox tokens the Proxmox and PBS stacks log in with, and the order to apply pbs and datacenter in, are created by hand in BOOTSTRAP.md.

State backend

State lives in Azure: resource group rg-tfstate-homelab, storage account sttfstatemvnhomelab, container tfstate, with blob versioning on (main.tf). Every other root names these in a backend "azurerm" block with its own key. azure/state stores its own state in the same container through an empty backend "azurerm" {} filled from a gitignored backend.conf (version.tf); its README has the two-stage bootstrap.

The backend blocks carry no credentials, so the azurerm backend authenticates with whatever Azure CLI login the shell has; the azurerm provider in azure/state sets use_cli = true for the same reason. That login (az login, in the azure/state README) is done by hand before running Terraform; nothing in the repo automates it.

Local files

Git holds a terraform.tfvars.example for every stack but azure/state; the real values are gitignored (*.tfvars, backend.conf, kubeconfig, talosconfig in .gitignore) and exist only on the laptop that runs Terraform.

Stack Needs Writes
azure/state terraform.tfvars (subscription_id), backend.conf
proxmox/hyper-cluster/datacenter terraform.tfvars (proxmox_api_token, pbs_backup_token)
proxmox/hyper-cluster/k8s/talos terraform.tfvars (Proxmox token, nodes, versions) kubeconfig, talosconfig
proxmox/hyper-cluster/tailscale terraform.tfvars (Proxmox token, SSH password, Tailscale OAuth client)
proxmox/pbs terraform.tfvars (pbs_api_token)
unifi/network, unifi/dns terraform.tfvars (unifi_api_key; network also the Wi-Fi passphrases and DDNS tokens)
cloudflare/* terraform.tfvars (cloudflare_api_token)
cloudflare/watchdog also account_id in terraform.tfvars, and BW_ACCESS_TOKEN and BW_ORGANIZATION_ID in the shell for the Bitwarden provider

The Talos stack's kubeconfig and talosconfig are the cluster's admin credentials. convert-secrets.sh turns the stack's talos_secrets output into a talosctl secrets bundle.

Backing up the local files

scripts/backup-secrets.sh zips the paths listed in .backup-manifest and between the # backup:start and # backup:end markers in .gitignore, and copies the zip over SMB to the NAS share shared-data under documents/IT/Repo-Hidden-Files-Backups/Homelab/, keeping the newest 14. Only paths are in the repo, never contents. The manifest lists the Talos kubeconfig, talosconfig and controlplane.yaml, every stack's terraform.tfvars, and azure/state/backend.conf. A new stack's gitignored files need a line there.

scripts/backup-secrets.sh --list      # what would be backed up, and what is missing
scripts/backup-secrets.sh --dry-run   # build the zip locally, no upload
scripts/backup-secrets.sh             # prompts for the NAS login, uploads, verifies

Restore is unzip <file>.zip at the repo root. Where it sits among the other backups: ../backups/README.md.

CI

ci-terraform.yaml runs on pull requests and pushes to main that touch terraform/: terraform fmt -check -recursive, then init -backend=false and validate in every directory that holds a .tf file, so a new root is covered without editing the workflow. It has no credentials and never reads state; plan and apply stay on the laptop, since the Proxmox and UniFi APIs are LAN-only.