feat(worker-pool): Registry-1B Pull 활성화 (auth + worker_jobs + 5 endpoint)
worker-pool-policy §B 1B 영역 완료. 1A scaffold (mig 270~274 + 503 stub) 위에:
- mig 275/276: worker_jobs (status CHECK + user_id=owner) + pending partial index
- create_laptop_worker_bot_token + require_worker_user dependency (voice-memo 동형)
- /internal/worker/{register,heartbeat,claim,result,drain} 5 endpoint 실 구현
- /claim FOR UPDATE SKIP LOCKED + 204 body 0
- /result 소유권 검증 (worker_id 매칭, 404) + failed 재시도 (attempts/max)
- explicit failure 시 request.result 무시 (DB result NULL 유지)
- 테스트 22 항목 7 파일
policy §B.2 5 invariant 보존: voice-memo wrapper 변경 0, drain advisory,
result raw JSONB, ProcessingQueue 무변경, 운영 자동 분기 변경 0.
활용처 (recap context + /jobs/recap + payload 100KB guard) = Registry-1C 영역.
stale recovery / 노트북 client / canonical promote = Notebook-Pilot-1 영역.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
"""laptop-worker-bot 계정 생성 / 비밀번호 갱신 스크립트 (PR-Worker-Pool-Registry-1B)
|
||||
|
||||
사용법:
|
||||
docker compose exec fastapi env \\
|
||||
LAPTOP_WORKER_BOT_USERNAME=laptop-worker-bot \\
|
||||
LAPTOP_WORKER_BOT_PASSWORD='<bw-issued>' \\
|
||||
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())
|
||||
Reference in New Issue
Block a user