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
900 lines
38 KiB
Markdown
900 lines
38 KiB
Markdown
# 🔍 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](#1-synthèse-exécutive)
|
|
2. [Audit d'Architecture et de Sécurité 🛡️](#2-audit-darchitecture-et-de-sécurité-)
|
|
3. [Corrections et Optimisations Code/Performances ⚙️](#3-corrections-et-optimisations-codeperformances-)
|
|
4. [Améliorations UI/UX 🎨](#4-améliorations-uiux-)
|
|
5. [Idées d'Évolution "Next Level" 🚀](#5-idées-dévolution-next-level-)
|
|
6. [Feuille de Route Actionnable 🗺️](#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 :**
|
|
|
|
```python
|
|
# 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 :**
|
|
|
|
```python
|
|
# 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.
|
|
|
|
```python
|
|
# 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
|
|
|
|
```python
|
|
# 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 :**
|
|
|
|
```python
|
|
# 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 :**
|
|
```python
|
|
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
|
|
|
|
```python
|
|
# 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`.
|
|
|
|
```python
|
|
# 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 :**
|
|
```python
|
|
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 :**
|
|
|
|
```python
|
|
# 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 :
|
|
|
|
```python
|
|
@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 :**
|
|
|
|
```python
|
|
# 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 :
|
|
|
|
```python
|
|
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 :**
|
|
|
|
```python
|
|
# 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
|
|
|
|
```python
|
|
# 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.
|
|
|
|
```python
|
|
# routes/bootstrap.py, ligne 140
|
|
db.logs.insert(0, log_entry) # ← Mémoire
|
|
```
|
|
|
|
vs.
|
|
|
|
```python
|
|
# 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
|
|
|
|
```python
|
|
# 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` :
|
|
|
|
```python
|
|
# 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 :**
|
|
```python
|
|
# 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
|
|
|
|
```python
|
|
# 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 :**
|
|
```python
|
|
# 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 :
|
|
```python
|
|
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
|
|
|
|
```python
|
|
# 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` :
|
|
|
|
```sql
|
|
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 :
|
|
|
|
```python
|
|
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
|
|
|
|
```python
|
|
# 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
|
|
|
|
```python
|
|
# 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 :**
|
|
```python
|
|
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 :
|
|
```python
|
|
async def _heartbeat_loop(self):
|
|
while True:
|
|
await asyncio.sleep(30)
|
|
await self.broadcast({"type": "ping"})
|
|
```
|
|
|
|
#### FIX-3 : Terminal proxy — `readexactly` vs `read`
|
|
|
|
```python
|
|
# 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 `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*
|