homelab_automation/documentation/AUDIT_STRATEGIQUE_COMPLET.md
Bruno Charest 88742892d0
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
refactorisation pour correction de sécurité
2026-03-03 08:29:52 -05:00

38 KiB

🔍 Rapport d'Audit Technique Stratégique — Homelab Automation Dashboard v2.0

Date de l'audit : 20 février 2026
Auditeur : Architecte Logiciel Full-Stack Senior / Spécialiste DevOps / Expert UI/UX
Version analysée : 2.0.0
Stack : FastAPI · SQLAlchemy Async · SQLite (aiosqlite) · APScheduler · Ansible · WebSocket · HTML/JS/Tailwind/Anime.js


Table des Matières

  1. Synthèse Exécutive
  2. Audit d'Architecture et de Sécurité 🛡️
  3. Corrections et Optimisations Code/Performances ⚙️
  4. Améliorations UI/UX 🎨
  5. Idées d'Évolution "Next Level" 🚀
  6. Feuille de Route Actionnable 🗺️

1. Synthèse Exécutive

Vue d'ensemble du Projet

Le Homelab Automation Dashboard est une application self-hosted impressionnante qui centralise la gestion d'infrastructure via Ansible, avec planification de tâches, monitoring Docker via SSH, un terminal SSH intégré via ttyd, et des notifications push via ntfy. L'application a atteint un niveau de maturité fonctionnel solide avec une architecture bien structurée (Factory pattern, Repository pattern, séparation services/routes/schemas/models).

Points Forts

Domaine Appréciation
Architecture modulaire Excellente séparation : routes/, services/, models/, schemas/, crud/, core/
Migrations Alembic 19 migrations bien structurées avec convention de nommage
Exécution Ansible async asyncio.create_subprocess_exec — ne bloque pas l'event loop
Service de notification Design robuste, never-throw, async, avec templates et filtrage par niveau
Gestion des sessions terminal Architecture GC + heartbeat + session reuse bien pensée
Docker monitoring Collection SSH + semaphore concurrency limiter + upsert/stale cleanup
Startup checks Service de vérification des prérequis complet et bien reporté
Exceptions typées Hiérarchie d'exceptions métier bien conçue (HomelabException)

Points d'Attention ⚠️

Domaine Sévérité Résumé
Secrets en .env committé 🔴 Critique .env contient API_KEY, JWT_SECRET_KEY en clair dans le repo
app_optimized.py monolithique 🟠 Haute 6585 lignes — fichier "God Object" qui duplique toute l'architecture
WebSocket sans authentification 🟠 Haute /ws n'exige aucun token/clé API
Bootstrap : mot de passe root en transit 🟠 Haute root_password transmis en clair dans la requête HTTP
threading.Lock dans WebSocket 🟡 Moyenne Devrait être asyncio.Lock pour la cohérence async
CORS allow_origins=["*"] 🟡 Moyenne Présent dans app_optimized.py et en production potentielle
Coverage à 45% 🟡 Moyenne Seuil minimal, certaines zones critiques non couvertes

2. Audit d'Architecture et de Sécurité 🛡️

2.1 Authentification & Gestion des Secrets

JWT — Implémentation Solide avec Réserves

L'implémentation JWT utilise python-jose avec bcrypt pour le hashing des mots de passe. C'est un bon choix.

Points positifs :

  • Hashing bcrypt avec sel aléatoire (bcrypt.gensalt())
  • Token avec exp et iat claims
  • Double mode d'authentification (API Key + JWT Bearer)
  • Setup initial protégé (uniquement si 0 utilisateurs)
  • Changement de mot de passe avec vérification de l'ancien

🔴 Problèmes critiques :

# auth_service.py, ligne 20
SECRET_KEY = os.environ.get("JWT_SECRET_KEY", "homelab-secret-key-change-in-production")
Problème Impact Recommandation
Clé secrète par défaut faible Un attaquant connaissant le code source peut forger des JWT valides Générer une clé de 256 bits minimum au premier démarrage, stocker dans un fichier protégé
Pas de rotation des clés Compromission permanente si la clé est exposée Implémenter JWK (JSON Web Key) avec rotation périodique
Pas de refresh token L'utilisateur doit se reconnecter après expiration Ajouter un système refresh/access token
Pas de révocation de token Impossible de déconnecter un utilisateur compromis Maintenir une blacklist en cache (Redis ou in-memory)

.env Commité dans le Repository

# .env (commité dans git !)
API_KEY=dev-key-1234567890
JWT_SECRET_KEY=dev-key-67890

🔴 CRITIQUE : Le fichier .env est commité et contient des secrets en clair. Même si ce sont des valeurs de développement, cela crée un risque car :

  1. Les développeurs pourraient déployer avec ces valeurs
  2. L'historique Git conserve les secrets même après suppression

Recommandations :

  1. Ajouter .env au .gitignore immédiatement
  2. Conserver uniquement un .env.example avec des valeurs placeholder
  3. Utiliser python-dotenv avec validation obligatoire des secrets au démarrage
  4. Pour la production, envisager HashiCorp Vault ou docker secrets

Gestion des Clés SSH

Points positifs :

  • Recherche intelligente multi-emplacement (find_ssh_private_key)
  • Vérification des permissions sur Linux/Mac
  • Support de multiples types de clés (RSA, Ed25519, ECDSA)

⚠️ Points d'attention :

# ssh_utils.py, ligne 103 & terminal_service.py, ligne 363
"-o", "StrictHostKeyChecking=no",
"-o", "UserKnownHostsFile=/dev/null",

Cela désactive la vérification TOFU (Trust On First Use), ce qui est acceptable pour un homelab mais devrait être configurable.

# docker_service.py, ligne 88
known_hosts=None,  # Accept any host key (homelab environment)

Recommandation : Ajouter un paramètre STRICT_HOST_KEY_CHECKING configurable avec une valeur par défaut no pour le homelab, yes pour la production.

Bootstrap — Mot de Passe Root en Transit

# routes/bootstrap.py, ligne 98
result = bootstrap_host(
    host=request.host,
    root_password=request.root_password,  # ⚠️ En clair dans la requête
    automation_user=request.automation_user
)

Le mot de passe root est transmis dans le corps de la requête HTTP. En l'absence de HTTPS, il circule en clair.

Recommandations :

  1. Exiger HTTPS pour les endpoints sensibles (bootstrap, auth)
  2. Ajouter un avertissement dans les logs si la requête arrive en HTTP
  3. Envisager un chiffrement côté client avec une clé éphémère (Diffie-Hellman)
  4. Limiter le rate-limiting sur /api/bootstrap (anti brute-force)

2.2 Robustesse de l'API FastAPI

Architecture Dual — Le Problème app_optimized.py

L'application maintient deux architectures parallèles :

Fichier Lignes Rôle
app/factory.py + routes modulaires ~10k+ Architecture propre, modulaire
app/app_optimized.py 6585 Monolithe qui duplique tout

🟠 Problème majeur : app_optimized.py est un "God File" de 6585 lignes contenant :

  • Modèles Pydantic (dupliqués de schemas/)
  • Services (dupliqués de services/)
  • Routes (dupliquées de routes/)
  • Gestion de base de données en mémoire et SQLite synchrone
  • Un scheduler complet

Recommandation : Supprimer app_optimized.py et ne conserver que l'architecture modulaire. Ce fichier semble être l'ancienne version monolithique conservée "au cas où" mais il crée de la confusion et un risque de régression.

Gestion de la Base de Données SQLite Async

Excellente configuration :

# models/database.py
engine = create_async_engine(DATABASE_URL, pool_pre_ping=True, future=True)

# Pragmas SQLite bien configurés
cursor.execute("PRAGMA foreign_keys=ON")
cursor.execute("PRAGMA journal_mode=WAL")  # Avec fallback sur DELETE

Session management correct :

async def get_db() -> AsyncGenerator[AsyncSession, None]:
    async with async_session_maker() as session:
        try:
            yield session
        except Exception:
            await session.rollback()
            raise
        finally:
            await session.close()

⚠️ Points d'attention :

  1. Pas de connection pooling avancé : SQLite single-writer peut devenir un bottleneck sous charge élevée. Envisager pool_size et max_overflow quand/si migration vers PostgreSQL.

  2. expire_on_commit=False : C'est le bon choix pour l'async, mais peut causer des données stale si les sessions sont longues. Actuellement OK car les sessions sont scoped aux requêtes.

  3. Migration Alembic dans init_db() : L'exécution de alembic upgrade head à chaque démarrage est robuste mais peut être lente si beaucoup de migrations. Ajouter un check "already at head" avant d'exécuter.

2.3 Communication WebSocket

WebSocket Manager — Simple mais Fonctionnel

# websocket_service.py
class WebSocketManager:
    def __init__(self):
        self.active_connections: List[WebSocket] = []
        self.lock = Lock()  # ⚠️ threading.Lock au lieu de asyncio.Lock

🟡 Problème : threading.Lock en contexte async

L'utilisation de threading.Lock dans la méthode broadcast (qui est async) peut causer un deadlock si jamais le lock est contenu quand l'event loop essaie d'envoyer un message WebSocket qui nécessite un await.

# Actuel (problématique)
async def broadcast(self, message: dict):
    with self.lock:  # ⚠️ Bloque le thread de l'event loop
        for connection in self.active_connections:
            await connection.send_json(message)  # ← await sous un Lock synchrone

La correction devrait être :

async def broadcast(self, message: dict):
    async with self._lock:  # asyncio.Lock
        disconnected = []
        for connection in self.active_connections:
            try:
                await connection.send_json(message)
            except Exception:
                disconnected.append(connection)
        for conn in disconnected:
            self.active_connections.remove(conn)

🔴 WebSocket sans authentification :

# routes/websocket.py, ligne 22-32
@router.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket):
    await ws_manager.connect(websocket)  # ⚠️ Aucune vérification d'identité

N'importe qui peut se connecter au WebSocket et recevoir toutes les notifications système (tâches, bootstrap, statuts). C'est un vecteur de fuite d'information.

Recommandation : Vérifier le JWT via query parameter lors du handshake WebSocket :

@router.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket):
    token = websocket.query_params.get("token")
    if not token or not decode_token(token):
        await websocket.close(code=4001, reason="Authentication required")
        return
    await ws_manager.connect(websocket)

Terminal WebSocket Proxy

Le proxy terminal (/terminal/ws/{session_id}) a une bonne implémentation de sécurité :

  • Token vérifié via hash SHA-256
  • secrets.compare_digest pour la comparaison (timing-safe)
  • Session validée en base de données

⚠️ Problème mineur dans le proxy :

# websocket.py, ligne 104
data = await reader.readexactly(1024)  # ⚠️ Attend exactement 1024 octets

readexactly attend exactement N bytes ou lève IncompleteReadError. Cela peut causer des latences si le terminal envoie moins de 1024 bytes. Utiliser reader.read(4096) à la place :

data = await reader.read(4096)
if not data:
    break

2.4 Planificateur de Tâches (APScheduler)

Configuration solide :

  • coalesce=True : évite les exécutions multiples si le scheduler rattrape son retard
  • max_instances=1 : empêche les exécutions parallèles du même job
  • misfire_grace_time configurable
  • Persistance en base de données avec rechargement au démarrage

⚠️ Risque de "Schedule Orphan" :

Si l'application crash pendant l'exécution d'un schedule, le ScheduleRun restera en statut running indéfiniment. Ajouter une vérification au démarrage pour marquer les runs "orphelins" comme failed.

⚠️ Jobs Docker hardcodés :

# factory.py, lignes 182-198
scheduler_service.scheduler.add_job(
    docker_service.collect_all_hosts,
    trigger="interval",
    seconds=60,  # Toutes les minutes
    id="docker_collect",
)

Les intervalles sont hardcodés. Rendre configurable via variables d'environnement (DOCKER_COLLECT_INTERVAL, DOCKER_ALERTS_INTERVAL).


3. Corrections et Optimisations Code/Performances ⚙️

3.1 Anti-Patterns Identifiés

AP-1 : Fichier Monolithique app_optimized.py

Métrique Valeur
Lignes 6585
Classes ~30+
Fonctions ~200+
Impact Maintenance, lisibilité, testabilité

Action : Supprimer ce fichier et s'appuyer uniquement sur l'architecture modulaire existante (qui est déjà complète et fonctionnelle).

AP-2 : Import Circulaire Protégé par Try/Except

# notification_service.py, lignes 32-47
try:
    from schemas.notification import (...)
except ModuleNotFoundError:
    from app.schemas.notification import (...)

Ce pattern indique une incertitude sur le sys.path. C'est un symptôme de app_optimized.py qui fonctionne avec un chemin différent.

Action : Après suppression de app_optimized.py, utiliser uniquement les imports absolus from app.schemas....

AP-3 : Double Système de Persistance (HybridDB)

Le service HybridDB (services/hybrid_db.py) maintient des données en mémoire (listes Python) en parallèle avec SQLite. Cela crée une incohérence potentielle.

# routes/bootstrap.py, ligne 140
db.logs.insert(0, log_entry)  # ← Mémoire

vs.

# Ailleurs dans le code
repo = LogRepository(session)
await repo.create(...)  # ← SQLite

Action : Migrer entièrement vers le pattern Repository + SQLite. Le HybridDB est un vestige de l'ancienne architecture.

AP-4 : Fonctions Synchrones Bloquantes

# ssh_utils.py, lignes 128-133
result = subprocess.run(  # ⚠️ BLOQUE l'event loop
    ssh_cmd,
    capture_output=True,
    text=True,
    timeout=timeout + 10
)

La fonction bootstrap_host() utilise subprocess.run synchrone et est appelée depuis une route async :

# routes/bootstrap.py, ligne 98
result = bootstrap_host(...)  # ← Appel synchrone dans une route async

Cela bloque l'event loop pendant toute la durée du bootstrap (jusqu'à 120 secondes).

Correction :

# Utiliser asyncio.to_thread pour les opérations bloquantes
result = await asyncio.to_thread(
    bootstrap_host,
    host=request.host,
    root_password=request.root_password,
    automation_user=request.automation_user
)

Ou mieux, réécrire bootstrap_host avec asyncio.create_subprocess_exec comme ansible_service.execute_playbook le fait déjà correctement.

AP-5 : asyncio.create_task Sans Référence

# routes/bootstrap.py, ligne 152
asyncio.create_task(notification_service.notify_bootstrap_success(host_name))

Les tâches créées avec asyncio.create_task sans référence peuvent être garbage-collectées avant leur fin si rien ne les retient. De plus, les exceptions non attrapées dans ces tâches seront silencieusement perdues.

Correction :

# Stocker une référence et ajouter un callback d'erreur
background_tasks = set()

task = asyncio.create_task(notification_service.notify_bootstrap_success(host_name))
background_tasks.add(task)
task.add_done_callback(background_tasks.discard)

Ou utiliser BackgroundTasks de FastAPI :

from fastapi import BackgroundTasks

@router.post("", response_model=CommandResult)
async def bootstrap_ansible_host(
    request: BootstrapRequest,
    background_tasks: BackgroundTasks,
    api_key_valid: bool = Depends(verify_api_key)
):
    # ...
    background_tasks.add_task(notification_service.notify_bootstrap_success, host_name)

3.2 Goulots d'Étranglement Identifiés

GE-1 : Scan Système de Fichiers pour les Logs de Tâches

# app_optimized.py, lignes 519-565
def _build_index(self, force: bool = False):
    for year_dir in self.base_dir.iterdir():
        for month_dir in year_dir.iterdir():
            for day_dir in month_dir.iterdir():
                for md_file in day_dir.glob("*.md"):
                    # Parse chaque fichier

Ce scan récursif du système de fichiers à chaque appel (avec un cache de 60 secondes) sera lent si des centaines/milliers de fichiers de logs s'accumulent.

Optimisation : Le service TaskLogService dans services/task_log_service.py devrait indexer les logs en base de données plutôt que scanner le filesystem. Créer une table task_logs_index :

CREATE TABLE task_logs_index (
    id TEXT PRIMARY KEY,
    filename TEXT NOT NULL,
    path TEXT NOT NULL,
    task_name TEXT,
    target TEXT,
    status TEXT,
    date TEXT,
    source_type TEXT,
    created_at TIMESTAMP,
    metadata_json TEXT
);

GE-2 : Docker Collection Séquentielle par Hôte

La collecte Docker utilise un semaphore de 5, ce qui est bien, mais chaque hôte fait 4 commandes SSH séquentielles (docker version, docker ps, docker images, docker volume ls).

Optimisation : Combiner les commandes en une seule session SSH :

combined_cmd = """
echo '---VERSION---'
docker version --format '{{.Server.Version}}'
echo '---CONTAINERS---'
docker ps -a --format '{{json .}}' --no-trunc
echo '---IMAGES---'
docker images --format '{{json .}}'
echo '---VOLUMES---'
docker volume ls --format '{{json .}}'
"""
stdout, stderr, code = await self._ssh_exec(conn, combined_cmd, timeout=30)
# Parser les sections

Cela réduit le nombre de round-trips SSH de 4 à 1 par hôte.

GE-3 : index.html Monolithique (247 KB)

Le fichier index.html fait 247 KB et main.js fait 617 KB. Ces fichiers ne sont ni minifiés ni compressés.

Optimisations :

  1. Activer la compression gzip/brotli via middleware FastAPI
  2. Ajouter des headers de cache (Cache-Control, ETag)
  3. Diviser main.js en modules ES (chargement paresseux des sections)

3.3 Améliorations des Pratiques de Code

Python — Bonnes Pratiques

# Recommandation Fichier(s) Priorité
1 Utiliser Annotated de typing pour les dépendances FastAPI Toutes les routes Basse
2 Remplacer @app.on_event("startup") par les lifespan events (FastAPI 0.93+) factory.py Moyenne
3 Ajouter des type hints manquants dans HybridDB services/hybrid_db.py Basse
4 Utiliser logging au lieu de print() dans factory.py factory.py (15+ prints) Moyenne
5 Remplacer les f-strings dans les loggers par %s formatage Partout Basse
6 Ajouter __slots__ aux dataclasses de configuration core/config.py Basse
7 Utiliser enum.StrEnum pour les statuts ("running", "failed", etc.) schemas/, models/ Moyenne

Python — Sécurité du Code

# routes/bootstrap.py, ligne 92-94 — Import à l'intérieur de la fonction
import logging
import traceback
logger = logging.getLogger("bootstrap_endpoint")

Recommandation : Déplacer les imports et la création du logger au niveau du module (en haut du fichier).

JavaScript — Bonnes Pratiques

# Recommandation Fichier Priorité
1 Migrer de var vers const/let main.js Moyenne
2 Utiliser ES Modules (import/export) au lieu du scope global main.js et *.js Haute
3 Ajouter un linter (ESLint) avec config stricte Projet Moyenne
4 Implémenter le debouncing sur les appels API fréquents main.js Moyenne
5 Ajouter une couche d'abstraction API (fetch wrapper) main.js Haute

3.4 Correctifs Prioritaires

FIX-1 : require_admin ne vérifie pas le bon champ

# core/dependencies.py, lignes 161-162
payload = user.get("payload", {})  # ⚠️ Le champ "payload" n'existe pas !
role = payload.get("role", "viewer")

Le dictionnaire user retourné par get_current_user_optional contient role directement, pas dans un sous-dictionnaire payload. La vérification admin ne fonctionnera jamais pour les utilisateurs JWT.

Correction :

async def require_admin(user: dict = Depends(get_current_user)) -> dict:
    if user.get("type") == "api_key":
        return user
    role = user.get("role", "viewer")  # Directement dans le dict
    if role != "admin":
        raise HTTPException(status_code=403, detail="Droits administrateur requis")
    return user

FIX-2 : Fuite mémoire potentielle dans WebSocket

Le WebSocketManager ne nettoie les connexions mortes que lors d'un broadcast. Si aucun broadcast n'est envoyé pendant longtemps, les connexions mortes s'accumulent.

Correction : Ajouter un heartbeat périodique :

async def _heartbeat_loop(self):
    while True:
        await asyncio.sleep(30)
        await self.broadcast({"type": "ping"})

FIX-3 : Terminal proxy — readexactly vs read

# routes/websocket.py, ligne 104
data = await reader.readexactly(1024)  # Bloque jusqu'à avoir exactement 1024 bytes

Correction : data = await reader.read(4096)


4. Améliorations UI/UX 🎨

4.1 Ergonomie du Tableau de Bord

Dashboard — Structure Recommandée

Le dashboard actuel charge un fichier HTML monolithique de 247 KB. Voici les améliorations ergonomiques recommandées :

Zone Amélioration Impact
Navigation Sidebar rétractable avec icônes et badges de notification Haute
Page d'accueil Ajouter des "sparklines" (micro-graphiques) pour les métriques Moyenne
Filtres Filtrage en temps réel avec chips/tags visuels Haute
Breadcrumbs Ajouter une navigation hiérarchique Moyenne
Raccourcis clavier Ctrl+K pour recherche rapide, Ctrl+T pour nouveau terminal Moyenne
Dark/Light mode Toggle avec persistance en localStorage Moyenne

Gestion des Erreurs — Feedback Visuel

Actuellement, les erreurs sont probablement affichées dans des alert() ou des toasts basiques. Voici un système plus professionnel :

Système de Toast Notifications à 4 Niveaux :

┌─────────────────────────────────────────┐
│ ✅ SUCCESS                              │
│ "Playbook vm-upgrade.yml exécuté"       │
│ 3 hôtes mis à jour en 45s              │
│ ░░░░░░░░░░░░░░░░░░░░░░ auto-dismiss 5s │
└─────────────────────────────────────────┘

┌─────────────────────────────────────────┐
│ ❌ ERROR                                │
│ "Bootstrap échoué pour srv-backup"      │
│ Connection timeout après 30s            │
│ [Voir les logs] [Réessayer]             │
│ ► Reste affiché jusqu'au dismiss        │
└─────────────────────────────────────────┘

Implémentation recommandée :

  • Position : coin supérieur droit, empilées
  • Auto-dismiss : 5s pour success/info, sticky pour errors
  • Actions contextuelles intégrées ("Voir les logs", "Réessayer")
  • Animation entrée/sortie via Anime.js

4.2 Intégration Anime.js — Suggestions

Animation Déclencheur Effet
Task Progress Pendant l'exécution Barre de progression animée avec shimmer
Host Status Change WebSocket event Pulse animation sur le badge de statut
Docker Container Start/Stop Action utilisateur Icon spin + color morph
Page Transition Navigation SPA Fade-in avec translate-Y léger (16px)
Card Hover Mouse enter Scale(1.02) + shadow elevation subtile
Data Loading Fetch en cours Skeleton screens avec wave animation
Error Shake Validation échouée Shake horizontal (6px, 3 cycles, 400ms)

4.3 Composants UI Recommandés

Terminal SSH — Améliorations

Le terminal SSH intégré est une fonctionnalité killer. Améliorations suggérées :

  1. Split panes : Permettre de diviser le terminal en panneaux horizontaux/verticaux
  2. Quick commands : Palette de commandes pré-définies (sidebar ou dropdown)
  3. Session history : Recall des commandes précédentes avec recherche fuzzy
  4. Tab management : Onglets pour multiples sessions, avec indicateur d'activité
  5. Copy-paste amélioré : Double-clic pour sélection de mot, click-and-drag pour sélection

Playbook Editor — Améliorations

  1. Syntax highlighting YAML : Déjà intégré via CodeMirror
  2. Live linting : Intégrer ansible-lint en temps réel (via WebSocket)
  3. Variables autocomplete : Complétion automatique des variables de groupe
  4. Diff view : Voir les changements avant sauvegarde
  5. Template snippets : Bibliothèque de snippets de tâches courantes

4.4 Accessibilité (a11y)

Aspect État Actuel Recommandation
Contraste À vérifier WCAG AA minimum (ratio 4.5:1)
Navigation clavier Probablement partielle tabindex sur tous les éléments interactifs
Screen reader Non implémenté Ajouter aria-label, aria-live pour les mises à jour dynamiques
Focus visible Par défaut navigateur Personnaliser le style de focus (outline)

5. Idées d'Évolution "Next Level" 🚀

5.1 Gestion des Secrets (Vault)

Objectif : Centraliser et sécuriser tous les secrets (mots de passe, clés API, tokens).

Architecture proposée :

┌─────────────────────────────────────────────┐
│           Homelab Secrets Manager            │
├─────────────────────────────────────────────┤
│  ┌─────────┐  ┌──────────┐  ┌────────────┐ │
│  │ Ansible  │  │ Docker   │  │ Services   │ │
│  │ Vault    │  │ Secrets  │  │ Credentials│ │
│  └─────────┘  └──────────┘  └────────────┘ │
├─────────────────────────────────────────────┤
│  Backend: AES-256-GCM encryption at rest    │
│  Master Key: derived via PBKDF2 from        │
│  user password + hardware fingerprint       │
└─────────────────────────────────────────────┘

Implémentation par phases :

  1. Phase 1 : Chiffrement des secrets en base (clés SSH, tokens ntfy)
  2. Phase 2 : Interface UI pour gérer les secrets (CRUD + audit log)
  3. Phase 3 : Intégration avec ansible-vault pour les playbooks

5.2 Intégration Proxmox

Objectif : Gérer les VMs et containers LXC directement depuis le dashboard.

Fonctionnalités proposées :

Fonctionnalité Complexité Valeur
Lister VMs/CTs avec statut Faible Haute
Start/Stop/Restart VM Moyenne Haute
Créer VM from template Haute Moyenne
Monitoring CPU/RAM/IO Moyenne Haute
Snapshots management Moyenne Moyenne
Console VNC/SPICE intégrée Haute Haute

Approche technique :

  • Utiliser l'API REST de Proxmox VE (/api2/json/)
  • Authentification via token PVE (pas de mot de passe stocké)
  • WebSocket pour le streaming des métriques en temps réel

5.3 Monitoring Avancé

Métriques Système Enrichies

┌──────────────────────────────────────────┐
│  CPU History (24h)                        │
│  ██████████░░░░░░░░░ 52% avg             │
│  ▁▂▃▅▆▇█▇▅▃▂▁▁▂▃▅▇█▇▅▃▂▁              │
├──────────────────────────────────────────┤
│  Memory   ████████████████░░ 78% (12/16GB)│
│  Swap     █░░░░░░░░░░░░░░░░ 3% (0.5/16GB)│
│  Disk /   ██████████░░░░░░░ 62% (120/200G)│
│  Network  ↑ 12 Mbit/s  ↓ 45 Mbit/s      │
└──────────────────────────────────────────┘

Stack recommandé :

  • Collection : Via Ansible ad-hoc (déjà capable) ou node_exporter
  • Stockage : SQLite pour 7 jours, puis agrégation/cleanup automatique
  • Visualisation : Graphiques temps réel en JavaScript (Chart.js ou D3.js)
  • Alerting : Intégration avec ntfy (déjà en place)

Alerting Intelligent

Passer d'alertes simples (seuil dépassé) à un système plus sophistiqué :

  1. Rate of change : Alerter si le CPU augmente de >30% en 5 minutes
  2. Anomaly detection : Baseline automatique + détection de déviation
  3. Correlation : "Disk full → Service crash" → alerte unifiée
  4. Escalation : ntfy (info) → email (warning) → SMS/appel (critique)

5.4 Intégration Docker Compose Avancée

Fonctionnalités proposées :

  1. Visualisation des stacks : Graphe de dépendances entre containers
  2. Logs en streaming : docker logs -f via WebSocket
  3. Compose file editor : Édition avec validation en temps réel
  4. One-click deploy : Upload et déploiement de stacks docker-compose
  5. Auto-update : Vérification et mise à jour automatique des images (comme Watchtower)

5.5 CI/CD Self-Hosted

Pipeline proposée pour le dashboard lui-même :

┌─────────┐    ┌──────────┐    ┌─────────┐    ┌──────────┐
│  Push   │ →  │  Lint &  │ →  │  Build  │ →  │  Deploy  │
│  Git    │    │  Test    │    │  Docker │    │  Auto    │
└─────────┘    └──────────┘    └─────────┘    └──────────┘
     │              │               │               │
     └──── Gitea/Forgejo ──── ── GitHub Actions ────┘

Pour les services managés du homelab :

  • Interface de déploiement "GitOps-like"
  • Rollback automatique si health check échoue
  • Historique de déploiement avec diff

5.6 Multi-Tenant & Multi-User

Fonctionnalité Description
Rôles granulaires Viewer, Operator, Admin avec permissions par section
Audit trail Log de toutes les actions utilisateur avec IP/timestamp
Workspaces Séparer les environnements (prod, staging, lab)
API tokens Tokens scoped avec date d'expiration
2FA Authentification à deux facteurs (TOTP) via un app authenticator

6. Feuille de Route Actionnable 🗺️

Phase 1 : Gains Rapides (1-2 semaines) 🏃

Objectif : Corriger les failles critiques et les quick wins sans refactoring majeur.

# Action Effort Impact Fichier(s)
1.1 ⚠️ Ajouter .env au .gitignore et créer .env.example 5 min 🔴 Critique .gitignore, .env.example
1.2 🔒 Authentifier le WebSocket /ws avec token JWT 2h 🔴 Haute routes/websocket.py
1.3 🐛 Corriger require_admin (champ role mal lu) 15 min 🔴 Haute core/dependencies.py
1.4 🔄 Remplacer threading.Lock par asyncio.Lock dans WebSocketManager 30 min 🟡 Moyenne services/websocket_service.py
1.5 🐛 Corriger readexactlyread dans le proxy terminal 5 min 🟡 Moyenne routes/websocket.py
1.6 Wrapper bootstrap_host avec asyncio.to_thread 30 min 🟠 Haute routes/bootstrap.py
1.7 📝 Remplacer print() par logging dans factory.py 1h 🟢 Basse factory.py
1.8 🔑 Générer un JWT_SECRET_KEY aléatoire au premier démarrage si non défini 1h 🔴 Haute services/auth_service.py

Phase 2 : Objectifs à Moyen Terme (1-2 mois) 🎯

Objectif : Consolider l'architecture et améliorer l'expérience utilisateur.

# Action Effort Impact
2.1 🗑️ Supprimer app_optimized.py et le HybridDB 2-3j Architecture
2.2 🔐 Implémenter refresh tokens + blacklist de tokens 2j Sécurité
2.3 📊 Indexer les logs de tâches en base (remplacer le scan filesystem) 2j Performance
2.4 Optimiser la collecte Docker (commandes SSH combinées) 1j Performance
2.5 🎨 Implémenter le système de toast notifications 2j UI/UX
2.6 🖥️ Ajouter le split-pane et les onglets au terminal 3j UI/UX
2.7 🔔 Heartbeat WebSocket avec reconnexion automatique côté JS 1j Fiabilité
2.8 📦 Migrer vers FastAPI lifespan (remplacer on_event) 1j Architecture
2.9 🧪 Augmenter la couverture de tests à 65% (focus : auth, scheduler, WebSocket) 3-5j Qualité
2.10 🗜️ Activer la compression gzip et les headers de cache statique 2h Performance
2.11 📱 Améliorer le responsive (breakpoints tablette + mobile) 2j UI/UX
2.12 ⚙️ Rendre configurables les intervalles Docker et les limites de pagination 1j Ops

Phase 3 : Vision à Long Terme (3-6 mois) 🔭

Objectif : Transformer le dashboard en plateforme homelab de référence.

# Action Effort Impact
3.1 🔐 Secrets Manager — Chiffrement des secrets en base + UI 1-2 sem Sécurité
3.2 🖥️ Intégration Proxmox — API + Dashboard VMs 2-3 sem Feature
3.3 📊 Monitoring avancé — Graphiques historiques + anomaly detection 2-3 sem Feature
3.4 🐳 Docker Compose Manager — Stacks + logs streaming + deploy 2-3 sem Feature
3.5 👥 Multi-user avec RBAC — Rôles granulaires + audit trail 2 sem Sécurité
3.6 🔑 2FA (TOTP) — Authentification à deux facteurs 1 sem Sécurité
3.7 🚀 CI/CD Pipeline — Auto-deploy du dashboard + rollback 2 sem DevOps
3.8 🔄 Migration PostgreSQL (optionnel) — Pour scalabilité multi-instance 1-2 sem Architecture
3.9 📱 PWA — Notifications push natives, mode offline partiel 1-2 sem UI/UX
3.10 🔌 Plugin System — Architecture extensible pour intégrations tierces 3-4 sem Architecture

Matrice de Prioritisation

          IMPACT ÉLEVÉ
              ↑
     ┌────────┼────────┐
     │ 1.1    │ 2.1    │
     │ 1.2    │ 2.2    │
     │ 1.3    │ 2.3    │
     │ 1.6    │ 3.1    │
     │ 1.8    │ 3.5    │
     ├────────┼────────┤
     │ 1.4    │ 2.6    │
     │ 1.5    │ 2.9    │
     │ 1.7    │ 3.2    │
     │ 2.10   │ 3.4    │
     │ 2.7    │ 3.10   │
     └────────┼────────┘
  EFFORT FAIBLE    EFFORT ÉLEVÉ
              ↓
          IMPACT FAIBLE

Annexe A : Métriques du Projet

Métrique Valeur
Fichiers Python 144
Fichiers JS 8 (app)
Fichier HTML 1 (monolithique)
Migrations Alembic 19
Routes API 22 routers
Modèles SQLAlchemy 20
Schemas Pydantic 20
Services 16
CRUD Repositories 14+
Tests backend ~45% coverage
Tests frontend Présents (vitest)
Taille app_optimized.py 6585 lignes ⚠️
Taille index.html 247 KB ⚠️
Taille main.js 617 KB ⚠️

Annexe B : Sécurité — Checklist de Déploiement Production

[ ] .env absent du repository Git
[ ] JWT_SECRET_KEY est une chaîne aléatoire de 64+ caractères
[ ] API_KEY est unique et complexe
[ ] HTTPS activé (reverse proxy nginx/traefik)
[ ] CORS restreint aux origines autorisées
[ ] WebSocket authentifié par JWT
[ ] Rate limiting activé sur /api/auth et /api/bootstrap
[ ] Logs de sécurité activés (tentatives de connexion, accès refusés)
[ ] Rotation des logs configurée
[ ] Backups de la base de données automatisés
[ ] SSH key permissions = 600
[ ] StrictHostKeyChecking configurable (pas hardcodé à "no")
[ ] Docker socket non exposé directement
[ ] ttyd bind sur localhost uniquement (si reverse proxy)

📌 Note finale : Ce projet est remarquablement bien structuré pour un projet homelab. L'architecture modulaire avec Factory Pattern, Repository Pattern, et la séparation claire des responsabilités témoignent d'une excellente compréhension des patterns d'architecture logicielle. Les correctifs et améliorations proposés dans ce rapport visent à renforcer une base déjà solide pour atteindre un niveau Enterprise-grade tout en conservant la flexibilité et l'agilité qui font le charme d'un projet homelab.

Rapport généré le 20 février 2026 — Homelab Automation Dashboard v2.0.0