"""Idempotent admin-user seed script. Runs at container start (after Alembic migrations) to ensure the dev-bypass admin email exists as an AppUser with role=admin. If the user already exists with a lower role, this script promotes them. Reads the same DEV_AUTH_* env vars the auth middleware uses, so they stay in sync. Pure-sync (psycopg2) — no async stack at boot. """ from __future__ import annotations import logging import os import sys # Make sure /app is on the path when this is invoked from start.sh. sys.path.insert(0, "/app") from sqlalchemy import create_engine, text # noqa: E402 logger = logging.getLogger(__name__) logging.basicConfig(level=logging.INFO, format="%(levelname)s [%(name)s] %(message)s") def main() -> None: bypass = os.environ.get("DEV_AUTH_BYPASS", "").lower() in ("1", "true", "yes") if not bypass: logger.info("DEV_AUTH_BYPASS not set — skipping admin seed (real SSO will provision users on login)") return email = (os.environ.get("DEV_AUTH_EMAIL") or "admin@oliver.agency").strip().lower() name = os.environ.get("DEV_AUTH_NAME") or "OSOP Admin" role = (os.environ.get("DEV_AUTH_ROLE") or "admin").strip().lower() if role not in {"viewer", "editor", "admin"}: logger.warning("Unknown DEV_AUTH_ROLE=%s — falling back to admin", role) role = "admin" sync_url = os.environ.get("DATABASE_URL_SYNC") if not sync_url: logger.error("DATABASE_URL_SYNC not set — cannot seed admin") return engine = create_engine(sync_url) try: with engine.begin() as conn: existing = conn.execute( text("SELECT id, role FROM app_users WHERE email = :email"), {"email": email}, ).fetchone() if existing is None: conn.execute( text( "INSERT INTO app_users (email, name, role, last_login) " "VALUES (:email, :name, CAST(:role AS userrole), NOW())" ), {"email": email, "name": name, "role": role.upper()}, ) logger.info("Seeded AppUser %s (role=%s)", email, role) elif existing.role.lower() != role and role == "admin": conn.execute( text("UPDATE app_users SET role = CAST(:role AS userrole) WHERE id = :id"), {"role": role.upper(), "id": existing.id}, ) logger.info("Promoted existing AppUser %s to %s", email, role) else: logger.info("AppUser %s already exists with role=%s — no change", email, existing.role.lower()) finally: engine.dispose() if __name__ == "__main__": main()