Files
factory/vibe/guidebooks/factory-provisioning/opentofu
Gabriel Radureau dbe32161dc docs(vibe): add factory-provisioning guidebook (Ansible + OpenTofu)
Deep, code-grounded tree-docs guidebook under vibe/guidebooks/factory-provisioning/,
explored from the actual playbooks/roles and tofu code:

- Hub: the two provisioning engines (operator-run Ansible vs CI-applied OpenTofu),
  a green-field bring-up flow, master index, maintenance rule.
- ansible/ sub-tree: ordered pages 01-system .. 06-recover, an inventory & variables
  concept page, and a Tier-1/Tier-2 roles reference (hashicorp_vault, step_ca,
  crowdsec, pihole, deploy_docker_compose + the gitea_* family and helpers).
- opentofu/ sub-tree: factory-iac (Cloudflare/OVH/GCP/Gitea/Vault edge +
  cloudflare_token module), postgres-iac (per-app DB/role/pgbouncer lookup),
  ci-apply-flow (Gitea OIDC-JWT -> Vault -> auto-approve apply).

Cross-linked bidirectionally with the lab-ecosystem guidebook and the safe-env
ADR/PRD (the sandbox rehearses exactly these engines). 14 mermaid diagrams
MCP-validated; zero dead links. Authored by the Lab Cartographer cohort.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 21:11:51 +02:00
..

vibe > Guidebooks > Factory provisioning > OpenTofu

OpenTofu — factory provisioning

Note

Status: active · Last Updated: 2026-06-23 Upstream: Factory provisioning hub · Lab ecosystem · 01 factory Downstream: factory iac · postgres iac · CI apply flow Related: Secrets & Vault · Storage & recovery · Naming conventions · ADR-0001 safe prod-like environment

OpenTofu is the declarative half of the factory: it provisions everything that lives outside the K3s cluster — Gitea repos & CI users, Vault policies, Cloudflare DNS, OVH domains, a GCS backup bucket, and the in-cluster PostgreSQL roles/databases. The imperative half (the cluster itself) is built by Ansible.

OpenTofu is pinned to 1.8.2 in CI (OPENTOFU_VERSION).


Two independent state roots

There are two separate Terraform/OpenTofu roots, each with its own backend.tf, its own GCS state prefix, its own provider set, and its own CI workflow. They never share state and can be applied independently.

Root Code path State backend (GCS) Triggered by
factory iac iac/ gs://arcodange-tf/factory/main changes under iac/**.gitea/workflows/iac.yaml
postgres iac postgres/iac/ gs://arcodange-tf/factory/postgres changes under postgres/**.gitea/workflows/postgres.yaml

Note

Both roots share the same GCS bucket (arcodange-tf) but live under distinct prefixes (factory/main vs factory/postgres), so their state objects never collide.


Providers

Provider Version Endpoint / scope Auth
go-gitea/gitea 0.6.0 https://gitea.arcodange.lab GITEA_TOKEN env var
vault 4.4.0 https://vault.arcodange.lab JWT login — mount gitea_jwt, role gitea_cicd
google 7.0.1 project arcodange, region US-EAST1 GOOGLE_CREDENTIALS (factory) / GOOGLE_BACKEND_CREDENTIALS (postgres backend)
cloudflare/cloudflare ~> 5 DNS / IAM CLOUDFLARE_API_TOKEN env var
ovh/ovh 2.8.0 endpoint ovh-eu OVH_APPLICATION_KEY / OVH_APPLICATION_SECRET / OVH_CONSUMER_KEY
cyrilgdn/postgresql 1.24.0 192.168.1.202 (pi2), superuser POSTGRES_USERNAME / POSTGRES_PASSWORD (TF vars)

The first five providers belong to the factory iac root (iac/providers.tf); the postgres iac root (postgres/iac/providers.tf) declares only postgresql + vault. Both roots configure the vault provider identically (JWT, mount gitea_jwt, role gitea_cicd).


The Vault-JWT auth model

Neither root carries long-lived Vault credentials. Instead CI mints a short-lived Gitea OIDC token and exchanges it for Vault access:

  1. A first job decodes the base64 secret vault_oauth__sh_b64 and runs it (base64 -d | bash), producing a Gitea OIDC JWT as a job output (gitea_vault_jwt).
  2. That JWT is exported into the apply job as TERRAFORM_VAULT_AUTH_JWT.
  3. The vault provider's auth_login_jwt block consumes it against mount gitea_jwt / role gitea_cicd, yielding a scoped Vault token used to read the per-provider secrets (Google creds, Gitea token, Cloudflare token, OVH app keys, Postgres creds).

See Secrets & Vault for the full Vault policy/mount design and CI apply flow for the job-by-job walkthrough.


CI apply flow

Both workflows share the same two-job shape: authenticate, then apply. The trigger paths differ (iac/** vs postgres/**) but the structure is identical.

%%{init: {'theme':'base', 'themeVariables': {'primaryColor':'#1f2937','primaryTextColor':'#f9fafb','lineColor':'#6b7280','fontSize':'14px'}}}%%
flowchart TD
  classDef trigger fill:#1e3a5f,stroke:#3b82f6,color:#f9fafb;
  classDef job fill:#1e4620,stroke:#22c55e,color:#f0fdf4;
  classDef danger fill:#5f1e1e,stroke:#ef4444,color:#fef2f2;

  push["push / PR touching<br/> iac/** or postgres/**"]:::trigger
  auth["job: gitea_vault_auth<br/>decode vault_oauth__sh_b64<br/> mint Gitea OIDC JWT"]:::job
  tofu["job: tofu<br/>read Vault secrets via JWT<br/> set provider env vars"]:::job
  apply["dflook/terraform-apply@v1<br/> auto_approve: true"]:::danger

  push --> auth
  auth -- "gitea_vault_jwt output" --> tofu
  tofu --> apply
  1. A push or PR that touches files under iac/** (factory) or postgres/** (postgres) starts the matching workflow; workflow_dispatch allows a manual run.
  2. The gitea_vault_auth job decodes vault_oauth__sh_b64 and emits the Gitea OIDC JWT as gitea_vault_jwt.
  3. The tofu job (needs: gitea_vault_auth) sets TERRAFORM_VAULT_AUTH_JWT from that output, reads the provider secrets out of Vault, and prepares the homelab CA cert (VAULT_CACERT).
  4. The job runs dflook/terraform-apply@v1 against the root's path (iac or postgres/iac) with auto_approve: true.

Caution

Applies are auto-approve. There is no manual plan-review gate — once a change to iac/** or postgres/** lands on main, CI applies it to the real Gitea, Vault, Cloudflare, OVH, GCS, and PostgreSQL targets without further confirmation. Treat every merge as a production change and review the diff before merging, not after. This trade-off is recorded in ADR-0001 · safe prod-like environment.


Index

Page Covers State
factory iac iac/ root — Gitea, Vault, Google/GCS backup, Cloudflare, OVH
postgres iac postgres/iac/ root — PostgreSQL roles & databases on pi2
CI apply flow Both Gitea workflows, the Vault-JWT exchange, auto-approve apply