#!/bin/bash # ============================================ # Armature - Database Migration Runner # ============================================ # NOTE: The Go backend auto-migrates on startup. # This script is for manual/emergency use only. # Canonical migration files: server/database/migrations/ # ============================================ # Tracks applied migrations in schema_migrations. # In dev (DB_WIPE=true): drops all tables, re-runs # all migrations for a clean slate every PR build. # # Required env vars: # PGHOST, PGPORT, PGUSER, PGPASSWORD — app-level connection # DB_NAME — target database # DB_WIPE — "true" to wipe first (dev only) # ============================================ set -euo pipefail MIGRATIONS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../server/database/migrations" && pwd)" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "Migration runner: ${DB_NAME}" echo " Wipe mode: ${DB_WIPE}" echo " Migrations: ${MIGRATIONS_DIR}" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" export PGDATABASE="${DB_NAME}" # ── Dev wipe: drop everything and start fresh ─ if [[ "${DB_WIPE}" == "true" ]]; then echo "⚠ Dev wipe: dropping all objects..." psql <<'SQL' DO $$ DECLARE r RECORD; BEGIN -- Drop all views FOR r IN (SELECT viewname FROM pg_views WHERE schemaname = 'public') LOOP EXECUTE 'DROP VIEW IF EXISTS public.' || quote_ident(r.viewname) || ' CASCADE'; END LOOP; -- Drop all tables (including schema_migrations for clean slate) FOR r IN (SELECT tablename FROM pg_tables WHERE schemaname = 'public') LOOP EXECUTE 'DROP TABLE IF EXISTS public.' || quote_ident(r.tablename) || ' CASCADE'; END LOOP; -- Drop all functions (skip extension-owned like uuid_nil etc) FOR r IN (SELECT ns.nspname || '.' || p.proname || '(' || pg_get_function_identity_arguments(p.oid) || ')' AS func FROM pg_proc p JOIN pg_namespace ns ON p.pronamespace = ns.oid WHERE ns.nspname = 'public' AND NOT EXISTS ( SELECT 1 FROM pg_depend d WHERE d.objid = p.oid AND d.deptype = 'e' )) LOOP EXECUTE 'DROP FUNCTION IF EXISTS ' || r.func || ' CASCADE'; END LOOP; END $$; SQL echo "✓ All public schema objects dropped" fi # ── Ensure tracking table exists ────────────── psql <<'SQL' CREATE TABLE IF NOT EXISTS schema_migrations ( version VARCHAR(255) PRIMARY KEY, applied_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() ); SQL # ── Apply pending migrations ────────────────── APPLIED=0 SKIPPED=0 for migration in "${MIGRATIONS_DIR}"/*.sql; do [[ -f "${migration}" ]] || continue VERSION=$(basename "${migration}") ALREADY=$(psql -tAc "SELECT 1 FROM schema_migrations WHERE version = '${VERSION}';") if [[ "${ALREADY}" == "1" ]]; then echo " skip: ${VERSION} (already applied)" SKIPPED=$((SKIPPED + 1)) continue fi echo " apply: ${VERSION}..." psql -v ON_ERROR_STOP=1 -f "${migration}" psql -c "INSERT INTO schema_migrations (version) VALUES ('${VERSION}');" APPLIED=$((APPLIED + 1)) echo " ✓ ${VERSION} applied" done echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "Migrations complete: ${APPLIED} applied, ${SKIPPED} skipped" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"