Wire schema v1 into a manual Supabase migration and vendor the real stack
- 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:
@@ -37,8 +37,98 @@ in
|
||||
# supabase_vault — TODO: package or vendor
|
||||
];
|
||||
# Init script creates Supabase's `auth`, `storage`, `_analytics`, `_supavisor`
|
||||
# schemas, the gebos_ingest role (BYPASSRLS), and telemetry hypertables.
|
||||
# schemas and the Supabase admin/API roles. It only runs at first initdb;
|
||||
# everything else arrives via manual supabase migrations (see
|
||||
# supabase/README.md) and the password oneshot below.
|
||||
initialScript = ../supabase/init.sql;
|
||||
|
||||
# The firewall limits 5432 to 10.0.0.0/8, but NixOS's default pg_hba
|
||||
# only covers local sockets and loopback — without this line every
|
||||
# connection from app-host / mqtt-ingest is rejected before password
|
||||
# auth even starts.
|
||||
authentication = ''
|
||||
host all all 10.0.0.0/8 scram-sha-256
|
||||
'';
|
||||
};
|
||||
|
||||
# Role passwords are data, not configuration: NixOS has no declarative
|
||||
# option for them (anything reachable from the config lands world-readable
|
||||
# in /nix/store, and initialScript only runs at first initdb). This
|
||||
# oneshot re-applies them from the sops-rendered env file on every boot /
|
||||
# deploy, idempotently. It also enforces two attributes the Supabase
|
||||
# services expect because the upstream supabase/postgres image ships them:
|
||||
# supabase_admin is SUPERUSER (Studio/postgres-meta connect as it) and
|
||||
# supabase_auth_admin owns the auth schema (GoTrue runs its own migrations
|
||||
# there on startup).
|
||||
#
|
||||
# After rotating a password in secrets.yaml, re-run with
|
||||
# `systemctl restart gebos-postgres-passwords` (deploys re-run it only
|
||||
# when the unit definition itself changed).
|
||||
systemd.services.gebos-postgres-passwords =
|
||||
lib.mkIf config.services.gebos.secrets.postgresAdmin {
|
||||
description = "Gebos — sync Postgres role passwords from sops secrets";
|
||||
after = [ "postgresql.service" ];
|
||||
requires = [ "postgresql.service" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
path = [ config.services.postgresql.package ];
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
User = "postgres";
|
||||
# LoadCredential hands the root-owned 0400 env file to the postgres
|
||||
# user without widening its permissions.
|
||||
LoadCredential = "gebos-env:${config.services.gebos.secrets.envFile}";
|
||||
};
|
||||
script = ''
|
||||
set -euo pipefail
|
||||
set -a; . "$CREDENTIALS_DIRECTORY/gebos-env"; set +a
|
||||
|
||||
# All three come from the gebos-env template; the toggles in
|
||||
# db-host.nix (postgresAdmin + supabase + ingester) must be on.
|
||||
# --output=/dev/null: the set_config() SELECTs would otherwise echo
|
||||
# the passwords into the journal.
|
||||
psql -v ON_ERROR_STOP=1 --output=/dev/null \
|
||||
-v admin_pw="''${POSTGRES_ADMIN_PASSWORD:?}" \
|
||||
-v supabase_pw="''${POSTGRES_PASSWORD:?}" \
|
||||
-v ingest_pw="''${GEBOS_POSTGRES_PASSWORD:?}" \
|
||||
--dbname postgres <<'SQL'
|
||||
-- psql :'var' interpolation does not reach inside DO bodies, so
|
||||
-- stash the values in session GUCs first.
|
||||
SELECT set_config('gebos.supabase_pw', :'supabase_pw', false);
|
||||
SELECT set_config('gebos.ingest_pw', :'ingest_pw', false);
|
||||
|
||||
ALTER ROLE postgres PASSWORD :'admin_pw';
|
||||
|
||||
DO $do$
|
||||
DECLARE r text;
|
||||
BEGIN
|
||||
-- One shared password for the roles the compose services log in
|
||||
-- as, mirroring upstream's POSTGRES_PASSWORD convention.
|
||||
FOREACH r IN ARRAY ARRAY['supabase_admin','supabase_auth_admin','authenticator'] LOOP
|
||||
IF EXISTS (SELECT FROM pg_roles WHERE rolname = r) THEN
|
||||
EXECUTE format('ALTER ROLE %I PASSWORD %L',
|
||||
r, current_setting('gebos.supabase_pw'));
|
||||
END IF;
|
||||
END LOOP;
|
||||
|
||||
IF EXISTS (SELECT FROM pg_roles WHERE rolname = 'supabase_admin') THEN
|
||||
EXECUTE 'ALTER ROLE supabase_admin SUPERUSER';
|
||||
END IF;
|
||||
|
||||
IF EXISTS (SELECT FROM pg_roles WHERE rolname = 'supabase_auth_admin')
|
||||
AND EXISTS (SELECT FROM pg_namespace WHERE nspname = 'auth') THEN
|
||||
EXECUTE 'ALTER SCHEMA auth OWNER TO supabase_auth_admin';
|
||||
END IF;
|
||||
|
||||
-- Created by the schema-v1 migration, so tolerate its absence on
|
||||
-- a cluster the migration has not reached yet.
|
||||
IF EXISTS (SELECT FROM pg_roles WHERE rolname = 'gebos_ingest') THEN
|
||||
EXECUTE format('ALTER ROLE gebos_ingest PASSWORD %L',
|
||||
current_setting('gebos.ingest_pw'));
|
||||
END IF;
|
||||
END
|
||||
$do$;
|
||||
SQL
|
||||
'';
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
@@ -30,10 +30,16 @@ in
|
||||
"d /var/lib/gebos-supabase 0750 root root - -"
|
||||
];
|
||||
|
||||
# Copy the vendored compose file into place at activation time so changes
|
||||
# to nix/supabase/docker-compose.yml are deployed atomically.
|
||||
# Copy the vendored compose bundle into place at activation time so
|
||||
# changes under nix/supabase/ are deployed atomically. Kong's declarative
|
||||
# config is mounted from here by the compose file (relative ./kong.yml
|
||||
# resolves against WorkingDirectory).
|
||||
environment.etc."gebos/supabase/docker-compose.yml".source =
|
||||
"${composeDir}/docker-compose.yml";
|
||||
environment.etc."gebos/supabase/kong.yml".source =
|
||||
"${composeDir}/kong.yml";
|
||||
environment.etc."gebos/supabase/kong-entrypoint.sh".source =
|
||||
"${composeDir}/kong-entrypoint.sh";
|
||||
|
||||
systemd.services.gebos-supabase = {
|
||||
description = "Gebos — Supabase compose stack";
|
||||
@@ -41,6 +47,14 @@ in
|
||||
wants = [ "docker.service" "network-online.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
|
||||
# `docker compose up -d` only recreates containers whose definition
|
||||
# changed, but the unit must re-run for it to notice at all.
|
||||
restartTriggers = [
|
||||
"${composeDir}/docker-compose.yml"
|
||||
"${composeDir}/kong.yml"
|
||||
"${composeDir}/kong-entrypoint.sh"
|
||||
];
|
||||
|
||||
environment = {
|
||||
POSTGRES_HOST = cfg.postgresHost;
|
||||
POSTGRES_PORT = toString cfg.postgresPort;
|
||||
|
||||
Reference in New Issue
Block a user