Rewrite DEPLOIEMENT.md to match actual codebase and stack
- 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>
This commit is contained in:
+126
-108
@@ -1,5 +1,5 @@
|
||||
# Guide d'auto-hébergement — La Voix du Peuple
|
||||
## RockyLinux 9 (ou RHEL 9, AlmaLinux 9)
|
||||
## Rocky Linux 9 (ou RHEL 9, AlmaLinux 9)
|
||||
|
||||
---
|
||||
|
||||
@@ -18,17 +18,20 @@ Internet
|
||||
- **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)
|
||||
- **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
|
||||
# Mettre à jour le système
|
||||
sudo dnf update -y
|
||||
|
||||
# Outils de base
|
||||
sudo dnf install -y git curl wget epel-release
|
||||
```
|
||||
|
||||
@@ -73,21 +76,22 @@ sudo systemctl enable nginx
|
||||
## 2. Configurer PostgreSQL
|
||||
|
||||
```bash
|
||||
# Connexion en tant que postgres
|
||||
sudo -u postgres psql
|
||||
```
|
||||
|
||||
-- Dans 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` pour autoriser la connexion locale par mot de passe :
|
||||
Éditez `/var/lib/pgsql/15/data/pg_hba.conf` — remplacez `ident` par `scram-sha-256` sur
|
||||
la ligne `127.0.0.1/32` :
|
||||
|
||||
```
|
||||
# Remplacez "ident" par "md5" sur la ligne 127.0.0.1/32 :
|
||||
host all all 127.0.0.1/32 md5
|
||||
host all all 127.0.0.1/32 scram-sha-256
|
||||
```
|
||||
|
||||
```bash
|
||||
@@ -109,13 +113,12 @@ sudo chown voixdupeuple:voixdupeuple /opt/voix-du-peuple
|
||||
|
||||
---
|
||||
|
||||
## 4. Cloner le dépôt depuis Gitea
|
||||
## 4. Cloner le dépôt
|
||||
|
||||
```bash
|
||||
sudo -u voixdupeuple bash -c "
|
||||
cd /opt
|
||||
git clone https://votre-gitea.example.com/utilisateur/voix-du-peuple.git voix-du-peuple
|
||||
"
|
||||
sudo -u voixdupeuple git clone \
|
||||
https://votre-depot.example.com/voix-du-peuple.git \
|
||||
/opt/voix-du-peuple
|
||||
```
|
||||
|
||||
---
|
||||
@@ -123,27 +126,24 @@ sudo -u voixdupeuple bash -c "
|
||||
## 5. Configurer les variables d'environnement
|
||||
|
||||
```bash
|
||||
sudo -u voixdupeuple bash -c "
|
||||
cp /opt/voix-du-peuple/.env.example /opt/voix-du-peuple/.env
|
||||
"
|
||||
|
||||
# Éditer le fichier .env
|
||||
sudo -u voixdupeuple cp /opt/voix-du-peuple/.env.example /opt/voix-du-peuple/.env
|
||||
sudo nano /opt/voix-du-peuple/.env
|
||||
```
|
||||
|
||||
Remplissez au minimum :
|
||||
Valeurs obligatoires à renseigner :
|
||||
|
||||
```env
|
||||
# Base de données
|
||||
DATABASE_URL=postgresql://voixdupeuple:CHANGEME_MOT_DE_PASSE_FORT@127.0.0.1:5432/voixdupeuple
|
||||
OPENAI_API_KEY=sk-VOTRE_CLE_OPENAI
|
||||
SESSION_SECRET=GENEREZ_UNE_VALEUR_ALEATOIRE_ICI
|
||||
PORT=8000
|
||||
FLASK_ENV=production
|
||||
```
|
||||
|
||||
Générez le `SESSION_SECRET` :
|
||||
```bash
|
||||
python3.11 -c "import secrets; print(secrets.token_hex(32))"
|
||||
# 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 :
|
||||
@@ -169,18 +169,9 @@ sudo -u voixdupeuple bash -c "
|
||||
|
||||
## 7. Initialiser la base de données
|
||||
|
||||
```bash
|
||||
sudo -u voixdupeuple bash -c "
|
||||
cd /opt/voix-du-peuple/artifacts/flask-api
|
||||
source /opt/voix-du-peuple/.env
|
||||
export DATABASE_URL OPENAI_API_KEY SESSION_SECRET FLASK_ENV PORT
|
||||
/opt/voix-du-peuple/.venv/bin/python3 -c '
|
||||
from database import init_db
|
||||
init_db()
|
||||
print(\"Base de données initialisée.\")
|
||||
'
|
||||
"
|
||||
```
|
||||
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.
|
||||
|
||||
---
|
||||
|
||||
@@ -190,14 +181,17 @@ print(\"Base de données initialisée.\")
|
||||
sudo -u voixdupeuple bash -c "
|
||||
cd /opt/voix-du-peuple
|
||||
pnpm install --frozen-lockfile
|
||||
|
||||
cd artifacts/voix-du-peuple
|
||||
pnpm exec vite build --config vite.config.selfhost.ts
|
||||
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)
|
||||
@@ -229,14 +223,16 @@ curl http://127.0.0.1:8000/api/healthz
|
||||
|
||||
## 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
|
||||
# Copier la config Nginx
|
||||
sudo cp /opt/voix-du-peuple/deploy/nginx.conf \
|
||||
/etc/nginx/conf.d/voix-du-peuple.conf
|
||||
|
||||
# Éditez le fichier pour mettre votre nom de domaine
|
||||
sudo nano /etc/nginx/conf.d/voix-du-peuple.conf
|
||||
# Remplacez voix-du-peuple.example.com par votre domaine
|
||||
# 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
|
||||
@@ -250,32 +246,35 @@ 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 à se connecter à Gunicorn :
|
||||
SELinux — autoriser Nginx à proxyfier vers Gunicorn :
|
||||
```bash
|
||||
sudo setsebool -P httpd_can_network_connect 1
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 12. (Recommandé) HTTPS avec Let's Encrypt
|
||||
## 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 admin@example.com --agree-tos --no-eff-email
|
||||
--email contact@example.com --agree-tos --no-eff-email
|
||||
|
||||
# Renouvellement automatique (déjà configuré par certbot)
|
||||
# Vérifier le renouvellement automatique
|
||||
sudo systemctl status certbot-renew.timer
|
||||
```
|
||||
|
||||
Puis décommentez le bloc HTTPS dans `/etc/nginx/conf.d/voix-du-peuple.conf`.
|
||||
|
||||
---
|
||||
|
||||
## Structure du projet
|
||||
@@ -283,28 +282,31 @@ Puis décommentez le bloc HTTPS dans `/etc/nginx/conf.d/voix-du-peuple.conf`.
|
||||
```
|
||||
voix-du-peuple/
|
||||
├── artifacts/
|
||||
│ ├── flask-api/ # Backend Python Flask
|
||||
│ │ ├── app.py # Application principale + routes
|
||||
│ │ ├── ai_agent.py # Agent IA (filtrage + synthèse)
|
||||
│ │ ├── legal_framework.py # Prompts ancrés dans le droit international
|
||||
│ │ ├── database.py # Accès PostgreSQL (psycopg2)
|
||||
│ │ ├── requirements.txt # Dépendances Python
|
||||
│ │ └── start.sh # Démarrage développement
|
||||
│ └── voix-du-peuple/ # Frontend React + Vite
|
||||
│ ├── 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.tsx, about.tsx
|
||||
│ │ ├── components/ # Composants UI (shadcn/ui + radix)
|
||||
│ │ └── App.tsx # Routing principal
|
||||
│ └── vite.config.ts # Config Vite (développement + production)
|
||||
│ │ ├── 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
|
||||
│ ├── api-client-react/ # Hooks React Query générés
|
||||
│ └── db/ # Schéma Drizzle (référence)
|
||||
│ ├── 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
|
||||
├── .env.example # Variables d'environnement (modèle)
|
||||
└── DEPLOIEMENT.md # Ce fichier
|
||||
│ └── 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
|
||||
```
|
||||
|
||||
---
|
||||
@@ -313,32 +315,36 @@ voix-du-peuple/
|
||||
|
||||
| Variable | Obligatoire | Description |
|
||||
|----------|-------------|-------------|
|
||||
| `DATABASE_URL` | Oui | URL PostgreSQL complète |
|
||||
| `OPENAI_API_KEY` | Oui | Clé API OpenAI (sk-...) |
|
||||
| `OPENAI_BASE_URL` | Non | Proxy OpenAI compatible (Ollama, Azure, etc.) |
|
||||
| `OPENAI_FILTER_MODEL` | Non | Modèle de filtrage (défaut : `gpt-4o-mini`) |
|
||||
| `OPENAI_SYNTHESIS_MODEL` | Non | Modèle de synthèse (défaut : `gpt-4o`) |
|
||||
| `SESSION_SECRET` | Oui | Clé secrète Flask (min 32 caractères aléatoires) |
|
||||
| `PORT` | Non | Port Gunicorn (défaut : 8000) |
|
||||
| `FLASK_ENV` | Non | `production` ou `development` |
|
||||
| `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 utilisés
|
||||
## Modèles IA
|
||||
|
||||
| Fonction | Modèle par défaut | Variable d'environnement |
|
||||
|----------|-------------------|--------------------------|
|
||||
| Filtrage des idées | `mistral-small-latest` | `FILTER_MODEL` |
|
||||
| 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 les modèles, éditez `artifacts/flask-api/ai_agent.py` :
|
||||
```python
|
||||
# Filtrage (ligne ~56)
|
||||
model="gpt-4o-mini", # ou tout modèle OpenAI compatible
|
||||
|
||||
# Synthèse (ligne ~104)
|
||||
model="gpt-4o", # ou tout modèle OpenAI compatible
|
||||
```
|
||||
Pour changer de modèle, définissez la variable dans `.env` — aucune modification du code n'est nécessaire.
|
||||
|
||||
---
|
||||
|
||||
@@ -349,16 +355,15 @@ sudo -u voixdupeuple bash -c "
|
||||
cd /opt/voix-du-peuple
|
||||
git pull origin main
|
||||
|
||||
# Mise à jour des dépendances Python si nécessaire
|
||||
# Dépendances Python
|
||||
.venv/bin/pip install -r artifacts/flask-api/requirements.txt
|
||||
|
||||
# Mise à jour des dépendances Node et rebuild frontend
|
||||
# Rebuild frontend
|
||||
pnpm install --frozen-lockfile
|
||||
cd artifacts/voix-du-peuple
|
||||
pnpm exec vite build --config vite.config.selfhost.ts
|
||||
cd artifacts/voix-du-peuple && pnpm build
|
||||
"
|
||||
|
||||
# Redémarrer le service
|
||||
# Redémarrer le service (les migrations DB s'appliquent automatiquement)
|
||||
sudo systemctl restart voix-du-peuple-api
|
||||
sudo systemctl reload nginx
|
||||
```
|
||||
@@ -368,10 +373,10 @@ sudo systemctl reload nginx
|
||||
## Logs et supervision
|
||||
|
||||
```bash
|
||||
# Logs API Flask
|
||||
# Logs API Flask (journald + fichiers)
|
||||
sudo journalctl -u voix-du-peuple-api -f
|
||||
tail -f /var/log/voix-du-peuple/api-error.log
|
||||
tail -f /var/log/voix-du-peuple/api-access.log
|
||||
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
|
||||
@@ -391,31 +396,44 @@ sudo systemctl status postgresql-15
|
||||
```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
|
||||
# Vérifier que PostgreSQL tourne
|
||||
sudo systemctl status postgresql-15
|
||||
# Vérifier l'URL de connexion dans .env
|
||||
psql -U voixdupeuple -h 127.0.0.1 -d voixdupeuple
|
||||
psql -U voixdupeuple -h 127.0.0.1 -d voixdupeuple -c "\dt"
|
||||
# Vérifier DATABASE_URL dans .env
|
||||
```
|
||||
|
||||
### Erreur OpenAI API
|
||||
### Erreur Mistral AI API
|
||||
```bash
|
||||
# Vérifier la clé dans .env
|
||||
grep OPENAI_API_KEY /opt/voix-du-peuple/.env
|
||||
# Tester directement
|
||||
curl https://api.openai.com/v1/models \
|
||||
-H "Authorization: Bearer $OPENAI_API_KEY"
|
||||
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/
|
||||
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
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user