feat(terraform): review production from the operator machine; retire the CI jobs #48

Merged
jfraeys merged 1 commit from jfraeys/chore/prod-plan-local into main 2026-10-10 12:58:33 -04:00
Owner

Option 2 from the discussion: production Terraform is reviewed and applied from the operator machine, not CI.

Why: prod's non-secret inputs (Zedmail/DMARC values, flags) and the deSEC/OVH credentials live only in the SOPS vault. A CI plan of the production workspace therefore ran on Terraform defaults and wanted to destroy 7 live Cloudflare resources (terraform-plan run 270).

Adds scripts/plan-prod.sh [plan|drift|both]: runs the linode workspace plan and refresh-only drift from this machine using the vault; read-only; exit 0 clean / 2 changes or drift / 1 error; PLAN_PROD_VERBOSE=1 for the attribute-level diff.

Removes terraform-plan.yml, drift-check.yml, terraform-apply.yml (it could only fail at its required variables; adding placeholders would have made it apply the destroys), and the pre-promotion drift gate in staging-validate (it exited 1 without root_pass, so the promotion PR was never opened). The promotion PR text now says to run the script first. README, manifest used_by, the CI-secrets restore runbook and the inventory note are updated.

Production apply is now local: ./setup --workspace linode --confirm-prod. Merging the main->prod PR no longer applies anything.

First real run (not applied): 0 add, 8 change, 0 destroy, all Cloudflare records: vpn A/AAAA proxied false->true; web @/mail/www A/AAAA content 172.105.107.95 -> .92 (95 is the services instance, 92 is web). Needs a human decision before any apply.

Not verified: staging-validate after the promote-job edit (YAML parses; the job only runs on push to main after a passing validate).

🤖 Generated with Claude Code

Option 2 from the discussion: production Terraform is reviewed and applied from the operator machine, not CI. **Why:** prod's non-secret inputs (Zedmail/DMARC values, flags) and the deSEC/OVH credentials live only in the SOPS vault. A CI plan of the production workspace therefore ran on Terraform defaults and wanted to destroy 7 live Cloudflare resources (terraform-plan run 270). **Adds** `scripts/plan-prod.sh [plan|drift|both]`: runs the linode workspace plan and refresh-only drift from this machine using the vault; read-only; exit 0 clean / 2 changes or drift / 1 error; `PLAN_PROD_VERBOSE=1` for the attribute-level diff. **Removes** `terraform-plan.yml`, `drift-check.yml`, `terraform-apply.yml` (it could only fail at its required variables; adding placeholders would have made it apply the destroys), and the pre-promotion drift gate in `staging-validate` (it exited 1 without root_pass, so the promotion PR was never opened). The promotion PR text now says to run the script first. README, manifest `used_by`, the CI-secrets restore runbook and the inventory note are updated. **Production apply is now local:** `./setup --workspace linode --confirm-prod`. Merging the main->prod PR no longer applies anything. **First real run (not applied):** 0 add, 8 change, 0 destroy, all Cloudflare records: vpn A/AAAA proxied false->true; web `@`/`mail`/`www` A/AAAA content 172.105.107.95 -> .92 (95 is the `services` instance, 92 is `web`). Needs a human decision before any apply. Not verified: `staging-validate` after the promote-job edit (YAML parses; the job only runs on push to main after a passing validate). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
feat(terraform): review production from the operator machine; retire the CI jobs
All checks were successful
Lint / lint (pull_request) Successful in 1m30s
58505593cc
CI cannot plan or apply production faithfully: prod's inputs and the deSEC/OVH
credentials live only in the SOPS vault, and a CI plan with Terraform's defaults
wanted to destroy 7 live Cloudflare resources (terraform-plan run 270). Add
scripts/plan-prod.sh (plan + refresh-only drift of the linode workspace,
read-only, exit 0/2/1, optional full output) and remove terraform-plan.yml,
drift-check.yml, terraform-apply.yml and the pre-promotion drift gate in
staging-validate (the promotion PR now says to run the script first). Docs,
manifest used_by and the CI-secrets restore runbook are updated.

First real run against prod: no adds or destroys, 8 in-place DNS updates.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
jfraeys/infra!48
No description provided.