Files
Nexus/scripts/seed_local_admin.py
T
2026-07-08 22:31:31 +08:00

76 lines
2.1 KiB
Python

#!/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()))