#!/usr/bin/env bash # Apply migration 007 (user_roles.admin_from_email_policy) to an EXISTING Postgres. # initdb scripts in docker-entrypoint-initdb.d run only on first volume creation. # # Default (full SQL file): adds column, runs one-time policy DELETE/UPDATE (see # be0/migrations/007_user_roles_email_policy_admin.sql before running on prod). # # Usage (from anywhere): # ./be0/scripts/apply-migration-007.sh # ./be0/scripts/apply-migration-007.sh --schema-only # only ADD COLUMN (safest repeat) # # On a remote host (SSH to be0/docker host, repo or copy of migrations present): # export POSTGRES_CONTAINER=initiative-postgres POSTGRES_USER=initiative POSTGRES_DB=initiatives # ./be0/scripts/apply-migration-007.sh # # From repo root (wrapper): # ./scripts/apply-migration-007-postgres.sh set -euo pipefail SCHEMA_ONLY=0 for arg in "$@"; do case "$arg" in --schema-only) SCHEMA_ONLY=1 ;; -h|--help) sed -n '2,20p' "$0" exit 0 ;; esac done BE0_ROOT="$(cd "$(dirname "$0")/.." && pwd)" SQL_FULL="$BE0_ROOT/migrations/007_user_roles_email_policy_admin.sql" CONTAINER="${POSTGRES_CONTAINER:-initiative-postgres}" PGUSER="${POSTGRES_USER:-initiative}" PGDATABASE="${POSTGRES_DB:-initiatives}" if ! docker info >/dev/null 2>&1; then echo "error: Docker is not reachable (is the daemon running?)" >&2 exit 1 fi if ! docker inspect "$CONTAINER" >/dev/null 2>&1; then echo "error: container not found: $CONTAINER (set POSTGRES_CONTAINER)" >&2 exit 1 fi if [[ "$(docker inspect -f '{{.State.Running}}' "$CONTAINER" 2>/dev/null || echo false)" != "true" ]]; then echo "error: container is not running: $CONTAINER" >&2 exit 1 fi apply_schema_only() { docker exec -i "$CONTAINER" psql -U "$PGUSER" -d "$PGDATABASE" -v ON_ERROR_STOP=1 <<'SQL' ALTER TABLE user_roles ADD COLUMN IF NOT EXISTS admin_from_email_policy BOOLEAN NOT NULL DEFAULT FALSE; COMMENT ON COLUMN user_roles.admin_from_email_policy IS 'TRUE when admin was granted by email allow-list (AUTH_ADMIN_EMAILS). Reconciliation may DELETE this row if the user email is no longer in the list. FALSE preserves manually granted admin (future / exceptional).'; SQL } apply_full() { if [[ ! -f "$SQL_FULL" ]]; then echo "error: missing migration file: $SQL_FULL" >&2 exit 1 fi docker exec -i "$CONTAINER" psql -U "$PGUSER" -d "$PGDATABASE" -v ON_ERROR_STOP=1 <"$SQL_FULL" } verify_column() { local out out="$(docker exec "$CONTAINER" psql -U "$PGUSER" -d "$PGDATABASE" -tAc \ "SELECT 1 FROM information_schema.columns WHERE table_schema = 'public' AND table_name = 'user_roles' AND column_name = 'admin_from_email_policy'")" if [[ "${out//$'\r'/}" != "1" ]]; then echo "error: verification failed: column admin_from_email_policy missing on public.user_roles" >&2 exit 1 fi } if (( SCHEMA_ONLY )); then echo "Applying schema only (ADD COLUMN + COMMENT) → $CONTAINER / $PGDATABASE" apply_schema_only else echo "Applying full 007_user_roles_email_policy_admin.sql → $CONTAINER / $PGDATABASE" apply_full fi verify_column echo "ok: user_roles.admin_from_email_policy is present; admin register/login should work with current be0."