|
All checks were successful
Sync infra to hosts / sync-beta (push) Has been skipped
Sync infra to hosts / sync-dev (push) Successful in 5s
secrets-guard / encrypted (push) Successful in 4s
PR build (required check) / build-backend (pull_request) Has been skipped
PR build (required check) / build-frontend (pull_request) Has been skipped
PR build (required check) / validate-observability (pull_request) Has been skipped
PR build (required check) / gate (pull_request) Successful in 1s
Sync infra to hosts / sync-prod (push) Has been skipped
shell-lint / shellcheck (push) Successful in 7s
secrets-guard / encrypted (pull_request) Successful in 5s
PR build (required check) / changes (pull_request) Successful in 7s
shell-lint / shellcheck (pull_request) Successful in 7s
Centralis could not migrate at all. `seed-from-sops.sh --all` refused
centralis.prod with `CENTRALIS_TOKENS: contains a shell metacharacter`, and
render-secrets-openbao.sh had no Centralis path whatsoever — so the seeded data
would have had nothing to read it.
Both halves come from applying the app stack's rules to a file that does not share
its shape. /etc/thermograph.env is raw unquoted KEY=value. /etc/centralis.env is
consumed by exactly one thing and that thing is a shell:
sudo bash -c 'set -a && . /etc/centralis.env && set +a && docker compose up'
CENTRALIS_TOKENS is JSON, so it can never satisfy a no-metacharacters rule, and
rendering it unquoted is precisely the 2026-07-24 failure: bash strips the quotes,
Centralis fails closed on the malformed registry and serves a single `shared`
identity, indistinguishable from the file never being written.
So:
- seed-from-sops.sh validates against the renderer that will consume the path. The
dotenv rules still apply to common and env/*; centralis/* is exempt because it is
single-quoted downstream.
- render-secrets-openbao.sh gains thermograph_render_openbao_centralis, mirroring
render_centralis_secrets: fetch as JSON, emit POSIX single-quoted assignments,
then PROVE the file by sourcing it under `env -i` with `set -a` and comparing
every value back before anything touches the destination. Mirrored rather than
shared, per the existing note above thermograph_render_openbao — consolidate when
the SOPS path is deleted.
- render_centralis_secrets gains the same early-return backend dispatch the app
path already uses, so the new function is reachable. Without it the SOPS path was
the only path regardless of TG_SECRETS_BACKEND.
Verified against a synthetic registry containing a JSON token map, an apostrophe,
and `$(...)`/backtick/backslash/double-quote payloads: 5 keys round-trip
byte-for-byte through `set -a; . file`, CENTRALIS_TOKENS parses to the right
subjects, and the command substitution does not execute. The negative case is
covered too — fed the unquoted 2026-07-24 shape, the round-trip check reports the
value as changed (by length, never content) and refuses to write.
Also corrects the claim in render-secrets-openbao.sh that the SOPS path never
tripped the metacharacter hazard "because every current value happens to be
alphanumeric". CENTRALIS_TOKENS is the counter-example; it never tripped those
paths because it was never in them.
|
||
|---|---|---|
| .. | ||
| backup | ||
| db | ||
| forgejo | ||
| migrations | ||
| openmeteo | ||
| secrets | ||
| stack | ||
| swarm | ||
| twa | ||
| Caddyfile.vps1 | ||
| Caddyfile.vps2 | ||
| deploy-dev.sh | ||
| deploy.sh | ||
| env-topology.sh | ||
| migrate-db.py | ||
| POSTGRES-MIGRATION.md | ||
| provision-agent-access.sh | ||
| provision-dev.sh | ||
| provision-mail-supervision.sh | ||
| provision-mail.sh | ||
| provision-secrets.sh | ||
| render-secrets-openbao.sh | ||
| render-secrets.sh | ||
| RUNBOOK-vps1-vps2-cutover.md | ||
| thermograph-dev.service | ||
| thermograph.env.example | ||
| thermograph.service | ||