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