Add self-hosting capabilities and deployment guide for the application
Implement self-hosting for RockyLinux by adding systemd and Nginx configurations, updating API models to support standard OpenAI keys, and providing a comprehensive deployment guide. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 923ae0e3-a363-4db8-b04a-e8baca2a1330 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: f8aa455f-f180-4964-94dd-11cfb1a42383 Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/8af7d2ec-2cc3-4ece-8af3-9f071488d072/923ae0e3-a363-4db8-b04a-e8baca2a1330/VnHW0bR Replit-Helium-Checkpoint-Created: true
This commit is contained in:
@@ -0,0 +1,8 @@
|
|||||||
|
uploads = []
|
||||||
|
generated = []
|
||||||
|
|
||||||
|
[[outputs]]
|
||||||
|
id = "x31gBYdQKnMGZriV7IJ1v"
|
||||||
|
uri = "file://DEPLOIEMENT.md"
|
||||||
|
type = "text"
|
||||||
|
title = "Guide d'auto-hébergement RockyLinux"
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
# ─── Base de données PostgreSQL ──────────────────────────────────────────────
|
||||||
|
# Format : postgresql://utilisateur:motdepasse@hote:port/nomdb
|
||||||
|
DATABASE_URL=postgresql://voixdupeuple:CHANGEME@localhost:5432/voixdupeuple
|
||||||
|
|
||||||
|
# ─── Clé API OpenAI ──────────────────────────────────────────────────────────
|
||||||
|
# Obtenez votre clé sur https://platform.openai.com/api-keys
|
||||||
|
OPENAI_API_KEY=sk-...
|
||||||
|
|
||||||
|
# ─── (Optionnel) Proxy OpenAI compatible ─────────────────────────────────────
|
||||||
|
# Décommentez si vous utilisez un proxy (Ollama avec OpenAI compat, Azure, etc.)
|
||||||
|
# OPENAI_BASE_URL=https://votre-proxy.example.com/v1
|
||||||
|
|
||||||
|
# ─── Modèles IA (optionnel — valeurs par défaut recommandées) ────────────────
|
||||||
|
# OPENAI_FILTER_MODEL=gpt-4o-mini # Modèle de filtrage (rapide, économique)
|
||||||
|
# OPENAI_SYNTHESIS_MODEL=gpt-4o # Modèle de synthèse (haute qualité)
|
||||||
|
|
||||||
|
# ─── Flask ────────────────────────────────────────────────────────────────────
|
||||||
|
FLASK_ENV=production
|
||||||
|
PORT=8000
|
||||||
|
|
||||||
|
# ─── Session (générez avec: python3 -c "import secrets; print(secrets.token_hex(32))") ───
|
||||||
|
SESSION_SECRET=CHANGEZ_CE_SECRET_AVEC_UNE_VALEUR_ALEATOIRE_LONGUE
|
||||||
+42
-36
@@ -1,49 +1,55 @@
|
|||||||
# See https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files for more about ignoring files.
|
# Dépendances
|
||||||
|
node_modules/
|
||||||
|
.pythonlibs/
|
||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
|
*.pyo
|
||||||
|
*.pyd
|
||||||
|
.Python
|
||||||
|
pip-log.txt
|
||||||
|
|
||||||
# compiled output
|
# Build
|
||||||
dist
|
dist/
|
||||||
tmp
|
tmp/
|
||||||
out-tsc
|
out-tsc/
|
||||||
*.tsbuildinfo
|
*.tsbuildinfo
|
||||||
.expo
|
build/
|
||||||
.expo-shared
|
|
||||||
|
|
||||||
# dependencies
|
# Environnement
|
||||||
node_modules
|
.env
|
||||||
|
.env.local
|
||||||
|
.env.*.local
|
||||||
|
|
||||||
# IDEs and editors
|
# Cache Replit
|
||||||
/.idea
|
.cache/
|
||||||
.project
|
.local/
|
||||||
.classpath
|
.upm/
|
||||||
.c9/
|
uv.lock
|
||||||
*.launch
|
|
||||||
.settings/
|
|
||||||
*.sublime-workspace
|
|
||||||
|
|
||||||
# IDE - VSCode
|
# Expo / mobile
|
||||||
|
.expo/
|
||||||
|
.expo-shared/
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
*.log
|
||||||
|
logs/
|
||||||
|
|
||||||
|
# IDEs
|
||||||
|
.idea/
|
||||||
.vscode/*
|
.vscode/*
|
||||||
!.vscode/settings.json
|
!.vscode/settings.json
|
||||||
!.vscode/tasks.json
|
!.vscode/tasks.json
|
||||||
!.vscode/launch.json
|
!.vscode/launch.json
|
||||||
!.vscode/extensions.json
|
!.vscode/extensions.json
|
||||||
|
*.sublime-workspace
|
||||||
|
.project
|
||||||
|
.classpath
|
||||||
|
.c9/
|
||||||
|
*.launch
|
||||||
|
.settings/
|
||||||
|
|
||||||
# misc
|
# Système
|
||||||
/.sass-cache
|
|
||||||
/connect.lock
|
|
||||||
/coverage
|
|
||||||
/libpeerconnection.log
|
|
||||||
npm-debug.log
|
|
||||||
yarn-error.log
|
|
||||||
testem.log
|
|
||||||
/typings
|
|
||||||
|
|
||||||
# System Files
|
|
||||||
.DS_Store
|
.DS_Store
|
||||||
Thumbs.db
|
Thumbs.db
|
||||||
|
*.sass-cache
|
||||||
.cursor/rules/nx-rules.mdc
|
coverage/
|
||||||
.github/instructions/nx.instructions.md
|
|
||||||
|
|
||||||
# Replit
|
|
||||||
.cache/
|
|
||||||
.local/
|
|
||||||
|
|||||||
+424
@@ -0,0 +1,424 @@
|
|||||||
|
# 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** : API OpenAI (clé standard, pas de proxy Replit)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 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 Replit (développement)
|
||||||
|
│ └── vite.config.selfhost.ts # Config auto-hébergement (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 | Configurable dans |
|
||||||
|
|----------|-------------------|-------------------|
|
||||||
|
| Filtrage des idées | `gpt-4o-mini` | `ai_agent.py` ligne 56 |
|
||||||
|
| Synthèse collective | `gpt-4o` | `ai_agent.py` ligne 104 |
|
||||||
|
|
||||||
|
> **Note :** Les modèles `gpt-5-mini` et `gpt-5.2` sont les noms utilisés sur la plateforme Replit. En auto-hébergement avec l'API OpenAI standard, utilisez `gpt-4o-mini` et `gpt-4o`.
|
||||||
|
|
||||||
|
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/
|
||||||
|
```
|
||||||
@@ -14,16 +14,31 @@ _client: OpenAI | None = None
|
|||||||
|
|
||||||
|
|
||||||
def get_client() -> OpenAI:
|
def get_client() -> OpenAI:
|
||||||
|
"""
|
||||||
|
Supporte deux modes :
|
||||||
|
- Replit AI Integration : AI_INTEGRATIONS_OPENAI_BASE_URL + AI_INTEGRATIONS_OPENAI_API_KEY
|
||||||
|
- Auto-hébergement standard : OPENAI_API_KEY (+ OPENAI_BASE_URL optionnel pour proxy)
|
||||||
|
"""
|
||||||
global _client
|
global _client
|
||||||
if _client is None:
|
if _client is None:
|
||||||
base_url = os.environ.get("AI_INTEGRATIONS_OPENAI_BASE_URL")
|
replit_base = os.environ.get("AI_INTEGRATIONS_OPENAI_BASE_URL")
|
||||||
api_key = os.environ.get("AI_INTEGRATIONS_OPENAI_API_KEY")
|
replit_key = os.environ.get("AI_INTEGRATIONS_OPENAI_API_KEY")
|
||||||
if not base_url or not api_key:
|
std_key = os.environ.get("OPENAI_API_KEY")
|
||||||
|
std_base = os.environ.get("OPENAI_BASE_URL")
|
||||||
|
|
||||||
|
if replit_base and replit_key:
|
||||||
|
_client = OpenAI(base_url=replit_base, api_key=replit_key)
|
||||||
|
elif std_key:
|
||||||
|
kwargs = {"api_key": std_key}
|
||||||
|
if std_base:
|
||||||
|
kwargs["base_url"] = std_base
|
||||||
|
_client = OpenAI(**kwargs)
|
||||||
|
else:
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
"AI_INTEGRATIONS_OPENAI_BASE_URL et AI_INTEGRATIONS_OPENAI_API_KEY "
|
"Aucune clé IA configurée. "
|
||||||
"sont requis. Configurez les intégrations Replit AI."
|
"Définissez OPENAI_API_KEY dans le fichier .env "
|
||||||
|
"ou configurez les intégrations Replit AI."
|
||||||
)
|
)
|
||||||
_client = OpenAI(base_url=base_url, api_key=api_key)
|
|
||||||
return _client
|
return _client
|
||||||
|
|
||||||
|
|
||||||
@@ -34,8 +49,9 @@ def filter_idea(content: str) -> dict:
|
|||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
client = get_client()
|
client = get_client()
|
||||||
|
filter_model = os.environ.get("OPENAI_FILTER_MODEL", "gpt-4o-mini")
|
||||||
response = client.chat.completions.create(
|
response = client.chat.completions.create(
|
||||||
model="gpt-5-mini",
|
model=filter_model,
|
||||||
max_completion_tokens=300,
|
max_completion_tokens=300,
|
||||||
response_format={"type": "json_object"},
|
response_format={"type": "json_object"},
|
||||||
messages=[
|
messages=[
|
||||||
@@ -96,9 +112,10 @@ def synthesize_ideas(ideas: list[str]) -> str:
|
|||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
client = get_client()
|
client = get_client()
|
||||||
|
synthesis_model = os.environ.get("OPENAI_SYNTHESIS_MODEL", "gpt-4o")
|
||||||
ideas_text = "\n".join(f"{i + 1}. {idea}" for i, idea in enumerate(ideas))
|
ideas_text = "\n".join(f"{i + 1}. {idea}" for i, idea in enumerate(ideas))
|
||||||
response = client.chat.completions.create(
|
response = client.chat.completions.create(
|
||||||
model="gpt-5.2",
|
model=synthesis_model,
|
||||||
max_completion_tokens=1200,
|
max_completion_tokens=1200,
|
||||||
messages=[
|
messages=[
|
||||||
{"role": "system", "content": SYNTHESIS_PROMPT},
|
{"role": "system", "content": SYNTHESIS_PROMPT},
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
flask==3.1.1
|
bleach>=6.2.0
|
||||||
flask-cors==5.0.1
|
flask>=3.1.1
|
||||||
flask-limiter==3.9.4
|
flask-cors>=5.0.1
|
||||||
openai==1.77.0
|
flask-limiter>=3.9.4
|
||||||
psycopg2-binary==2.9.10
|
gunicorn>=23.0.0
|
||||||
python-dotenv==1.0.1
|
openai>=1.77.0
|
||||||
bleach==6.2.0
|
psycopg2-binary>=2.9.10
|
||||||
validators==0.34.0
|
python-dotenv>=1.0.1
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { defineConfig } from "vite";
|
||||||
|
import react from "@vitejs/plugin-react";
|
||||||
|
import tailwindcss from "@tailwindcss/vite";
|
||||||
|
import path from "path";
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
base: "/",
|
||||||
|
plugins: [
|
||||||
|
react(),
|
||||||
|
tailwindcss(),
|
||||||
|
],
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
"@": path.resolve(import.meta.dirname, "src"),
|
||||||
|
},
|
||||||
|
dedupe: ["react", "react-dom"],
|
||||||
|
},
|
||||||
|
root: path.resolve(import.meta.dirname),
|
||||||
|
build: {
|
||||||
|
outDir: path.resolve(import.meta.dirname, "dist/public"),
|
||||||
|
emptyOutDir: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name voix-du-peuple.example.com;
|
||||||
|
|
||||||
|
# Redirection HTTPS (décommentez après avoir configuré Certbot)
|
||||||
|
# return 301 https://$host$request_uri;
|
||||||
|
|
||||||
|
root /opt/voix-du-peuple/artifacts/voix-du-peuple/dist/public;
|
||||||
|
index index.html;
|
||||||
|
|
||||||
|
# Sécurité
|
||||||
|
add_header X-Content-Type-Options "nosniff" always;
|
||||||
|
add_header X-Frame-Options "DENY" always;
|
||||||
|
add_header X-XSS-Protection "1; mode=block" always;
|
||||||
|
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
||||||
|
|
||||||
|
# API Flask — proxy vers Gunicorn
|
||||||
|
location /api/ {
|
||||||
|
proxy_pass http://127.0.0.1:8000;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
proxy_read_timeout 60s;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Frontend React — Single Page Application
|
||||||
|
location / {
|
||||||
|
try_files $uri $uri/ /index.html;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Assets statiques — cache long
|
||||||
|
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff2?)$ {
|
||||||
|
expires 1y;
|
||||||
|
add_header Cache-Control "public, immutable";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# HTTPS — décommentez et adaptez après avoir obtenu votre certificat SSL
|
||||||
|
# server {
|
||||||
|
# listen 443 ssl http2;
|
||||||
|
# server_name voix-du-peuple.example.com;
|
||||||
|
#
|
||||||
|
# ssl_certificate /etc/letsencrypt/live/voix-du-peuple.example.com/fullchain.pem;
|
||||||
|
# ssl_certificate_key /etc/letsencrypt/live/voix-du-peuple.example.com/privkey.pem;
|
||||||
|
# ssl_protocols TLSv1.2 TLSv1.3;
|
||||||
|
# ssl_ciphers HIGH:!aNULL:!MD5;
|
||||||
|
#
|
||||||
|
# add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
|
||||||
|
# add_header X-Content-Type-Options "nosniff" always;
|
||||||
|
# add_header X-Frame-Options "DENY" always;
|
||||||
|
# add_header X-XSS-Protection "1; mode=block" always;
|
||||||
|
# add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
||||||
|
#
|
||||||
|
# root /opt/voix-du-peuple/artifacts/voix-du-peuple/dist/public;
|
||||||
|
# index index.html;
|
||||||
|
#
|
||||||
|
# location /api/ {
|
||||||
|
# proxy_pass http://127.0.0.1:8000;
|
||||||
|
# proxy_set_header Host $host;
|
||||||
|
# proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
# proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
# proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
# proxy_read_timeout 60s;
|
||||||
|
# }
|
||||||
|
#
|
||||||
|
# location / {
|
||||||
|
# try_files $uri $uri/ /index.html;
|
||||||
|
# }
|
||||||
|
#
|
||||||
|
# location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff2?)$ {
|
||||||
|
# expires 1y;
|
||||||
|
# add_header Cache-Control "public, immutable";
|
||||||
|
# }
|
||||||
|
# }
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=La Voix du Peuple — API Flask
|
||||||
|
After=network.target postgresql.service
|
||||||
|
Requires=postgresql.service
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=exec
|
||||||
|
User=voixdupeuple
|
||||||
|
Group=voixdupeuple
|
||||||
|
WorkingDirectory=/opt/voix-du-peuple/artifacts/flask-api
|
||||||
|
EnvironmentFile=/opt/voix-du-peuple/.env
|
||||||
|
ExecStart=/opt/voix-du-peuple/.venv/bin/gunicorn \
|
||||||
|
--workers 4 \
|
||||||
|
--bind 127.0.0.1:8000 \
|
||||||
|
--access-logfile /var/log/voix-du-peuple/api-access.log \
|
||||||
|
--error-logfile /var/log/voix-du-peuple/api-error.log \
|
||||||
|
--timeout 60 \
|
||||||
|
app:app
|
||||||
|
Restart=always
|
||||||
|
RestartSec=5
|
||||||
|
|
||||||
|
# Sécurité
|
||||||
|
NoNewPrivileges=true
|
||||||
|
PrivateTmp=true
|
||||||
|
ProtectSystem=strict
|
||||||
|
ReadWritePaths=/var/log/voix-du-peuple
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
Reference in New Issue
Block a user