Wire schema v1 into a manual Supabase migration and vendor the real stack
deploy / deploy (push) Successful in 5m57s
check / flake-check (push) Failing after 35m52s

- Move db/schema.sql to supabase/migrations/ as the first supabase CLI
  migration (manual `db push` only, no automated runner); re-add the
  gebos_ingest role + grants there since init.sql never re-runs
- Add gebos-postgres-passwords oneshot on db-host: syncs role passwords
  from sops (LoadCredential, journal-safe), makes supabase_admin
  SUPERUSER and hands the auth schema to supabase_auth_admin to match
  the upstream supabase/postgres image; add pg_hba rule for 10.0.0.0/8
- Vendor the official docker-compose (studio/kong/auth/rest/meta only,
  external Postgres, loopback Studio with no Kong dashboard route) plus
  kong.yml (trimmed) and kong-entrypoint.sh (verbatim); tested: compose
  config, Kong config parse, migration applied on TimescaleDB pg17
- Document decisions as ADRs 0001-0004 (migrations, passwords, vendored
  stack, JWT API keys incl. verify/mint procedure)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Lars Nolden
2026-07-08 22:33:50 +02:00
parent b2a1056255
commit 2ce68cb098
17 changed files with 967 additions and 118 deletions
@@ -0,0 +1,61 @@
# ADR-0002: Postgres role passwords are synced by a one-shot systemd unit
Date: 2026-07-08
Status: Accepted
## Context
The Supabase services on app-host and the ingester on mqtt-ingest connect to
db-host over TCP and need password auth, but every login role
(`supabase_admin`, `supabase_auth_admin`, `authenticator`, `gebos_ingest`)
was created passwordless.
NixOS deliberately offers **no declarative option** for Postgres passwords:
anything the configuration references lands world-readable in `/nix/store`,
and `initialScript` — the only built-in hook that could set one — runs solely
at first initdb. Passwords are data, not configuration.
Two supporting gaps surfaced at the same time:
* NixOS's default `pg_hba.conf` covers only local sockets and loopback, so
connections from 10.0.0.0/8 were rejected before password auth even
started, firewall rule notwithstanding.
* The upstream `supabase/postgres` image ships role *attributes* our vanilla
Postgres lacked: `supabase_admin` is a SUPERUSER there (Studio/postgres-meta
connect as it), and `supabase_auth_admin` owns the `auth` schema (GoTrue
runs its own migrations in it at startup).
## Decision
A oneshot unit, `gebos-postgres-passwords` (`nix/modules/gebos-postgres.nix`),
runs on db-host after `postgresql.service` on every boot/deploy:
* Runs as the `postgres` OS user (local peer auth); reads the sops-rendered
env file via systemd `LoadCredential`, so the root-owned 0400 file needs no
permission widening.
* Idempotently syncs: `postgres``postgres_admin_password`;
`supabase_admin` / `supabase_auth_admin` / `authenticator` ← the shared
`supabase_postgres_password` (mirroring upstream's single
`POSTGRES_PASSWORD` convention); `gebos_ingest`
`ingester_postgres_password`.
* Enforces the two upstream attributes: `supabase_admin SUPERUSER` and
`ALTER SCHEMA auth OWNER TO supabase_auth_admin`.
* Guards every role for existence (tolerates a cluster the schema-v1
migration hasn't reached) and discards query output
(`--output=/dev/null`) so passwords never reach the journal.
* db-host enables the `supabase` and `ingester` secret toggles solely to get
those keys into its env file; `.sops.yaml`'s single key group already
permits this.
* `pg_hba` gains `host all all 10.0.0.0/8 scram-sha-256`.
## Consequences
* No password ever exists in git, the nix store, or SQL files; rotation is
`sops nix/secrets/secrets.yaml` + deploy + `systemctl restart
gebos-postgres-passwords` (the unit does not auto-rerun on secret-content
changes alone).
* The three Supabase roles share one password by design — per-role passwords
would diverge from the vendored compose, which interpolates a single
`${POSTGRES_PASSWORD}` everywhere.
* db-host can decrypt the Supabase JWT/API-key secrets it doesn't strictly
need; acceptable until `.sops.yaml` moves to per-prefix rules (noted there).