# 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 ```