Files
la-voix-du-peuple/EXPLOITATION.md
T
pironantoine 432509b2d3 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
2026-04-03 17:09:34 +00:00

497 lines
13 KiB
Markdown

# 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 |