"""First-run install wizard. `GET /api/setup/status` is public (the SPA uses it to decide whether to show the wizard). Every mutating step goes through `require_setup`: it rejects once the instance is configured AND requires the one-time setup token (printed to the container logs at first boot, carried by the wizard as the X-Setup-Token header). The wizard steps themselves are added in epic 6c. """ from fastapi import APIRouter, Depends, Header, HTTPException from sqlalchemy.orm import Session from app import state from app.db import get_db router = APIRouter(prefix="/api/setup", tags=["setup"]) def require_setup( x_setup_token: str | None = Header(default=None), db: Session = Depends(get_db), ) -> Session: """Gate for setup steps. Returns the db session for the handler to reuse.""" if state.is_configured(db): raise HTTPException(status_code=409, detail="This instance is already set up.") if not state.check_setup_token(db, x_setup_token): raise HTTPException(status_code=403, detail="Invalid or missing setup token.") return db @router.get("/status") def setup_status(db: Session = Depends(get_db)) -> dict: """Public: whether the instance still needs first-run setup.""" return {"configured": state.is_configured(db)}