""" Factory pour créer l'application FastAPI. Ce module contient la fonction create_app() qui configure et retourne une instance FastAPI prête à l'emploi. """ from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware from fastapi.staticfiles import StaticFiles from fastapi.responses import HTMLResponse from app.core.config import settings from app.models.database import init_db, async_session_maker from app.routes import api_router from app.routes.websocket import router as ws_router def create_app() -> FastAPI: """Crée et configure l'application FastAPI. Returns: Instance FastAPI configurée avec tous les routers et middleware """ app = FastAPI( title=settings.api_title, description=settings.api_description, version=settings.api_version, docs_url="/docs", redoc_url="/redoc", ) # Configuration CORS app.add_middleware( CORSMiddleware, allow_origins=settings.cors_origins, allow_credentials=settings.cors_allow_credentials, allow_methods=settings.cors_allow_methods, allow_headers=settings.cors_allow_headers, ) # Monter les fichiers statiques (main.js, etc.) app.mount("/static", StaticFiles(directory=settings.base_dir, html=False), name="static") # Inclure les routers API app.include_router(api_router, prefix="/api") # Inclure le router WebSocket (sans préfixe /api) app.include_router(ws_router) # Routes racine @app.get("/", response_class=HTMLResponse) async def root(): """Page d'accueil - redirige vers le dashboard.""" # Essayer de servir index.html index_path = settings.base_dir / "index.html" if index_path.exists(): return index_path.read_text(encoding='utf-8') return """
Redirecting to API Documentation...
""" @app.get("/api", response_class=HTMLResponse) async def api_home(): """Page d'accueil de l'API.""" return """API REST pour la gestion automatisée de votre homelab avec Ansible.
""" # Événements de démarrage et d'arrêt @app.on_event("startup") async def startup_event(): """Événement de démarrage de l'application.""" # Démarrer la capture des logs console en premier from app.services import console_log_service console_log_service.start_capture() print("🚀 Homelab Automation Dashboard démarré") # Afficher les paramètres d'environnement print("\n📋 Configuration:") print(f" BASE_DIR: {settings.base_dir}") print(f" ANSIBLE_DIR: {settings.ansible_dir}") print(f" TASKS_LOGS_DIR: {settings.tasks_logs_dir}") print(f" DATABASE_URL: {settings.async_database_url}") print(f" SSH_KEY_PATH: {settings.ssh_key_path}") print(f" SSH_USER: {settings.ssh_user}") print(f" SSH_REMOTE_USER: {settings.ssh_remote_user}") print(f" API_KEY: {'*' * 8}...{settings.api_key[-4:] if len(settings.api_key) > 4 else '****'}") print(f" NTFY_ENABLED: {settings.ntfy_enabled}") print(f" NTFY_BASE_URL: {settings.ntfy_base_url}") print(f" NTFY_TOPIC: {settings.ntfy_default_topic}") print() # Validation des chemins critiques validation_ok = True if not settings.ansible_dir.exists(): print(f"⚠️ ANSIBLE_DIR n'existe pas: {settings.ansible_dir}") validation_ok = False else: print(f"✅ ANSIBLE_DIR OK: {settings.ansible_dir}") inventory_path = settings.ansible_dir / "inventory" / "hosts.yml" if not inventory_path.exists(): print(f"⚠️ Inventaire Ansible non trouvé: {inventory_path}") else: print(f"✅ Inventaire Ansible OK: {inventory_path}") playbooks_dir = settings.ansible_dir / "playbooks" if not playbooks_dir.exists(): print(f"⚠️ Dossier playbooks non trouvé: {playbooks_dir}") else: playbook_count = len(list(playbooks_dir.glob("*.yml"))) print(f"✅ Dossier playbooks OK: {playbook_count} playbook(s) trouvé(s)") print() # Initialiser la base de données await init_db() print("📦 Base de données SQLite initialisée") # Charger les services from app.services import ( bootstrap_status_service, scheduler_service, notification_service, ) from app.crud.log import LogRepository # Charger les statuts bootstrap depuis la BD await bootstrap_status_service.load_from_db() # Démarrer le scheduler await scheduler_service.start_async() # Ajouter les jobs Docker au scheduler from app.services.docker_service import docker_service from app.services.docker_alerts import docker_alerts_service scheduler_service.scheduler.add_job( docker_service.collect_all_hosts, trigger="interval", seconds=60, # Toutes les minutes id="docker_collect", name="Docker Metrics Collection", replace_existing=True ) scheduler_service.scheduler.add_job( docker_alerts_service.check_all_alerts, trigger="interval", seconds=30, # Toutes les 30 secondes id="docker_alerts", name="Docker Alerts Check", replace_existing=True ) print("🐳 Docker monitoring jobs scheduled") # Afficher l'état du service de notification ntfy_status = "activé" if notification_service.enabled else "désactivé" print(f"🔔 Service de notification ntfy: {ntfy_status} ({notification_service.config.base_url})") # Log de démarrage en base async with async_session_maker() as session: repo = LogRepository(session) await repo.create( level="INFO", message="Application démarrée - Services initialisés (BD)", source="system", ) await session.commit() # Notification ntfy au démarrage startup_notif = notification_service.templates.app_started() await notification_service.send( message=startup_notif.message, topic=startup_notif.topic, title=startup_notif.title, priority=startup_notif.priority, tags=startup_notif.tags, ) @app.on_event("shutdown") async def shutdown_event(): """Événement d'arrêt de l'application.""" print("👋 Arrêt de l'application...") from app.services import scheduler_service, notification_service # Arrêter la capture des logs console from app.services import console_log_service console_log_service.stop_capture() # Arrêter le scheduler scheduler_service.shutdown() # Notification ntfy à l'arrêt shutdown_notif = notification_service.templates.app_stopped() await notification_service.send( message=shutdown_notif.message, topic=shutdown_notif.topic, title=shutdown_notif.title, priority=shutdown_notif.priority, tags=shutdown_notif.tags, ) # Fermer le client HTTP await notification_service.close() print("✅ Services arrêtés proprement") return app