Add technical and operational guides for the platform

Add ARCHITECTURE.md and EXPLOITATION.md files detailing the system's technical design and operational procedures.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 923ae0e3-a363-4db8-b04a-e8baca2a1330
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 9fd0e274-629e-4dae-8960-adf4c556797f
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/8af7d2ec-2cc3-4ece-8af3-9f071488d072/923ae0e3-a363-4db8-b04a-e8baca2a1330/VnHW0bR
Replit-Helium-Checkpoint-Created: true
This commit is contained in:
pironantoine
2026-04-03 17:09:34 +00:00
parent c0322d5c8e
commit 432509b2d3
2 changed files with 831 additions and 0 deletions
+335
View File
@@ -0,0 +1,335 @@
# Architecture Technique — La Voix du Peuple
**Version :** 1.0
**Date :** Avril 2026
**Statut :** Production
---
## 1. Vue d'ensemble
La Voix du Peuple est une plateforme démocratique citoyenne permettant la soumission d'idées politiques, leur filtrage automatique par un agent IA ancré dans le droit international des droits humains, et leur synthèse en un texte collectif vivant.
---
## 2. Architecture générale
```
RÉSEAU EXTERNE (Internet)
┌─────────────────────┐
│ HAProxy │
│ (réseau séparé) │
│ - Terminaison TLS │
│ - Load balancing │
│ - ACL / filtrage IP │
└──────────┬──────────┘
│ HTTP (port 80, réseau interne)
┌─────────────────────┐
│ Nginx │
│ (reverse proxy) │
│ - Routage /api/* │
│ - Serve SPA React │
│ - Cache statiques │
└────────┬────────────┘
┌─────────────────┴──────────────────┐
│ │
▼ ▼
┌───────────────────────┐ ┌───────────────────────┐
│ Gunicorn + Flask │ │ Fichiers statiques │
│ (127.0.0.1:8000) │ │ React SPA (dist/) │
│ - API REST /api/* │ │ (servis par Nginx) │
│ - Filtrage IA │ └───────────────────────┘
│ - Rate limiting │
└──────────┬────────────┘
┌─────────┴──────────┐
│ │
▼ ▼
┌──────────┐ ┌───────────────┐
│PostgreSQL│ │ OpenAI API │
│(local) │ │ (externe) │
│- ideas │ │ gpt-4o-mini │
│- synthesis│ │ gpt-4o │
└──────────┘ └───────────────┘
```
---
## 3. Composants
### 3.1 HAProxy (réseau séparé)
| Propriété | Valeur |
|-----------|--------|
| Rôle | Point d'entrée unique, terminaison TLS, load balancing |
| Réseau | DMZ / réseau séparé dédié |
| Protocoles entrants | HTTPS/443, HTTP/80 (redirect) |
| Protocoles sortants | HTTP/80 vers Nginx (réseau interne) |
| TLS | Terminaison SSL/TLS sur HAProxy, communication interne en HTTP clair |
Configuration recommandée dans HAProxy :
```
frontend voix_du_peuple_https
bind *:443 ssl crt /etc/ssl/certs/voix-du-peuple.pem
mode http
option forwardfor
http-request set-header X-Forwarded-Proto https
default_backend voix_du_peuple_backend
frontend voix_du_peuple_http
bind *:80
mode http
redirect scheme https code 301 if !{ ssl_fc }
backend voix_du_peuple_backend
mode http
balance roundrobin
option httpchk GET /api/healthz
http-check expect status 200
server app01 <IP_NGINX>:80 check inter 10s
```
### 3.2 Nginx
| Propriété | Valeur |
|-----------|--------|
| Rôle | Reverse proxy applicatif, service des fichiers statiques |
| Écoute | 0.0.0.0:80 (réseau interne uniquement) |
| Vers Flask | 127.0.0.1:8000 pour toute requête `/api/*` |
| Vers SPA | `dist/public/` pour tout le reste (`try_files`) |
Responsabilités :
- **Routage** : `/api/*` → Gunicorn, `/*` → SPA React
- **Cache** : assets JS/CSS/images avec `Cache-Control: immutable`
- **Headers de sécurité** : X-Frame-Options, X-Content-Type-Options, Referrer-Policy
- **Logging** : access.log et error.log structurés
> **Note HAProxy :** Nginx doit faire confiance à l'en-tête `X-Forwarded-For` positionné par HAProxy pour que Flask voie la vraie IP cliente (rate limiting par IP).
Configuration Nginx requise :
```nginx
set_real_ip_from <IP_HAPROXY>;
real_ip_header X-Forwarded-For;
real_ip_recursive on;
```
### 3.3 Gunicorn + Flask (Backend API)
| Propriété | Valeur |
|-----------|--------|
| Serveur WSGI | Gunicorn 23+ |
| Workers | 4 (synchrones, ajustable) |
| Bind | 127.0.0.1:8000 (non exposé directement) |
| Framework | Flask 3.1+ |
| Python | 3.11+ |
**Routes exposées :**
| Méthode | Route | Description | Rate limit |
|---------|-------|-------------|------------|
| GET | `/api/healthz` | Santé du service | Aucun |
| GET | `/api/ideas` | Liste des idées acceptées | 120/min |
| POST | `/api/ideas` | Soumettre une idée | 5/min, 20/h par IP |
| GET | `/api/ideas/stats` | Statistiques | 120/min |
| GET | `/api/synthesis` | Texte synthétisé collectif | 120/min |
**Sécurité applicative :**
- Rate limiting par IP réelle (via `flask-limiter`)
- Assainissement XSS : `bleach.clean()` sur toutes les entrées
- Requêtes SQL paramétrées (`psycopg2`) — pas de concaténation de chaînes
- En-têtes HTTP de sécurité sur toutes les réponses
- Aucun secret exposé dans les messages d'erreur
### 3.4 Agent IA
Deux agents distincts, chacun configuré avec son propre modèle :
#### Agent de filtrage
| Propriété | Valeur |
|-----------|--------|
| Modèle par défaut | `gpt-4o-mini` |
| Variable d'env | `OPENAI_FILTER_MODEL` |
| Tokens max | 300 |
| Format de sortie | JSON strict (`response_format: json_object`) |
| Déclenchement | À chaque soumission d'idée |
| Temps de réponse | ~3-6 secondes |
Logique de décision :
1. Appel OpenAI avec le prompt légal complet
2. Parse JSON `{"accepted": bool, "reason"?: str, "legal_basis"?: str}`
3. Si l'API retourne une erreur de filtre de contenu → rejet automatique avec citation DUDH/PIDCP/CEDH
4. Résultat persisté en base avec l'idée
#### Agent de synthèse
| Propriété | Valeur |
|-----------|--------|
| Modèle par défaut | `gpt-4o` |
| Variable d'env | `OPENAI_SYNTHESIS_MODEL` |
| Tokens max | 1200 |
| Déclenchement | En arrière-plan après chaque acceptation |
| Temps de réponse | ~8-15 secondes |
La synthèse est non-bloquante : l'API répond immédiatement à l'utilisateur, la synthèse s'effectue dans un thread daemon.
#### Base légale du filtre
Le prompt de filtrage intègre textuellement les articles pertinents de :
- Déclaration universelle des droits de l'homme (DUDH, ONU 1948)
- Pacte international relatif aux droits civils et politiques (PIDCP, ONU 1966)
- Convention européenne des droits de l'homme (CEDH, 1950)
- Charte des droits fondamentaux de l'UE (2000/2009)
- Convention pour la prévention du génocide (ONU 1948)
- Statut de Rome / CPI (1998)
- Convention sur la discrimination raciale (CERD, ONU 1965)
### 3.5 PostgreSQL
| Propriété | Valeur |
|-----------|--------|
| Version | 15+ |
| Accès | Localhost uniquement (127.0.0.1) |
| Connexion Flask | Via `DATABASE_URL` (psycopg2-binary) |
| Authentification | md5 / scram-sha-256 |
**Schéma de la base de données :**
```sql
CREATE TABLE ideas (
id SERIAL PRIMARY KEY,
content TEXT NOT NULL,
author VARCHAR(100),
accepted BOOLEAN NOT NULL DEFAULT FALSE,
rejection_reason TEXT,
legal_basis TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE synthesis (
id SERIAL PRIMARY KEY,
text TEXT NOT NULL,
idea_count INTEGER NOT NULL DEFAULT 0,
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
```
### 3.6 Frontend React
| Propriété | Valeur |
|-----------|--------|
| Framework | React 18 + Vite 6 |
| Styles | Tailwind CSS v4 + shadcn/ui + Radix UI |
| Routing | Wouter (léger, côté client) |
| Requêtes API | TanStack React Query |
| Build | Sortie statique dans `dist/public/` |
**Communication avec le backend :**
- Hooks générés depuis la spécification OpenAPI (`lib/api-spec/openapi.yaml`)
- Codegen via Orval → `lib/api-client-react/src/generated/`
- Toutes les requêtes passent par `/api/` (même origine, pas de CORS cross-origin en production)
- Rafraîchissement automatique de la synthèse toutes les 15 secondes
---
## 4. Flux de données
### 4.1 Soumission d'une idée
```
Citoyen
│ POST /api/ideas {"content": "...", "author": "..."}
HAProxy ──[TLS]──► Nginx ──► Gunicorn/Flask
├─ Validation entrées (longueur, XSS)
├─ Rate limiting (5/min par IP)
├─ Agent filtrage ──► OpenAI API
│ │
│ JSON {"accepted": bool, ...}
├─ INSERT INTO ideas (...)
├─ Si accepté :
│ Thread daemon ──► Agent synthèse ──► OpenAI
│ │
│ UPDATE synthesis
◄── HTTP 201 {"accepted": bool, "reason": "...", "idea": {...}} ──────────┘
```
### 4.2 Lecture de la synthèse
```
Citoyen (polling 15s)
│ GET /api/synthesis
HAProxy ──► Nginx ──► Flask ──► SELECT FROM synthesis ──► {"text": "...", "ideaCount": N}
```
---
## 5. Sécurité
### 5.1 Couches de défense
| Couche | Mécanisme |
|--------|-----------|
| Réseau | HAProxy sur réseau séparé, Nginx non exposé directement |
| Transport | TLS 1.2/1.3 terminé sur HAProxy |
| Applicative Flask | Rate limiting, validation, assainissement XSS, headers sécurité |
| IA (double filtre) | Filtre de contenu Azure/OpenAI + filtre légal interne |
| Base de données | Requêtes paramétrées, accès localhost uniquement |
| Système | Utilisateur dédié non-root, systemd sandboxing |
### 5.2 Périmètre des ports réseau
| Port | Interface | Exposé à | Rôle |
|------|-----------|----------|------|
| 443 | HAProxy | Internet | HTTPS public |
| 80 | HAProxy | Internet | Redirect HTTPS |
| 80 | Nginx | Réseau interne | HTTP depuis HAProxy |
| 8000 | Gunicorn | 127.0.0.1 | API Flask |
| 5432 | PostgreSQL | 127.0.0.1 | Base de données |
### 5.3 En-têtes HTTP de sécurité (Flask)
```
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-XSS-Protection: 1; mode=block
Referrer-Policy: strict-origin-when-cross-origin
Content-Security-Policy: default-src 'self'; script-src 'none'; object-src 'none'
Cache-Control: no-store
```
---
## 6. Décisions d'architecture
| Décision | Choix | Justification |
|----------|-------|---------------|
| ORM vs SQL direct | psycopg2 direct | Transparence, simplicité, facilité d'audit |
| Sync vs Async Flask | Synchrone (Gunicorn) | Complexité réduite, suffisant pour la charge attendue |
| Synthèse bloquante vs thread | Thread daemon | Latence utilisateur réduite, synthèse non-critique au retour |
| TLS sur HAProxy vs Nginx | HAProxy | TLS centralisé sur le composant réseau dédié à cet usage |
| SPA vs SSR | SPA statique | Déploiement simple, aucune dépendance Node en production |
| Rate limiting en mémoire vs Redis | Mémoire (`memory://`) | Installation simple ; Redis optionnel pour cluster multi-instances |
---
## 7. Scalabilité
L'architecture actuelle est dimensionnée pour une instance unique. En cas de montée en charge :
- **Gunicorn workers** : augmenter via `--workers` (règle : `2 × CPU + 1`)
- **Multi-instances** : ajouter des backends dans HAProxy + basculer le rate limiting sur Redis (`RATELIMIT_STORAGE_URL=redis://...`)
- **Base de données** : ajouter des index sur `ideas.accepted` et `ideas.created_at`
- **Cache synthèse** : la synthèse est unique en base, naturellement partagée entre workers
+496
View File
@@ -0,0 +1,496 @@
# Manuel d'Exploitation — La Voix du Peuple
**Version :** 1.0
**Date :** Avril 2026
**Public :** Administrateurs système / DevOps
---
## 1. Services et composants
| Service | Technologie | Géré par |
|---------|-------------|----------|
| Équilibreur frontal | HAProxy | Infrastructure réseau séparée |
| Reverse proxy | Nginx | systemd / DNF |
| API backend | Gunicorn + Flask | systemd (`voix-du-peuple-api`) |
| Base de données | PostgreSQL 15 | systemd (`postgresql-15`) |
| Frontend | Fichiers statiques | Nginx (pas de service dédié) |
---
## 2. Commandes de gestion courantes
### 2.1 API Flask (Gunicorn)
```bash
# Statut
sudo systemctl status voix-du-peuple-api
# Démarrer / arrêter / redémarrer
sudo systemctl start voix-du-peuple-api
sudo systemctl stop voix-du-peuple-api
sudo systemctl restart voix-du-peuple-api
# Rechargement gracieux (sans coupure de connexions)
sudo systemctl reload voix-du-peuple-api
# Activer/désactiver le démarrage automatique
sudo systemctl enable voix-du-peuple-api
sudo systemctl disable voix-du-peuple-api
```
### 2.2 Nginx
```bash
sudo systemctl status nginx
sudo systemctl reload nginx # Recharge la config sans coupure
sudo systemctl restart nginx # Redémarrage complet
# Tester la configuration avant reload
sudo nginx -t
```
### 2.3 PostgreSQL
```bash
sudo systemctl status postgresql-15
sudo systemctl restart postgresql-15
# Connexion console
sudo -u postgres psql -d voixdupeuple
# Sauvegarde manuelle
sudo -u postgres pg_dump voixdupeuple > /backup/voixdupeuple_$(date +%Y%m%d_%H%M).sql
```
### 2.4 HAProxy (réseau séparé)
HAProxy est géré sur son propre hôte/réseau. Référez-vous à la documentation de votre équipe réseau.
Vérification de la connectivité depuis l'hôte applicatif :
```bash
# Vérifier que Nginx répond bien sur le réseau interne
curl -v http://<IP_NGINX>/api/healthz
```
---
## 3. Vérifications de santé
### 3.1 Endpoint de santé applicatif
```bash
# Depuis l'hôte (via Nginx)
curl http://localhost/api/healthz
# Réponse attendue : {"status":"ok"}
# Directement sur Gunicorn (bypass Nginx)
curl http://127.0.0.1:8000/api/healthz
```
### 3.2 Vérification complète de la chaîne
```bash
# 1. PostgreSQL
sudo -u postgres psql -c "SELECT 1" voixdupeuple
# 2. Gunicorn
curl -s http://127.0.0.1:8000/api/healthz | grep ok
# 3. Nginx
curl -s http://localhost/api/healthz | grep ok
# 4. Stats de la plateforme
curl -s http://localhost/api/ideas/stats
# Réponse : {"total": N, "accepted": N, "rejected": N}
# 5. Depuis HAProxy (test end-to-end TLS)
curl -s https://voix-du-peuple.example.com/api/healthz
```
### 3.3 Script de vérification rapide
```bash
#!/bin/bash
# /opt/voix-du-peuple/scripts/healthcheck.sh
ERRORS=0
check() {
echo -n "$1 ... "
if eval "$2" &>/dev/null; then
echo "OK"
else
echo "ERREUR"
ERRORS=$((ERRORS+1))
fi
}
check "PostgreSQL" "sudo -u postgres psql -c 'SELECT 1' voixdupeuple"
check "Gunicorn" "curl -sf http://127.0.0.1:8000/api/healthz"
check "Nginx" "curl -sf http://localhost/api/healthz"
check "Systemd API" "systemctl is-active --quiet voix-du-peuple-api"
[ $ERRORS -eq 0 ] && echo "Tout OK" || echo "$ERRORS service(s) en erreur"
exit $ERRORS
```
---
## 4. Logs
### 4.1 Localisation des logs
| Source | Fichier / Commande |
|--------|--------------------|
| API Flask (applicatif) | `/var/log/voix-du-peuple/api-error.log` |
| API Flask (accès HTTP) | `/var/log/voix-du-peuple/api-access.log` |
| Journald (systemd) | `journalctl -u voix-du-peuple-api` |
| Nginx accès | `/var/log/nginx/access.log` |
| Nginx erreurs | `/var/log/nginx/error.log` |
| PostgreSQL | `/var/lib/pgsql/15/data/log/` |
| HAProxy | Voir hôte HAProxy dédié |
### 4.2 Consultation en temps réel
```bash
# Logs applicatifs Flask
tail -f /var/log/voix-du-peuple/api-error.log
# Logs systemd en temps réel
journalctl -u voix-du-peuple-api -f
# Logs Nginx
tail -f /var/log/nginx/error.log
tail -f /var/log/nginx/access.log
# Combiner plusieurs sources
journalctl -u voix-du-peuple-api -u nginx -f
```
### 4.3 Recherche dans les logs
```bash
# Erreurs OpenAI des dernières 24h
grep -i "openai\|erreur\|error" /var/log/voix-du-peuple/api-error.log | tail -50
# Requêtes POST /api/ideas
grep "POST /api/ideas" /var/log/voix-du-peuple/api-access.log | tail -20
# IP ayant déclenché le rate limiting (code 429)
grep " 429 " /var/log/nginx/access.log | awk '{print $1}' | sort | uniq -c | sort -rn
```
### 4.4 Rotation des logs
Créer `/etc/logrotate.d/voix-du-peuple` :
```
/var/log/voix-du-peuple/*.log {
daily
rotate 30
compress
delaycompress
missingok
notifempty
postrotate
systemctl reload voix-du-peuple-api > /dev/null 2>&1 || true
endscript
}
```
---
## 5. Sauvegardes
### 5.1 Base de données
```bash
# Sauvegarde manuelle
sudo -u postgres pg_dump voixdupeuple \
| gzip > /backup/voixdupeuple_$(date +%Y%m%d_%H%M%S).sql.gz
# Restauration
gunzip -c /backup/voixdupeuple_20260401_120000.sql.gz \
| sudo -u postgres psql voixdupeuple
```
### 5.2 Sauvegarde automatique (cron)
```bash
sudo crontab -e -u postgres
```
```cron
# Sauvegarde quotidienne à 3h00
0 3 * * * pg_dump voixdupeuple | gzip > /backup/voixdupeuple_$(date +\%Y\%m\%d).sql.gz
# Nettoyage des sauvegardes de plus de 30 jours
0 4 * * * find /backup -name "voixdupeuple_*.sql.gz" -mtime +30 -delete
```
---
## 6. Mise à jour applicative
### 6.1 Mise à jour du code
```bash
sudo -u voixdupeuple bash << 'EOF'
cd /opt/voix-du-peuple
git fetch origin
git log HEAD..origin/main --oneline # Voir ce qui va changer
git pull origin main
EOF
```
### 6.2 Mise à jour des dépendances Python
```bash
sudo -u voixdupeuple bash << 'EOF'
cd /opt/voix-du-peuple
.venv/bin/pip install -r artifacts/flask-api/requirements.txt
EOF
sudo systemctl restart voix-du-peuple-api
```
### 6.3 Rebuild du frontend
```bash
sudo -u voixdupeuple bash << 'EOF'
cd /opt/voix-du-peuple
pnpm install --frozen-lockfile
cd artifacts/voix-du-peuple
pnpm exec vite build --config vite.config.selfhost.ts
EOF
sudo systemctl reload nginx
```
### 6.4 Procédure de mise à jour complète
```bash
sudo -u voixdupeuple bash << 'EOF'
cd /opt/voix-du-peuple
git pull origin main
.venv/bin/pip install -r artifacts/flask-api/requirements.txt
pnpm install --frozen-lockfile
cd artifacts/voix-du-peuple
pnpm exec vite build --config vite.config.selfhost.ts
EOF
sudo systemctl restart voix-du-peuple-api
sudo systemctl reload nginx
# Vérification
sleep 3
curl -s http://localhost/api/healthz
```
---
## 7. HAProxy — intégration réseau séparé
### 7.1 Vérification de la connectivité inter-réseaux
```bash
# Depuis l'hôte HAProxy : vérifier que Nginx est joignable
curl -v http://<IP_NGINX_INTERNE>:80/api/healthz
# Depuis l'hôte applicatif : vérifier que les X-Forwarded-For arrivent bien
curl -s http://localhost/api/ideas/stats
journalctl -u voix-du-peuple-api -n 20 | grep "127.0.0.1\|X-Forwarded"
```
### 7.2 IP réelle dans Flask (rate limiting)
Flask utilise l'IP réelle du client via l'en-tête `X-Forwarded-For` positionné par HAProxy. Nginx doit être configuré pour transmettre cet en-tête et déclarer l'IP de HAProxy comme source de confiance.
Dans `/etc/nginx/conf.d/voix-du-peuple.conf` :
```nginx
# Remplacez par l'IP réelle de votre HAProxy
set_real_ip_from 192.168.10.5;
real_ip_header X-Forwarded-For;
real_ip_recursive on;
location /api/ {
proxy_pass http://127.0.0.1:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $http_x_forwarded_proto;
proxy_read_timeout 60s;
}
```
### 7.3 Vérification du rate limiting par IP réelle
```bash
# Simuler une requête comme si elle venait d'une IP externe via HAProxy
curl -H "X-Forwarded-For: 1.2.3.4" http://127.0.0.1:8000/api/healthz
# Vérifier dans les logs Flask que l'IP 1.2.3.4 est bien loggée
journalctl -u voix-du-peuple-api -n 5
```
### 7.4 Check HAProxy depuis la plateforme
```bash
# Vérifier les en-têtes qui arrivent réellement sur Gunicorn
python3 -c "
import urllib.request
req = urllib.request.Request('http://127.0.0.1:8000/api/healthz')
r = urllib.request.urlopen(req)
print(dict(r.headers))
"
```
---
## 8. Gestion des incidents
### 8.1 L'API ne répond plus (5xx)
```bash
# 1. Vérifier le service
sudo systemctl status voix-du-peuple-api
# 2. Lire les logs récents
journalctl -u voix-du-peuple-api -n 50 --no-pager
# 3. Redémarrer
sudo systemctl restart voix-du-peuple-api
# 4. Si l'erreur persiste, vérifier PostgreSQL
sudo systemctl status postgresql-15
curl -s http://127.0.0.1:8000/api/healthz
```
### 8.2 Erreurs OpenAI (filtrage/synthèse dégradé)
Les erreurs OpenAI n'interrompent pas le service :
- Si le filtrage échoue → l'idée est **rejetée par défaut** (fail-safe)
- Si la synthèse échoue → le texte précédent est conservé
```bash
# Vérifier les erreurs OpenAI
grep -i "openai\|openrouter\|api_key" /var/log/voix-du-peuple/api-error.log | tail -20
# Tester la clé API manuellement
source /opt/voix-du-peuple/.env
curl https://api.openai.com/v1/models \
-H "Authorization: Bearer $OPENAI_API_KEY" | python3 -m json.tool | head -20
```
### 8.3 Base de données inaccessible
```bash
# Vérifier PostgreSQL
sudo systemctl status postgresql-15
sudo -u postgres psql -c "SELECT version();"
# Connexion directe avec l'utilisateur applicatif
source /opt/voix-du-peuple/.env
psql "$DATABASE_URL" -c "SELECT COUNT(*) FROM ideas;"
# Redémarrer PostgreSQL en dernier recours
sudo systemctl restart postgresql-15
sudo systemctl restart voix-du-peuple-api
```
### 8.4 Attaque ou abus (rate limit dépassé)
```bash
# Identifier les IPs agressives
grep " 429 " /var/log/nginx/access.log | awk '{print $1}' | sort | uniq -c | sort -rn | head -20
# Bloquer une IP via firewalld
sudo firewall-cmd --add-rich-rule='rule family="ipv4" source address="1.2.3.4" reject' --permanent
sudo firewall-cmd --reload
# Ou déléguer le blocage à HAProxy (recommandé — ACL HAProxy)
```
### 8.5 Disque plein
```bash
# Localiser ce qui prend de la place
df -h
du -sh /var/log/voix-du-peuple/*
du -sh /backup/*
# Nettoyer les anciens logs (si logrotate non configuré)
find /var/log/voix-du-peuple -name "*.log.*" -mtime +14 -delete
# Nettoyer les anciennes sauvegardes
find /backup -name "voixdupeuple_*.sql.gz" -mtime +30 -delete
```
---
## 9. Supervision (recommandations)
### 9.1 Métriques à surveiller
| Métrique | Seuil d'alerte | Commande |
|----------|----------------|----------|
| CPU Gunicorn | > 80% sustained | `top -p $(pgrep -d, gunicorn)` |
| Mémoire | > 80% | `free -m` |
| Espace disque | > 80% | `df -h` |
| Temps de réponse `/api/ideas` | > 10s | `time curl http://localhost/api/ideas` |
| Taux d'erreur 5xx | > 1% | `grep " 5[0-9][0-9] " /var/log/nginx/access.log` |
| PostgreSQL connexions | > 80 | `SELECT count(*) FROM pg_stat_activity;` |
### 9.2 Intégration avec des outils de supervision
**Prometheus + Alertmanager** (optionnel) :
```bash
# Installer flask-prometheus-metrics si vous souhaitez exposer des métriques
.venv/bin/pip install prometheus-flask-exporter
```
**Nagios / Zabbix** — check HTTP simple :
```
check_http -H localhost -u /api/healthz -e "200 OK" -s '{"status":"ok"}'
```
---
## 10. Opérations PostgreSQL courantes
```sql
-- Nombre d'idées par statut
SELECT accepted, COUNT(*) FROM ideas GROUP BY accepted;
-- Dernières idées soumises
SELECT id, LEFT(content, 60), author, accepted, created_at
FROM ideas ORDER BY created_at DESC LIMIT 10;
-- Idées rejetées avec motif
SELECT LEFT(content, 60), rejection_reason, legal_basis
FROM ideas WHERE accepted = FALSE ORDER BY created_at DESC LIMIT 10;
-- Synthèse actuelle
SELECT idea_count, updated_at, LEFT(text, 200) FROM synthesis;
-- Taille des tables
SELECT relname, pg_size_pretty(pg_total_relation_size(relid))
FROM pg_stat_user_tables ORDER BY pg_total_relation_size(relid) DESC;
-- Connexions actives
SELECT count(*), state FROM pg_stat_activity GROUP BY state;
```
---
## 11. Référence des fichiers de configuration
| Fichier | Rôle |
|---------|------|
| `/opt/voix-du-peuple/.env` | Variables d'environnement (secrets) |
| `/etc/systemd/system/voix-du-peuple-api.service` | Service systemd Gunicorn |
| `/etc/nginx/conf.d/voix-du-peuple.conf` | Configuration Nginx |
| `/var/lib/pgsql/15/data/pg_hba.conf` | Authentification PostgreSQL |
| `/var/lib/pgsql/15/data/postgresql.conf` | Configuration PostgreSQL |
| `/etc/logrotate.d/voix-du-peuple` | Rotation des logs |
| `/opt/voix-du-peuple/artifacts/flask-api/ai_agent.py` | Configuration agents IA |