Wire up the FastAPI app and master-key bootstrap script

app/main.py assembles the lifespan-managed background tasks (Electrum
listener, round scheduler, confirmation poller, RBF bumper) and mounts
all routers behind a /health check. generate_master_key.py is the
one-time ops script that creates and Fernet-encrypts the server's
master xprv before first launch.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-21 10:26:42 +02:00
co-authored by Claude Sonnet 5
parent 01331c1e4c
commit c45bf543c0
2 changed files with 79 additions and 0 deletions
+63
View File
@@ -0,0 +1,63 @@
import asyncio
from contextlib import asynccontextmanager
from fastapi import FastAPI
import app.bets.confirmation # noqa: F401 (registers the "bet" confirmation handler)
import app.rounds.confirmation # noqa: F401 (registers the "payout" confirmation handler)
import app.withdrawals.confirmation # noqa: F401 (registers the "withdrawal" confirmation handler)
from app.api.routes.admin import router as admin_router
from app.api.routes.bets import router as bets_router
from app.api.routes.users import router as users_router
from app.api.routes.withdrawals import router as withdrawals_router
from app.auth.routes import router as auth_router
from app.config import settings
from app.db.base import AsyncSessionLocal
from app.electrum.client import ElectrumClient
from app.electrum.listener import ElectrumListener
from app.rounds.scheduler import RoundScheduler
from app.tx.broadcast import RbfBumper
from app.tx.confirmation import ConfirmationPoller
from app.tx.locks import UserLocks
def _make_electrum_client() -> ElectrumClient:
return ElectrumClient(settings.electrum_host, settings.electrum_port, settings.electrum_use_ssl)
@asynccontextmanager
async def lifespan(app: FastAPI):
listener = ElectrumListener(_make_electrum_client, AsyncSessionLocal)
app.state.electrum_listener = listener
app.state.user_locks = UserLocks()
scheduler = RoundScheduler(AsyncSessionLocal, listener)
poller = ConfirmationPoller(AsyncSessionLocal, lambda: listener.client)
bumper = RbfBumper(AsyncSessionLocal, lambda: listener.client)
tasks = [
asyncio.create_task(listener.run()),
asyncio.create_task(scheduler.run()),
asyncio.create_task(poller.run()),
asyncio.create_task(bumper.run()),
]
try:
yield
finally:
for task in tasks:
task.cancel()
if listener.client is not None:
await listener.client.close()
app = FastAPI(title="PLM Lottery", lifespan=lifespan)
app.include_router(auth_router)
app.include_router(users_router)
app.include_router(bets_router)
app.include_router(withdrawals_router)
app.include_router(admin_router)
@app.get("/health")
async def health() -> dict[str, str]:
return {"status": "ok"}
+16
View File
@@ -0,0 +1,16 @@
"""One-time ops bootstrap: generate and encrypt the server's master xprv.
Usage:
python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"
# put that value in XPRV_ENCRYPTION_KEY (.env), then:
python scripts/generate_master_key.py
"""
from app.config import settings
from app.wallet.hd import generate_master_key
if __name__ == "__main__":
if not settings.xprv_encryption_key:
raise SystemExit("XPRV_ENCRYPTION_KEY is not set")
generate_master_key()
print(f"Master key written (encrypted) to {settings.master_key_path}")