homelab_automation/app/factory.py
Bruno Charest 68a9b0f390
Some checks failed
Tests / Backend Tests (Python) (3.10) (push) Has been cancelled
Tests / Backend Tests (Python) (3.11) (push) Has been cancelled
Tests / Backend Tests (Python) (3.12) (push) Has been cancelled
Tests / Frontend Tests (JS) (push) Has been cancelled
Tests / Integration Tests (push) Has been cancelled
Tests / All Tests Passed (push) Has been cancelled
Remove Node.js cache files containing npm vulnerability data for vitest and vite packages
2025-12-15 20:36:06 -05:00

242 lines
8.7 KiB
Python

"""
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 """
<!DOCTYPE html>
<html>
<head>
<title>Homelab Automation</title>
<meta http-equiv="refresh" content="0; url=/docs">
</head>
<body>
<p>Redirecting to <a href="/docs">API Documentation</a>...</p>
</body>
</html>
"""
@app.get("/api", response_class=HTMLResponse)
async def api_home():
"""Page d'accueil de l'API."""
return """
<!DOCTYPE html>
<html>
<head>
<title>Homelab Automation API</title>
<style>
body { font-family: system-ui, sans-serif; max-width: 800px; margin: 50px auto; padding: 20px; }
h1 { color: #1a1a2e; }
.links { margin-top: 30px; }
.links a { display: inline-block; margin-right: 20px; padding: 10px 20px;
background: #7c3aed; color: white; text-decoration: none; border-radius: 5px; }
.links a:hover { background: #6d28d9; }
</style>
</head>
<body>
<h1>🏠 Homelab Automation API</h1>
<p>API REST pour la gestion automatisée de votre homelab avec Ansible.</p>
<div class="links">
<a href="/docs">📚 Documentation Swagger</a>
<a href="/redoc">📖 Documentation ReDoc</a>
<a href="/">🖥️ Dashboard</a>
</div>
</body>
</html>
"""
# É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