- Fix .env example: OPENAI_API_KEY → MISTRAL_API_KEY, SESSION_SECRET → SECRET_KEY, add ADMIN_SECRET - Remove manual DB init section (init_db() is automatic at service startup) - Fix build command: remove non-existent vite.config.selfhost.ts, use pnpm build - Rewrite env vars table: Mistral-first, add Redis/hCaptcha/flood/IP blacklist vars, fix FILTER_MODEL / SYNTHESIS_MODEL names - Remove Python code snippet in "Modèles IA" section (env vars suffice) - Clarify nginx.conf listens on 8080 (behind HAProxy) vs standalone (port 80) - Fix section 12: remove "décommentez le bloc HTTPS" (no such block in nginx.conf) - Fix dépannage: OpenAI → Mistral AI, correct API endpoint and curl command - Add VITE_* build-time var troubleshooting entry - Fix project structure: remove vite.config.selfhost.ts, add api-zod, update pages list - Fix pg_hba.conf: md5 → scram-sha-256 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
13 KiB
Guide d'auto-hébergement — La Voix du Peuple
Rocky Linux 9 (ou RHEL 9, AlmaLinux 9)
Architecture
Internet
│
▼
[Nginx] ─── /api/* ──► [Gunicorn + Flask] ──► [PostgreSQL]
│ │
└─── /* ──► [React SPA] └──► [Mistral AI API]
(fichiers statiques)
- Frontend : React + Vite, servi comme fichiers statiques par Nginx
- Backend : Flask + Gunicorn (4 workers), accessible uniquement via Nginx
- Base de données : PostgreSQL 15+
- IA : Mistral AI (
MISTRAL_API_KEYrequis) ou tout fournisseur compatible OpenAI
Note production : En déploiement exposé à Internet, placer HAProxy devant Nginx pour la terminaison TLS. Voir
ARCHITECTURE.mdpour le schéma complet. Dans cette configuration, Nginx écoute sur le port 8080 (fichierdeploy/nginx.conf) et HAProxy forward sur ce port. Pour un déploiement standalone sans HAProxy, changezlisten 8080;enlisten 80;dansdeploy/nginx.confet activez HTTPS avec certbot (section 12).
Prérequis
sudo dnf update -y
sudo dnf install -y git curl wget epel-release
1. Installer les dépendances système
Python 3.11
sudo dnf install -y python3.11 python3.11-pip python3.11-devel
python3.11 --version # doit afficher Python 3.11.x
Node.js 20 (LTS)
curl -fsSL https://rpm.nodesource.com/setup_20.x | sudo bash -
sudo dnf install -y nodejs
node --version # doit afficher v20.x.x
pnpm
npm install -g pnpm
pnpm --version
PostgreSQL 15
sudo dnf install -y postgresql15-server postgresql15
sudo /usr/pgsql-15/bin/postgresql-15-setup initdb
sudo systemctl enable --now postgresql-15
Nginx
sudo dnf install -y nginx
sudo systemctl enable nginx
2. Configurer PostgreSQL
sudo -u postgres psql
Dans psql :
CREATE USER voixdupeuple WITH PASSWORD 'CHANGEME_MOT_DE_PASSE_FORT';
CREATE DATABASE voixdupeuple OWNER voixdupeuple;
GRANT ALL PRIVILEGES ON DATABASE voixdupeuple TO voixdupeuple;
\q
Éditez /var/lib/pgsql/15/data/pg_hba.conf — remplacez ident par scram-sha-256 sur
la ligne 127.0.0.1/32 :
host all all 127.0.0.1/32 scram-sha-256
sudo systemctl restart postgresql-15
# Test de connexion
psql -U voixdupeuple -h 127.0.0.1 -d voixdupeuple -c "\dt"
3. Créer l'utilisateur système
sudo useradd -r -s /bin/bash -d /opt/voix-du-peuple voixdupeuple
sudo mkdir -p /opt/voix-du-peuple
sudo chown voixdupeuple:voixdupeuple /opt/voix-du-peuple
4. Cloner le dépôt
sudo -u voixdupeuple git clone \
https://votre-depot.example.com/voix-du-peuple.git \
/opt/voix-du-peuple
5. Configurer les variables d'environnement
sudo -u voixdupeuple cp /opt/voix-du-peuple/.env.example /opt/voix-du-peuple/.env
sudo nano /opt/voix-du-peuple/.env
Valeurs obligatoires à renseigner :
# Base de données
DATABASE_URL=postgresql://voixdupeuple:CHANGEME_MOT_DE_PASSE_FORT@127.0.0.1:5432/voixdupeuple
# IA — Mistral AI (recommandé)
MISTRAL_API_KEY=votre-cle-mistral
# Sécurité — générez avec : python3 -c "import secrets; print(secrets.token_hex(32))"
SECRET_KEY=VALEUR_ALEATOIRE_LONGUE
# Panel d'administration
ADMIN_SECRET=MOT_DE_PASSE_ADMIN_FORT
Sécurisez le fichier :
sudo chmod 600 /opt/voix-du-peuple/.env
sudo chown voixdupeuple:voixdupeuple /opt/voix-du-peuple/.env
6. Installer les dépendances Python
sudo -u voixdupeuple bash -c "
cd /opt/voix-du-peuple
python3.11 -m venv .venv
.venv/bin/pip install --upgrade pip
.venv/bin/pip install -r artifacts/flask-api/requirements.txt
"
7. Initialiser la base de données
La base de données est initialisée automatiquement au premier démarrage du service (création des tables et migrations incrémentales idempotentes). Aucune action manuelle n'est requise. Passez directement à l'étape suivante.
8. Construire le frontend React
sudo -u voixdupeuple bash -c "
cd /opt/voix-du-peuple
pnpm install --frozen-lockfile
cd artifacts/voix-du-peuple
pnpm build
"
Les fichiers statiques sont générés dans artifacts/voix-du-peuple/dist/public/.
Si vous avez configuré VITE_APP_URL (URL publique du site, pour le QR code du flyer) ou
VITE_HCAPTCHA_SITE_KEY, ajoutez ces variables dans le .env avant le build — elles
sont intégrées statiquement dans le bundle.
9. Configurer le service systemd (Gunicorn)
# Créer le répertoire de logs
sudo mkdir -p /var/log/voix-du-peuple
sudo chown voixdupeuple:voixdupeuple /var/log/voix-du-peuple
# Copier le fichier de service
sudo cp /opt/voix-du-peuple/deploy/voix-du-peuple-api.service \
/etc/systemd/system/voix-du-peuple-api.service
# Activer et démarrer
sudo systemctl daemon-reload
sudo systemctl enable --now voix-du-peuple-api
# Vérifier
sudo systemctl status voix-du-peuple-api
Test de l'API :
curl http://127.0.0.1:8000/api/healthz
# {"status": "ok"}
10. Configurer Nginx
Le fichier deploy/nginx.conf configure Nginx en écoute sur le port 8080 (mode
derrière HAProxy). Pour un déploiement standalone sans HAProxy, changez listen 8080;
en listen 80; avant de copier.
sudo cp /opt/voix-du-peuple/deploy/nginx.conf \
/etc/nginx/conf.d/voix-du-peuple.conf
# Si déploiement standalone : changer le port d'écoute
sudo sed -i 's/listen 8080;/listen 80;/' /etc/nginx/conf.d/voix-du-peuple.conf
# Tester la configuration
sudo nginx -t
# Recharger Nginx
sudo systemctl reload nginx
11. Ouvrir le pare-feu
# Standalone (Nginx expose directement HTTP/HTTPS)
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --permanent --add-service=https
sudo firewall-cmd --reload
SELinux — autoriser Nginx à proxyfier vers Gunicorn :
sudo setsebool -P httpd_can_network_connect 1
12. (Recommandé) HTTPS avec Let's Encrypt — déploiement standalone
Ne s'applique qu'en déploiement standalone (Nginx exposé directement). Si HAProxy gère le TLS, configurez Let's Encrypt sur HAProxy — voir
ARCHITECTURE.md.
sudo dnf install -y certbot python3-certbot-nginx
# Remplacez voix-du-peuple.example.com par votre domaine
sudo certbot --nginx -d voix-du-peuple.example.com \
--email contact@example.com --agree-tos --no-eff-email
# Vérifier le renouvellement automatique
sudo systemctl status certbot-renew.timer
Structure du projet
voix-du-peuple/
├── artifacts/
│ ├── flask-api/ # Backend Python Flask
│ │ ├── app.py # Application principale + routes API
│ │ ├── ai_agent.py # Agent IA (filtrage, synthèse, check-law)
│ │ ├── legal_framework.py # Prompts ancrés dans le droit (16 sources)
│ │ ├── database.py # Accès PostgreSQL (psycopg2, sans ORM)
│ │ ├── requirements.txt # Dépendances Python
│ │ └── start.sh # Démarrage dev direct (python app.py)
│ └── voix-du-peuple/ # Frontend React + Vite 7
│ ├── src/
│ │ ├── pages/ # home, about, transparence, contributions-brutes,
│ │ │ # consultations, legal-notice, privacy-policy, admin…
│ │ ├── components/ # UI (shadcn/ui + Radix), consent-dialog, navbar…
│ │ ├── lib/ # prompts.ts (verbatim IA), hooks, utils
│ │ └── App.tsx # Routing principal (Wouter)
│ └── vite.config.ts # Config Vite (dev + production)
├── lib/
│ ├── api-spec/ # Spécification OpenAPI + config Orval
│ ├── api-client-react/ # Hooks React Query générés
│ └── api-zod/ # Schémas Zod générés
├── deploy/
│ ├── voix-du-peuple-api.service # Service systemd Gunicorn
│ └── nginx.conf # Configuration Nginx (port 8080, derrière HAProxy)
├── docs/ # Documentation technique (RGPD, sécurité, prompts…)
├── .env.example # Variables d'environnement (modèle commenté)
└── DEPLOIEMENT.md # Ce fichier
Variables d'environnement complètes
| Variable | Obligatoire | Description |
|---|---|---|
DATABASE_URL |
Oui | URL PostgreSQL — postgresql://user:pass@host:5432/db |
MISTRAL_API_KEY |
Oui* | Clé API Mistral AI (fournisseur par défaut) |
OPENAI_API_KEY |
Oui* | Alternative si Mistral non utilisé (compatible OpenAI) |
OPENAI_BASE_URL |
Non | Base URL pour fournisseur tiers (Ollama, proxy…) |
FILTER_MODEL |
Non | Modèle de filtrage (défaut : mistral-small-latest) |
SYNTHESIS_MODEL |
Non | Modèle de synthèse (défaut : mistral-large-latest) |
SECRET_KEY |
Recommandé | Active le cooldown cookie et le consentement RGPD signé HMAC-SHA256 |
ADMIN_SECRET |
Recommandé | Mot de passe Bearer token pour le panel /admin |
REDIS_URL |
Non | Rate limiting persistant entre workers — redis://localhost:6379/0 |
RATE_LIMIT_CONTRIBUTIONS |
Non | Seuil de soumissions (défaut : 5 per minute;3 per hour) |
CONTRIBUTION_COOLDOWN_SECONDS |
Non | Délai inter-soumissions par session (défaut : 240) |
FLOOD_THRESHOLD |
Non | Seuil d'alerte flood en 5 min (défaut : 10) |
IP_BLACKLIST_DAYS |
Non | Durée du blacklist IP (défaut : 30) |
HCAPTCHA_SECRET_KEY |
Non | Active la vérification hCaptcha côté serveur |
VITE_HCAPTCHA_SITE_KEY |
Non | Clé publique hCaptcha côté frontend (rebuild requis) |
VITE_APP_URL |
Non | URL publique du site — utilisée par le QR code du flyer (rebuild requis) |
*MISTRAL_API_KEY est prioritaire. Si absent, OPENAI_API_KEY est utilisé. L'un des deux est obligatoire.
Modèles IA
| Rôle | Modèle par défaut | Variable de contrôle |
|---|---|---|
| Filtrage des contributions | mistral-small-latest |
FILTER_MODEL |
| Synthèse collective | mistral-large-latest |
SYNTHESIS_MODEL |
| Vérification cadre légal | même que FILTER_MODEL |
FILTER_MODEL |
Pour changer de modèle, définissez la variable dans .env — aucune modification du code n'est nécessaire.
Mise à jour
sudo -u voixdupeuple bash -c "
cd /opt/voix-du-peuple
git pull origin main
# Dépendances Python
.venv/bin/pip install -r artifacts/flask-api/requirements.txt
# Rebuild frontend
pnpm install --frozen-lockfile
cd artifacts/voix-du-peuple && pnpm build
"
# Redémarrer le service (les migrations DB s'appliquent automatiquement)
sudo systemctl restart voix-du-peuple-api
sudo systemctl reload nginx
Logs et supervision
# Logs API Flask (journald + fichiers)
sudo journalctl -u voix-du-peuple-api -f
sudo tail -f /var/log/voix-du-peuple/api-error.log
sudo tail -f /var/log/voix-du-peuple/api-access.log
# Logs Nginx
sudo tail -f /var/log/nginx/error.log
sudo tail -f /var/log/nginx/access.log
# Statut des services
sudo systemctl status voix-du-peuple-api
sudo systemctl status nginx
sudo systemctl status postgresql-15
Dépannage fréquent
L'API répond 502 Bad Gateway
# Vérifier que Gunicorn tourne
sudo systemctl status voix-du-peuple-api
# Vérifier SELinux
sudo setsebool -P httpd_can_network_connect 1
# Tester Gunicorn directement
curl http://127.0.0.1:8000/api/healthz
Erreur "could not connect to server" (PostgreSQL)
sudo systemctl status postgresql-15
psql -U voixdupeuple -h 127.0.0.1 -d voixdupeuple -c "\dt"
# Vérifier DATABASE_URL dans .env
Erreur Mistral AI API
# Vérifier la clé dans .env
grep MISTRAL_API_KEY /opt/voix-du-peuple/.env
# Tester l'accès à l'API Mistral
curl https://api.mistral.ai/v1/models \
-H "Authorization: Bearer $(grep MISTRAL_API_KEY /opt/voix-du-peuple/.env | cut -d= -f2)"
Le frontend affiche une page blanche
# Vérifier que le build existe
ls /opt/voix-du-peuple/artifacts/voix-du-peuple/dist/public/
# Vérifier les droits
sudo chown -R voixdupeuple:voixdupeuple \
/opt/voix-du-peuple/artifacts/voix-du-peuple/dist/
Les variables VITE_* ne sont pas prises en compte
Les variables préfixées VITE_ sont intégrées au moment du build, pas à l'exécution.
Après toute modification de VITE_APP_URL ou VITE_HCAPTCHA_SITE_KEY, relancez le build :
cd /opt/voix-du-peuple/artifacts/voix-du-peuple && pnpm build