#!/usr/bin/env python3 """Create initial admin on empty local DB (127.0.0.1 only). Usage: NEXUS_TEST_ADMIN_PASSWORD='your-pass' python3 scripts/seed_local_admin.py # or set NEXUS_TEST_ADMIN_PASSWORD in .env first Refuses non-local hosts and skips if any admin already exists. """ from __future__ import annotations import asyncio import os import sys from pathlib import Path import bcrypt from sqlalchemy import text ROOT = Path(__file__).resolve().parent.parent if str(ROOT) not in sys.path: sys.path.insert(0, str(ROOT)) from server.config import settings from server.infrastructure.database.session import init_db, AsyncSessionLocal def _local_db_only() -> None: url = settings.DATABASE_URL if "127.0.0.1" not in url and "localhost" not in url: raise SystemExit(f"Refusing seed on non-local DATABASE_URL: {url!r}") def _password() -> str: pwd = os.environ.get("NEXUS_TEST_ADMIN_PASSWORD", "").strip() if not pwd: raise SystemExit( "Set NEXUS_TEST_ADMIN_PASSWORD in .env or environment (min 6 chars)." ) if len(pwd) < 6: raise SystemExit("Password must be at least 6 characters.") return pwd async def main() -> int: _local_db_only() username = os.environ.get("NEXUS_TEST_ADMIN_USER", "admin").strip() or "admin" password = _password() await init_db() async with AsyncSessionLocal() as session: count = (await session.execute(text("SELECT COUNT(*) FROM admins"))).scalar() or 0 if count > 0: print(f"SKIP: {count} admin(s) already exist") return 0 password_hash = bcrypt.hashpw(password.encode("utf-8"), bcrypt.gensalt()).decode( "utf-8" ) await session.execute( text( "INSERT INTO admins (username, password_hash, email, is_active, totp_enabled) " "VALUES (:u, :h, NULL, 1, 0)" ), {"u": username, "h": password_hash}, ) await session.commit() print(f"OK: created admin '{username}' on local database") return 0 if __name__ == "__main__": raise SystemExit(asyncio.run(main()))