# Guide d'auto-hébergement — La Voix du Peuple ## RockyLinux 9 (ou RHEL 9, AlmaLinux 9) --- ## Architecture ``` Internet │ ▼ [Nginx] ─── /api/* ──► [Gunicorn + Flask] ──► [PostgreSQL] │ │ └─── /* ──► [React SPA] └──► [OpenAI 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) --- ## Prérequis ```bash # Mettre à jour le système sudo dnf update -y # Outils de base 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 # Connexion en tant que postgres 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` pour autoriser la connexion locale par mot de passe : ``` # Remplacez "ident" par "md5" sur la ligne 127.0.0.1/32 : host all all 127.0.0.1/32 md5 ``` ```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 depuis Gitea ```bash sudo -u voixdupeuple bash -c " cd /opt git clone https://votre-gitea.example.com/utilisateur/voix-du-peuple.git voix-du-peuple " ``` --- ## 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 nano /opt/voix-du-peuple/.env ``` Remplissez au minimum : ```env 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))" ``` 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 ```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.\") ' " ``` --- ## 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 exec vite build --config vite.config.selfhost.ts " ``` Les fichiers statiques sont générés dans `artifacts/voix-du-peuple/dist/public/`. --- ## 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 ```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 # Tester la configuration sudo nginx -t # Recharger Nginx sudo systemctl reload nginx ``` --- ## 11. Ouvrir le pare-feu ```bash sudo firewall-cmd --permanent --add-service=http sudo firewall-cmd --permanent --add-service=https sudo firewall-cmd --reload ``` SELinux — autoriser Nginx à se connecter à Gunicorn : ```bash sudo setsebool -P httpd_can_network_connect 1 ``` --- ## 12. (Recommandé) HTTPS avec Let's Encrypt ```bash sudo dnf install -y certbot python3-certbot-nginx sudo certbot --nginx -d voix-du-peuple.example.com \ --email admin@example.com --agree-tos --no-eff-email # Renouvellement automatique (déjà configuré par certbot) sudo systemctl status certbot-renew.timer ``` Puis décommentez le bloc HTTPS dans `/etc/nginx/conf.d/voix-du-peuple.conf`. --- ## Structure du projet ``` 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 │ ├── src/ │ │ ├── pages/ # home.tsx, about.tsx │ │ ├── components/ # Composants UI (shadcn/ui + radix) │ │ └── App.tsx # Routing principal │ └── vite.config.ts # Config Vite (développement + production) ├── lib/ │ ├── api-spec/ # Spécification OpenAPI │ ├── api-client-react/ # Hooks React Query générés │ └── db/ # Schéma Drizzle (référence) ├── deploy/ │ ├── voix-du-peuple-api.service # Service systemd Gunicorn │ └── nginx.conf # Configuration Nginx ├── .env.example # Variables d'environnement (modèle) └── DEPLOIEMENT.md # Ce fichier ``` --- ## Variables d'environnement complètes | 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` | --- ## Modèles IA utilisés | Fonction | Modèle par défaut | Variable d'environnement | |----------|-------------------|--------------------------| | Filtrage des idées | `mistral-small-latest` | `FILTER_MODEL` | | Synthèse collective | `mistral-large-latest` | `SYNTHESIS_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 ``` --- ## Mise à jour ```bash sudo -u voixdupeuple bash -c " cd /opt/voix-du-peuple git pull origin main # Mise à jour des dépendances Python si nécessaire .venv/bin/pip install -r artifacts/flask-api/requirements.txt # Mise à jour des dépendances Node et rebuild frontend pnpm install --frozen-lockfile cd artifacts/voix-du-peuple pnpm exec vite build --config vite.config.selfhost.ts " # Redémarrer le service sudo systemctl restart voix-du-peuple-api sudo systemctl reload nginx ``` --- ## Logs et supervision ```bash # Logs API Flask 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 # 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 ``` ### 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 ``` ### Erreur OpenAI 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" ``` ### 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/ ```