Promote dev to main: vault consolidation + renderer fail-closed #76

Merged
admin_emi merged 2 commits from dev into main 2026-07-25 00:47:06 +00:00
Owner

Promotes the vault consolidation and the renderer fix to beta (and syncs infra/ to both hosts).

Detail in #75. What matters for this hop:

Merging here triggers infra-sync.yml, which does git reset --hard and then re-renders /etc/thermograph.env on both prod and beta. That is the moment common.yaml takes effect.

The sequencing is safe but worth understanding. prod.yaml is now trimmed to 16 keys and the other 16 live in common.yaml. A render with the trimmed file but without common.yaml present would write a half-empty env file. infra-sync checks out both together before rendering, so they arrive atomically — but the verification after this merge is a key count, not a green tick.

Expected after sync: prod 32 keys, beta 24 — exactly as before. The consolidation is provably value-preserving (merged(common + env) == original, verified twice), so a different count means something went wrong and the fix is to revert this merge, not to debug forward.

The renderer change makes the failure mode loud: if a host is SOPS-configured but its vault is not where the caller looked, the render now exits 1 instead of returning 0 having done nothing.

Promotes the vault consolidation and the renderer fix to **beta** (and syncs `infra/` to both hosts). Detail in #75. What matters for this hop: **Merging here triggers `infra-sync.yml`**, which does `git reset --hard` and then re-renders `/etc/thermograph.env` on both prod and beta. That is the moment `common.yaml` takes effect. **The sequencing is safe but worth understanding.** `prod.yaml` is now trimmed to 16 keys and the other 16 live in `common.yaml`. A render with the trimmed file but *without* `common.yaml` present would write a half-empty env file. `infra-sync` checks out both together before rendering, so they arrive atomically — but the verification after this merge is a key count, not a green tick. **Expected after sync:** prod 32 keys, beta 24 — exactly as before. The consolidation is provably value-preserving (`merged(common + env) == original`, verified twice), so a different count means something went wrong and the fix is to revert this merge, not to debug forward. **The renderer change makes the failure mode loud**: if a host is SOPS-configured but its vault is not where the caller looked, the render now exits 1 instead of returning 0 having done nothing.
admin_emi added 2 commits 2026-07-25 00:45:11 +00:00
secrets: factor shared values into common.yaml; stop the renderer failing open
All checks were successful
PR build (required check) / changes (pull_request) Successful in 7s
secrets-guard / encrypted (pull_request) Successful in 5s
shell-lint / shellcheck (pull_request) Successful in 6s
PR build (required check) / validate-observability (pull_request) Has been skipped
PR build (required check) / build-frontend (pull_request) Successful in 1m11s
PR build (required check) / build-backend (pull_request) Successful in 1m25s
PR build (required check) / gate (pull_request) Successful in 2s
cb69a22c0f
Two changes to how credentials are stored and distributed.

1. common.yaml now exists.

The renderer has always concatenated common.yaml then <env>.yaml, host winning,
and the README has always documented common.yaml with a checkmark. The file was
never created. So every value shared by prod and beta was duplicated in both
vaults, free to drift apart with nothing to detect it.

16 values move to common.yaml: VAPID keypair, metrics token, IndexNow key,
REGISTRY_TOKEN, the S3 endpoint/bucket and both S3 keypairs, plus shared config.
5 stay per-host because their values genuinely differ (APP_CPUS, DB_CPUS,
DB_MEMORY, WORKERS, THERMOGRAPH_BASE_URL). 8 exist only on prod — the Discord
and mail credentials, which beta does not have at all.

Three more are held back deliberately despite being identical today:
POSTGRES_PASSWORD, THERMOGRAPH_AUTH_SECRET, THERMOGRAPH_DATABASE_URL. These are
the credentials that let one environment act as another, and beta is the more
exposed box — it serves public Forgejo and Grafana. They match only because beta
was seeded from prod. Keeping them per-host costs one line each and preserves
the ability to diverge; putting them in common.yaml would encode the equivalence
as intentional and make breaking it a migration rather than an edit. Reasoning
recorded in the vault README, since a future reader will otherwise "fix" it.

Done without any plaintext leaving prod: ciphertext was shipped up, decrypted
against the host's age key, recombined, re-encrypted, and shipped back. The
consolidation refuses to write unless it has proved merged(common + env) equals
the original env vault exactly — same keys, same values — and re-verifies from
the written files afterwards. Both checks passed for prod and beta.

2. render_thermograph_secrets no longer fails open.

One `return 0` covered two different situations: "this host is not configured
for SOPS" (true of the LAN dev box, and correct to succeed) and "this host IS
configured but its vault is not where we looked". The second is a failure, and
returning 0 made it a silent no-op wearing a success code — a deploy would
report success having rendered nothing, and the host would keep serving whatever
/etc/thermograph.env already held, including after a rotation.

The likely cause is passing the wrong root: the function wants the directory
containing deploy/secrets, which on the hosts is /opt/thermograph/infra, not
/opt/thermograph. That mistake looked exactly like "not configured here", which
is why it went unnoticed. It now exits 1 and names the probable cause.

Claude-Session: https://claude.ai/code/session_0182KTMrsTHJc3TcewCatJFY
Merge pull request 'secrets: factor shared values into common.yaml; stop the renderer failing open' (#75) from fix/key-gaps-digit-regex into dev
All checks were successful
secrets-guard / encrypted (push) Successful in 6s
shell-lint / shellcheck (push) Successful in 8s
PR build (required check) / changes (pull_request) Successful in 8s
secrets-guard / encrypted (pull_request) Successful in 7s
shell-lint / shellcheck (pull_request) Successful in 8s
PR build (required check) / validate-observability (pull_request) Has been skipped
PR build (required check) / build-frontend (pull_request) Successful in 1m5s
PR build (required check) / build-backend (pull_request) Successful in 1m14s
PR build (required check) / gate (pull_request) Successful in 2s
a00b2c016b
admin_emi merged commit 9350045866 into main 2026-07-25 00:47:06 +00:00
Sign in to join this conversation.
No reviewers
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: Jinemi/thermograph#76
No description provided.