From e40d6b0e44a9ddaf1e50b5b8aa74b068a9c395d4 Mon Sep 17 00:00:00 2001 From: rob thijssen Date: Sun, 3 May 2026 17:52:35 +0300 Subject: [PATCH] chore(asset): add postgres bootstrap and pg_ident template MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Idempotent SQL for role and database creation, split between the postgres-database scope (bootstrap.sql) and the moments-database scope (bootstrap-moments.sql), since CREATE DATABASE can't run inside a DO block or transaction. Roles: moments_rw — owner of the moments database; runs migrations and writes events from moments-worker. moments_ro — read-only; consumed by moments-api. The pg_ident template is rendered per-host by deploy.sh once it lands; one (host, role) mapping per file. Reload required on both magrathea and frankie after install — pg_ident is not replicated. Co-Authored-By: Claude Opus 4.7 (1M context) --- asset/postgres/ident.conf.tmpl | 11 +++++++++++ asset/sql/bootstrap-moments.sql | 17 +++++++++++++++++ asset/sql/bootstrap.sql | 26 ++++++++++++++++++++++++++ 3 files changed, 54 insertions(+) create mode 100644 asset/postgres/ident.conf.tmpl create mode 100644 asset/sql/bootstrap-moments.sql create mode 100644 asset/sql/bootstrap.sql diff --git a/asset/postgres/ident.conf.tmpl b/asset/postgres/ident.conf.tmpl new file mode 100644 index 0000000..4e5e36b --- /dev/null +++ b/asset/postgres/ident.conf.tmpl @@ -0,0 +1,11 @@ +# moments — pg_ident.conf.d drop-in template +# Rendered by script/deploy.sh per host based on manifest.yml. +# Install path: /var/lib/pgsql/18/data/pg_ident.conf.d/{{HOST_FQDN}}.conf +# Apply with: sudo systemctl reload postgresql-18 — on BOTH magrathea +# (primary) and frankie (standby) so failover doesn't lock the app out. +# +# One line per (host, role) mapping. A host that runs both moments-api +# and moments-worker will have two lines (one for moments_ro, one for +# moments_rw). + +cert_cn {{HOST_FQDN}} {{DB_ROLE}} diff --git a/asset/sql/bootstrap-moments.sql b/asset/sql/bootstrap-moments.sql new file mode 100644 index 0000000..6da8209 --- /dev/null +++ b/asset/sql/bootstrap-moments.sql @@ -0,0 +1,17 @@ +-- In-database grants for the moments database. +-- Run after asset/sql/bootstrap.sql, against the moments database. +-- Idempotent — safe to re-run on every deploy. +-- +-- psql -h magrathea.kosherinata.internal -U postgres -d moments \ +-- -f asset/sql/bootstrap-moments.sql +-- +-- The schema itself is created by sqlx migrations executed by moments-api +-- on startup (which runs as moments_rw, the database owner). This file +-- only manages the read-only role's access to whatever moments_rw creates. + +GRANT USAGE ON SCHEMA public TO moments_ro; +GRANT SELECT ON ALL TABLES IN SCHEMA public TO moments_ro; + +-- Tables created later by moments_rw (running migrations) inherit SELECT for moments_ro. +ALTER DEFAULT PRIVILEGES FOR ROLE moments_rw IN SCHEMA public + GRANT SELECT ON TABLES TO moments_ro; diff --git a/asset/sql/bootstrap.sql b/asset/sql/bootstrap.sql new file mode 100644 index 0000000..98dda3d --- /dev/null +++ b/asset/sql/bootstrap.sql @@ -0,0 +1,26 @@ +-- moments role and database bootstrap. +-- Run as a postgres superuser against the cluster's `postgres` database. +-- Idempotent — safe to re-run on every deploy. +-- +-- psql -h magrathea.kosherinata.internal -U postgres -d postgres \ +-- -f asset/sql/bootstrap.sql +-- +-- After this completes, run asset/sql/bootstrap-moments.sql against the +-- newly created `moments` database to apply the in-database grants. + +DO $$ +BEGIN + IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = 'moments_rw') THEN + CREATE ROLE moments_rw LOGIN; + END IF; + IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = 'moments_ro') THEN + CREATE ROLE moments_ro LOGIN; + END IF; +END +$$; + +SELECT 'CREATE DATABASE moments OWNER moments_rw' +WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = 'moments') +\gexec + +GRANT CONNECT ON DATABASE moments TO moments_ro, moments_rw;