Files
LoreMind/brain/app/main.py
IETM_FIXE\ietm6 997aadf5b5
All checks were successful
Build & Push Images / build (brain) (push) Successful in 1m21s
Build & Push Images / build (web) (push) Successful in 1m49s
Build & Push Images / build (core) (push) Successful in 3m22s
Build & Push Images / build-switcher (push) Successful in 19s
Mise en place de la distribution en .appimage pour les distro linux
2026-06-20 11:04:15 +02:00

92 lines
3.4 KiB
Python

"""Point d'entrée FastAPI du Brain LoreMind : assemblage de l'application.
Responsabilité UNIQUE : créer l'app, brancher le middleware d'auth inter-service,
le hook de démarrage et les routers (un par responsabilité, voir `app.api.routers`).
Toute la logique HTTP vit dans les routers ; la logique métier dans `app.application`.
"""
import asyncio
import hmac
import logging
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
from app.api.routers import (
chat,
generation,
imports,
models,
notebooks,
settings as settings_router,
tables,
)
from app.core.config import get_settings
from app.infrastructure.ollama_model_installer import ensure_ollama_embedding_model
app = FastAPI(
title="LoreMind Brain",
description="Backend IA pour la génération de contenu narratif.",
version="0.17.1-beta",
)
logger = logging.getLogger(__name__)
# Chemins exemptes d'auth inter-service : healthcheck docker + introspection
# FastAPI (docs uniquement utiles en dev ; en prod docker-compose, le Brain
# n'est pas expose en dehors du reseau interne donc pas un risque).
_PUBLIC_PATHS = frozenset({"/health", "/docs", "/redoc", "/openapi.json"})
@app.middleware("http")
async def require_internal_secret(request: Request, call_next):
"""Refuse toute requete qui ne presente pas le secret partage core<->brain.
Fail-closed : si `INTERNAL_SHARED_SECRET` n'est pas configure cote Brain,
TOUTES les requetes non-publiques sont rejetees. Force la configuration
explicite en prod et empeche un deploiement par defaut non-authentifie.
Comparaison en temps-constant via `hmac.compare_digest` pour eviter les
attaques par timing side-channel sur la validation du secret.
"""
if request.url.path in _PUBLIC_PATHS:
return await call_next(request)
expected = get_settings().internal_shared_secret
provided = request.headers.get("x-internal-secret", "")
if not expected or not hmac.compare_digest(expected, provided):
return JSONResponse(
{"detail": "Unauthorized: invalid or missing X-Internal-Secret"},
status_code=401,
)
return await call_next(request)
@app.get("/health")
def health() -> dict[str, str]:
"""Sonde de santé — permet au Core Java de vérifier que le Brain répond."""
return {"status": "ok", "service": "brain"}
@app.on_event("startup")
async def _auto_install_embedding_model() -> None:
"""Au démarrage : si le provider d'embeddings est Ollama et que le modèle n'est
pas installé, on le télécharge EN ARRIÈRE-PLAN → le RAG marche d'emblée pour un
nouvel utilisateur, sans bloquer le démarrage du Brain. Best-effort (Ollama peut
être absent / la connexion limitée) ; désactivable via `auto_pull_embedding_model`.
"""
settings = get_settings()
if not settings.auto_pull_embedding_model or settings.embedding_provider != "ollama":
return
asyncio.create_task(ensure_ollama_embedding_model(
settings.ollama_base_url, settings.ollama_embedding_model))
# Un router par responsabilité (SRP) — chemins identiques à l'ancien monolithe.
app.include_router(generation.router)
app.include_router(chat.router)
app.include_router(tables.router)
app.include_router(imports.router)
app.include_router(notebooks.router)
app.include_router(settings_router.router)
app.include_router(models.router)