"""laptop-worker-bot 계정 생성 / 비밀번호 갱신 스크립트 (PR-Worker-Pool-Registry-1B) 사용법: docker compose exec fastapi env \\ LAPTOP_WORKER_BOT_USERNAME=laptop-worker-bot \\ LAPTOP_WORKER_BOT_PASSWORD='' \\ python /app/scripts/seed_laptop_worker_bot.py env 우선순위: - LAPTOP_WORKER_BOT_USERNAME (default 'laptop-worker-bot') - LAPTOP_WORKER_BOT_PASSWORD (required, prompt fallback) worker-pool-policy §9 (Token 발급 path) Sec-1H 패턴 동형. password_changed_at 갱신 시 기존 JWT 자동 무효화 (Sec-1H mig 269). """ import asyncio import getpass import os import sys sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "app")) from sqlalchemy import text from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine from core.auth import hash_password async def seed_laptop_worker_bot(): database_url = os.getenv( "DATABASE_URL", "postgresql+asyncpg://pkm:pkm@localhost:5432/pkm", ) username = os.getenv("LAPTOP_WORKER_BOT_USERNAME", "laptop-worker-bot").strip() password = os.getenv("LAPTOP_WORKER_BOT_PASSWORD", "") if not password: password = getpass.getpass(f"'{username}' 비밀번호: ") if not password: print("비밀번호가 비어 있습니다.", file=sys.stderr) sys.exit(1) password_hash = hash_password(password) engine = create_async_engine(database_url) async_session = async_sessionmaker(engine, class_=AsyncSession, expire_on_commit=False) async with async_session() as session: existing = await session.execute( text("SELECT id FROM users WHERE username = :username"), {"username": username}, ) row = existing.first() if row is not None: await session.execute( text( "UPDATE users SET password_hash = :hash, is_active = TRUE, " "password_changed_at = NOW() WHERE username = :username" ), {"hash": password_hash, "username": username}, ) print(f"'{username}' 계정 비밀번호 갱신 + password_changed_at 동기화.") else: await session.execute( text( "INSERT INTO users (username, password_hash, is_active, password_changed_at) " "VALUES (:username, :hash, TRUE, NOW())" ), {"username": username, "hash": password_hash}, ) print(f"'{username}' 계정 신규 생성.") await session.commit() await engine.dispose() if __name__ == "__main__": asyncio.run(seed_laptop_worker_bot())