# Database migrations Plain-SQL migrations in `migrations/`, applied **manually** with the supabase CLI (available in the dev shell). There is deliberately no automated runner: schema changes are rare, deliberate, admin-level operations. The CLI records what has been applied in `supabase_migrations.schema_migrations` on the target database, so pushing is incremental and re-running it is safe. ## Applying to prod (db-host) Postgres on db-host only accepts connections from the private network, so go through an SSH tunnel: ```sh # terminal 1 — tunnel to db-host's loopback ssh -L 15432:127.0.0.1:5432 deploy@db.gebos.online # terminal 2 — from the repo root, inside `nix develop` PGPASS=$(sops -d --extract '["postgres_admin_password"]' nix/secrets/secrets.yaml) supabase db push --db-url "postgres://postgres:${PGPASS}@127.0.0.1:15432/postgres" ``` The `postgres` superuser password is set from the same sops secret by the `gebos-postgres-passwords` oneshot on db-host (see `nix/modules/gebos-postgres.nix`), so it only works after the first deploy of that unit. ## Adding a migration ```sh supabase migration new # creates migrations/_.sql ``` Write plain SQL. Conventions: * Migrations are ordered and applied exactly once — they do NOT need to be idempotent (the schema-v1 file's `CREATE TYPE`s aren't). * Cluster bootstrap (Supabase schemas/roles, extensions that need `shared_preload_libraries`) belongs in `nix/supabase/init.sql`, not here. * Role passwords never go in migrations — they are synced from sops by the `gebos-postgres-passwords` unit. ## Local dev `nix run .#dev` starts a plain Postgres via process-compose; apply migrations to it the same way: ```sh supabase db push --db-url "postgres://gebos_ingest:dev@127.0.0.1:5432/gebos" ``` (Note: the dev database has no TimescaleDB unless the process-compose postgres gains the extension — `create_hypertable` calls will fail there until then.)