Files
la-voix-du-peuple/DEPLOIEMENT.md
T
billisdead f704f48e72 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>
2026-06-24 08:58:08 +02:00

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

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