From c45bf543c06d3a8afdf851512a15dd3c17453a50 Mon Sep 17 00:00:00 2001 From: Davide Grilli Date: Tue, 21 Jul 2026 10:26:42 +0200 Subject: [PATCH] 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 --- app/main.py | 63 ++++++++++++++++++++++++++++++++++ scripts/generate_master_key.py | 16 +++++++++ 2 files changed, 79 insertions(+) create mode 100644 app/main.py create mode 100644 scripts/generate_master_key.py diff --git a/app/main.py b/app/main.py new file mode 100644 index 0000000..c36cd28 --- /dev/null +++ b/app/main.py @@ -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"} diff --git a/scripts/generate_master_key.py b/scripts/generate_master_key.py new file mode 100644 index 0000000..4b9e3f1 --- /dev/null +++ b/scripts/generate_master_key.py @@ -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}")