""" run_migrations.py — Idempotent SQL migration runner for Docker init service. Executes each .sql migration file in order using psycopg2. Tolerates re-run errors (e.g. "policy already exists") so deploys are safe. """ import os import sys import psycopg2 DATABASE_URL = os.environ["DATABASE_URL"] MIGRATIONS = [ "02_tracksolid_full_schema_rev.sql", "03_webhook_schema_migration.sql", ] def run_file(conn, path, filename): """Execute a SQL file. Returns True on success, False on error.""" with open(path) as f: sql = f.read() try: with conn.cursor() as cur: cur.execute(sql) print(f" OK: {filename}") return True except psycopg2.Error as e: msg = (e.pgerror or str(e)).strip().split("\n")[0] print(f" WARN: {filename}: {msg}") # Connection is now in error state — must reset conn.close() return False def main(): print("=== Database Migration Runner ===") conn = psycopg2.connect(DATABASE_URL) conn.autocommit = True warnings = 0 for sql_file in MIGRATIONS: path = os.path.join("/app", sql_file) if not os.path.exists(path): print(f" SKIP: {sql_file} (not found)") continue print(f"Running {sql_file}...") ok = run_file(conn, path, sql_file) if not ok: warnings += 1 # Reconnect for the next file conn = psycopg2.connect(DATABASE_URL) conn.autocommit = True conn.close() if warnings: print(f"Completed with {warnings} warning(s) (expected on re-deploy).") else: print("All migrations applied cleanly.") sys.exit(0) if __name__ == "__main__": main()