f704f48e72
- 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>
440 lines
13 KiB
Markdown
440 lines
13 KiB
Markdown
# 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_KEY` requis) ou tout fournisseur compatible OpenAI
|
|
|
|
> **Note production :** En déploiement exposé à Internet, placer HAProxy devant Nginx pour la
|
|
> terminaison TLS. Voir `ARCHITECTURE.md` pour le schéma complet. Dans cette configuration,
|
|
> Nginx écoute sur le port 8080 (fichier `deploy/nginx.conf`) et HAProxy forward sur ce port.
|
|
> Pour un déploiement standalone sans HAProxy, changez `listen 8080;` en `listen 80;` dans
|
|
> `deploy/nginx.conf` et activez HTTPS avec certbot (section 12).
|
|
|
|
---
|
|
|
|
## Prérequis
|
|
|
|
```bash
|
|
sudo dnf update -y
|
|
sudo dnf install -y git curl wget epel-release
|
|
```
|
|
|
|
---
|
|
|
|
## 1. Installer les dépendances système
|
|
|
|
### Python 3.11
|
|
```bash
|
|
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)
|
|
```bash
|
|
curl -fsSL https://rpm.nodesource.com/setup_20.x | sudo bash -
|
|
sudo dnf install -y nodejs
|
|
node --version # doit afficher v20.x.x
|
|
```
|
|
|
|
### pnpm
|
|
```bash
|
|
npm install -g pnpm
|
|
pnpm --version
|
|
```
|
|
|
|
### PostgreSQL 15
|
|
```bash
|
|
sudo dnf install -y postgresql15-server postgresql15
|
|
sudo /usr/pgsql-15/bin/postgresql-15-setup initdb
|
|
sudo systemctl enable --now postgresql-15
|
|
```
|
|
|
|
### Nginx
|
|
```bash
|
|
sudo dnf install -y nginx
|
|
sudo systemctl enable nginx
|
|
```
|
|
|
|
---
|
|
|
|
## 2. Configurer PostgreSQL
|
|
|
|
```bash
|
|
sudo -u postgres psql
|
|
```
|
|
|
|
Dans psql :
|
|
```sql
|
|
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
|
|
```
|
|
|
|
```bash
|
|
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
|
|
|
|
```bash
|
|
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
|
|
|
|
```bash
|
|
sudo -u voixdupeuple git clone \
|
|
https://votre-depot.example.com/voix-du-peuple.git \
|
|
/opt/voix-du-peuple
|
|
```
|
|
|
|
---
|
|
|
|
## 5. Configurer les variables d'environnement
|
|
|
|
```bash
|
|
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 :
|
|
|
|
```env
|
|
# 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 :
|
|
```bash
|
|
sudo chmod 600 /opt/voix-du-peuple/.env
|
|
sudo chown voixdupeuple:voixdupeuple /opt/voix-du-peuple/.env
|
|
```
|
|
|
|
---
|
|
|
|
## 6. Installer les dépendances Python
|
|
|
|
```bash
|
|
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
|
|
|
|
```bash
|
|
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)
|
|
|
|
```bash
|
|
# 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 :
|
|
```bash
|
|
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.
|
|
|
|
```bash
|
|
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
|
|
|
|
```bash
|
|
# 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 :
|
|
```bash
|
|
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`.
|
|
|
|
```bash
|
|
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
|
|
|
|
```bash
|
|
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
|
|
|
|
```bash
|
|
# 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
|
|
```bash
|
|
# 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)
|
|
```bash
|
|
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
|
|
```bash
|
|
# 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
|
|
```bash
|
|
# 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 :
|
|
```bash
|
|
cd /opt/voix-du-peuple/artifacts/voix-du-peuple && pnpm build
|
|
```
|