"""Brain of Reese — application entrypoint. Boots logging + conditional debugpy, then creates the FastAPI app: API routes first (so they win over the catch-all), and the static frontend mounted last. No CDN: everything the browser needs is served by this process from local files (see PLAN §UI/UX — No External Dependencies). Phase 16 (A10 revised): before anything is served, admin auth must be configured (fail-loud), and the app wraps every route in Starlette's SessionMiddleware — a signed ``bor_session`` cookie is the only session state in the system. """ from __future__ import annotations import logging from pathlib import Path from fastapi import FastAPI from fastapi.staticfiles import StaticFiles from starlette.middleware.sessions import SessionMiddleware from app.api.auth import router as auth_router from app.api.chat import router as chat_router from app.api.docs import router as docs_router from app.api.health import router as health_router from app.api.steering import router as steering_router from app.api.suggestions import router as suggestions_router from app.config import get_settings from app.core.auth import ensure_admin_configured from app.core.debugging import configure_debugging from app.core.logging import configure_logging configure_logging() configure_debugging() settings = get_settings() logger = logging.getLogger("app") def create_app() -> FastAPI: # Fail loud BEFORE serving anything (phase 16): missing # BOR_ADMIN_PASSWORD / BOR_SESSION_SECRET raises at boot, naming the # variable(s) — the app never starts in a half-authenticated state. ensure_admin_configured(settings) app = FastAPI(title=settings.app_name, version=settings.app_version) # Signed single-admin session cookie (Starlette middleware, itsdangerous # signer — no server-side store, no new services). Homelab HTTP: same_site # is "lax" and https_only stays off (documented in the README). app.add_middleware( SessionMiddleware, secret_key=settings.session_secret, session_cookie=settings.session_cookie, max_age=settings.session_max_age, same_site="lax", https_only=False, ) # API routes first so they take precedence over the catch-all static mount. app.include_router(health_router, prefix="/api") app.include_router(auth_router, prefix="/api") app.include_router(suggestions_router, prefix="/api") app.include_router(docs_router, prefix="/api") app.include_router(chat_router, prefix="/api") app.include_router(steering_router, prefix="/api") static_dir = Path(settings.static_dir).resolve() if static_dir.is_dir(): app.mount("/", StaticFiles(directory=static_dir, html=True), name="static") else: logger.warning("static dir %s not found — serving API only", static_dir) return app app = create_app()