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
- Synthèse Exécutive
- Audit d'Architecture et de Sécurité 🛡️
- Corrections et Optimisations Code/Performances ⚙️
- Améliorations UI/UX 🎨
- Idées d'Évolution "Next Level" 🚀
- 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
expetiatclaims - 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 :
- Les développeurs pourraient déployer avec ces valeurs
- L'historique Git conserve les secrets même après suppression
Recommandations :
- Ajouter
.envau.gitignoreimmédiatement - Conserver uniquement un
.env.exampleavec des valeurs placeholder - Utiliser
python-dotenvavec validation obligatoire des secrets au démarrage - 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 :
- Exiger HTTPS pour les endpoints sensibles (bootstrap, auth)
- Ajouter un avertissement dans les logs si la requête arrive en HTTP
- Envisager un chiffrement côté client avec une clé éphémère (Diffie-Hellman)
- 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 :
-
Pas de connection pooling avancé : SQLite single-writer peut devenir un bottleneck sous charge élevée. Envisager
pool_sizeetmax_overflowquand/si migration vers PostgreSQL. -
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. -
Migration Alembic dans
init_db(): L'exécution dealembic 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_digestpour 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 retardmax_instances=1: empêche les exécutions parallèles du même jobmisfire_grace_timeconfigurable- 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 :
- Activer la compression gzip/brotli via middleware FastAPI
- Ajouter des headers de cache (
Cache-Control,ETag) - Diviser
main.jsen 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 :
- Split panes : Permettre de diviser le terminal en panneaux horizontaux/verticaux
- Quick commands : Palette de commandes pré-définies (sidebar ou dropdown)
- Session history : Recall des commandes précédentes avec recherche fuzzy
- Tab management : Onglets pour multiples sessions, avec indicateur d'activité
- Copy-paste amélioré : Double-clic pour sélection de mot, click-and-drag pour sélection
Playbook Editor — Améliorations
- Syntax highlighting YAML : Déjà intégré via CodeMirror ✅
- Live linting : Intégrer ansible-lint en temps réel (via WebSocket)
- Variables autocomplete : Complétion automatique des variables de groupe
- Diff view : Voir les changements avant sauvegarde
- 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 :
- Phase 1 : Chiffrement des secrets en base (clés SSH, tokens ntfy)
- Phase 2 : Interface UI pour gérer les secrets (CRUD + audit log)
- Phase 3 : Intégration avec
ansible-vaultpour 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é :
- Rate of change : Alerter si le CPU augmente de >30% en 5 minutes
- Anomaly detection : Baseline automatique + détection de déviation
- Correlation : "Disk full → Service crash" → alerte unifiée
- Escalation : ntfy (info) → email (warning) → SMS/appel (critique)
5.4 Intégration Docker Compose Avancée
Fonctionnalités proposées :
- Visualisation des stacks : Graphe de dépendances entre containers
- Logs en streaming :
docker logs -fvia WebSocket - Compose file editor : Édition avec validation en temps réel
- One-click deploy : Upload et déploiement de stacks docker-compose
- 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 readexactly → read 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