From e279dab70a17707004e6082b2329b81005b92bcf Mon Sep 17 00:00:00 2001 From: pironantoine <57062554-pironantoine@users.noreply.replit.com> Date: Sat, 4 Apr 2026 14:26:14 +0000 Subject: [PATCH] Add a comprehensive Rocky Linux installation guide and streamline domain configuration Add a detailed guide for installing the application on Rocky Linux, including systemd service setup and Nginx configuration. Streamline domain setting by introducing a script to update environment variables and rebuild the frontend, and remove the URL input from the flyer component. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 923ae0e3-a363-4db8-b04a-e8baca2a1330 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: 3d999b96-66af-4728-92b9-3a39ade05f44 Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/8af7d2ec-2cc3-4ece-8af3-9f071488d072/923ae0e3-a363-4db8-b04a-e8baca2a1330/qCk7LE3 Replit-Helium-Checkpoint-Created: true --- .agents/agent_assets_metadata.toml | 6 + artifacts/voix-du-peuple/src/pages/flyer.tsx | 58 +- deploy/nginx.conf | 57 +- docs/INSTALL_ROCKY.md | 538 +++++++++++++++++++ scripts/set-domain.sh | 51 ++ 5 files changed, 623 insertions(+), 87 deletions(-) create mode 100644 docs/INSTALL_ROCKY.md create mode 100644 scripts/set-domain.sh diff --git a/.agents/agent_assets_metadata.toml b/.agents/agent_assets_metadata.toml index f6ccfda..b0ad16b 100644 --- a/.agents/agent_assets_metadata.toml +++ b/.agents/agent_assets_metadata.toml @@ -30,3 +30,9 @@ id = "kJNXgVnYp_LQmPcWr6Osb" uri = "file://docs/WIKI.md" type = "text" title = "Wiki v1.3" + +[[outputs]] +id = "4toQ5OnXtI6PicOZHDKnP" +uri = "file://docs/INSTALL_ROCKY.md" +type = "text" +title = "Guide d’installation RockyLinux" diff --git a/artifacts/voix-du-peuple/src/pages/flyer.tsx b/artifacts/voix-du-peuple/src/pages/flyer.tsx index 6804763..1ae7660 100644 --- a/artifacts/voix-du-peuple/src/pages/flyer.tsx +++ b/artifacts/voix-du-peuple/src/pages/flyer.tsx @@ -1,53 +1,27 @@ -import React, { useState } from "react"; +import React from "react"; import { QRCodeSVG } from "qrcode.react"; import { Printer, ExternalLink } from "lucide-react"; import { Button } from "@/components/ui/button"; -import { Input } from "@/components/ui/input"; import { Link } from "wouter"; -// ══════════════════════════════════════════════════════════ -// URL encodée dans le QR code — modifiez cette ligne -const DEFAULT_QR_URL = "https://lavoixdupeuple.fr"; -// ══════════════════════════════════════════════════════════ +// URL du QR code — définie via VITE_APP_URL dans le fichier .env +// Modifier .env puis exécuter : bash scripts/set-domain.sh https://votredomaine.fr +const QR_URL = (new URLSearchParams(window.location.search).get("url")) + || import.meta.env.VITE_APP_URL + || "https://lavoixdupeuple.fr"; export default function Flyer() { - const params = new URLSearchParams(window.location.search); - const [qrUrl, setQrUrl] = useState(params.get("url") || DEFAULT_QR_URL); - const [inputValue, setInputValue] = useState(qrUrl); - - const applyUrl = () => { - const trimmed = inputValue.trim(); - if (trimmed) setQrUrl(trimmed); - }; - return (
{/* Barre de contrôle — masquée à l'impression */} -
-

- Destination du QR code -

-
- setInputValue(e.target.value)} - onKeyDown={(e) => e.key === "Enter" && applyUrl()} - placeholder="https://lavoixdupeuple.fr" - className="font-mono text-sm" - /> - -
-
- - - - -
+
+ + + +
{/* Flyer imprimable */} @@ -83,7 +57,7 @@ export default function Flyer() {
- {qrUrl} + {QR_URL}
@@ -106,7 +80,7 @@ export default function Flyer() { className="text-lg font-bold text-primary tracking-wide" style={{ fontFamily: "'Bahnschrift', 'DIN Alternate', sans-serif" }} > - {qrUrl.replace(/^https?:\/\//, "")} + {QR_URL.replace(/^https?:\/\//, "")}

diff --git a/deploy/nginx.conf b/deploy/nginx.conf index b2c6f2c..4f104d3 100644 --- a/deploy/nginx.conf +++ b/deploy/nginx.conf @@ -1,9 +1,6 @@ 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; + listen 8080; + server_name _; # remplacer par votre nom de domaine si nécessaire root /opt/voix-du-peuple/artifacts/voix-du-peuple/dist/public; index index.html; @@ -14,7 +11,13 @@ server { add_header X-XSS-Protection "1; mode=block" always; add_header Referrer-Policy "strict-origin-when-cross-origin" always; - # API Flask — proxy vers Gunicorn + # Health check + location /health { + proxy_pass http://127.0.0.1:8000/health; + proxy_set_header Host $host; + } + + # API Flask — proxy vers Gunicorn (interne) location /api/ { proxy_pass http://127.0.0.1:8000; proxy_set_header Host $host; @@ -24,7 +27,7 @@ server { proxy_read_timeout 60s; } - # Frontend React — Single Page Application + # Frontend React — SPA location / { try_files $uri $uri/ /index.html; } @@ -34,42 +37,6 @@ server { 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"; -# } -# } + client_max_body_size 1m; +} diff --git a/docs/INSTALL_ROCKY.md b/docs/INSTALL_ROCKY.md new file mode 100644 index 0000000..de27447 --- /dev/null +++ b/docs/INSTALL_ROCKY.md @@ -0,0 +1,538 @@ +# Installation sur RockyLinux — La Voix du Peuple + +**Cible** : VM RockyLinux 9.x +**Port public** : HTTP 8080 (vous gérez le HTTPS en amont) +**Port interne** : Gunicorn 8000 (localhost uniquement) +**Répertoire d'installation** : `/opt/voix-du-peuple` +**Utilisateur système** : `voixdupeuple` + +--- + +## Table des matières + +1. [Prérequis](#1-prérequis) +2. [Préparation du serveur](#2-préparation-du-serveur) +3. [Installation des paquets](#3-installation-des-paquets) +4. [Utilisateur système](#4-utilisateur-système) +5. [Clonage du dépôt](#5-clonage-du-dépôt) +6. [Base de données PostgreSQL](#6-base-de-données-postgresql) +7. [Environnement Python](#7-environnement-python) +8. [Configuration du domaine et variables d'environnement](#8-configuration-du-domaine-et-variables-denvironnement) +9. [Build du frontend](#9-build-du-frontend) +10. [Service Gunicorn (systemd)](#10-service-gunicorn-systemd) +11. [Nginx](#11-nginx) +12. [Firewall](#12-firewall) +13. [Vérification finale](#13-vérification-finale) +14. [Disaster Recovery](#14-disaster-recovery) +15. [Maintenance et mises à jour](#15-maintenance-et-mises-à-jour) + +--- + +## 1. Prérequis + +- VM RockyLinux 9.x avec accès root (ou sudo) +- 1 vCPU minimum, 1 Go RAM (2 Go recommandés) +- 10 Go de disque +- Accès réseau sortant (pour cloner le dépôt et télécharger les paquets) +- Nom de domaine configuré (enregistrement DNS A pointant vers l'IP de la VM) +- Clé SSH ou accès console + +--- + +## 2. Préparation du serveur + +```bash +# Mise à jour complète du système +dnf update -y + +# Outils de base +dnf install -y curl wget git tar unzip vim + +# Activer le dépôt EPEL (nécessaire pour certains paquets) +dnf install -y epel-release +``` + +--- + +## 3. Installation des paquets + +### Python 3.11 + +```bash +dnf install -y python3.11 python3.11-pip python3.11-devel +python3.11 --version # doit afficher 3.11.x +``` + +### Node.js 20 et pnpm + +```bash +# Dépôt Node.js 20 LTS +curl -fsSL https://rpm.nodesource.com/setup_20.x | bash - +dnf install -y nodejs + +# pnpm +npm install -g pnpm +pnpm --version +node --version # doit afficher 20.x +``` + +### PostgreSQL 15 + +```bash +# Dépôt officiel PostgreSQL +dnf install -y https://download.postgresql.org/pub/repos/yum/reporpms/EL-9-x86_64/pgdg-redhat-repo-latest.noarch.rpm +dnf -qy module disable postgresql +dnf install -y postgresql15-server postgresql15 + +# Initialisation et démarrage +postgresql-15-setup initdb +systemctl enable postgresql-15 +systemctl start postgresql-15 +systemctl status postgresql-15 # doit afficher "active (running)" +``` + +### Nginx + +```bash +dnf install -y nginx +systemctl enable nginx +``` + +--- + +## 4. Utilisateur système + +```bash +# Créer un utilisateur dédié sans shell de connexion +useradd -r -s /sbin/nologin -d /opt/voix-du-peuple voixdupeuple + +# Répertoire de logs +mkdir -p /var/log/voix-du-peuple +chown voixdupeuple:voixdupeuple /var/log/voix-du-peuple +``` + +--- + +## 5. Clonage du dépôt + +```bash +# Créer le répertoire d'installation +mkdir -p /opt/voix-du-peuple +cd /opt/voix-du-peuple + +# Cloner depuis votre Gitea +git clone https://homegit.gyozamancave.fr/billisdead/la-voix-du-peuple.git . +# (ou via SSH si une clé est configurée sur votre serveur Gitea) + +# Rendre le répertoire accessible à l'utilisateur système +chown -R voixdupeuple:voixdupeuple /opt/voix-du-peuple +``` + +--- + +## 6. Base de données PostgreSQL + +```bash +# Se connecter en tant que postgres +su - postgres + +# Créer l'utilisateur et la base +psql < Le port 8000 (Gunicorn) ne doit PAS être ouvert — il n'est accessible que depuis localhost via Nginx. + +--- + +## 13. Vérification finale + +```bash +# Health check API (via Nginx) +curl http://localhost:8080/health +# {"status": "ok"} + +# Page principale +curl -s -o /dev/null -w "%{http_code}" http://localhost:8080/ +# 200 + +# Depuis l'extérieur (remplacer par l'IP de votre VM) +curl http://IP_DE_LA_VM:8080/health +``` + +**Checklist** : + +- [ ] `systemctl status postgresql-15` → active +- [ ] `systemctl status voix-du-peuple-api` → active +- [ ] `systemctl status nginx` → active +- [ ] `curl http://localhost:8080/health` → `{"status":"ok"}` +- [ ] L'interface s'affiche dans le navigateur sur `http://IP:8080` +- [ ] Le flyer `/flyer` affiche le bon nom de domaine dans le QR code + +--- + +## 14. Disaster Recovery + +### 14.1 Sauvegarde de la base de données + +```bash +# Dump complet (à planifier via cron) +pg_dump -U voix_user -h 127.0.0.1 voix_du_peuple > /opt/backups/voix_$(date +%Y%m%d_%H%M).sql + +# Automatiser avec cron (tous les jours à 3h) +crontab -e +# Ajouter : +# 0 3 * * * pg_dump -U voix_user -h 127.0.0.1 voix_du_peuple > /opt/backups/voix_$(date +\%Y\%m\%d).sql +``` + +```bash +mkdir -p /opt/backups +``` + +### 14.2 Restauration de la base de données + +```bash +# Stopper l'API pendant la restauration +systemctl stop voix-du-peuple-api + +# Vider et restaurer la base +psql -U voix_user -h 127.0.0.1 voix_du_peuple -c "DROP SCHEMA public CASCADE; CREATE SCHEMA public;" +psql -U voix_user -h 127.0.0.1 voix_du_peuple < /opt/backups/voix_20260401.sql + +# Redémarrer +systemctl start voix-du-peuple-api +``` + +### 14.3 Réinstallation complète depuis zéro + +Si la VM est perdue, répéter les étapes 1 à 13 puis restaurer la base depuis le dernier backup : + +```bash +psql -U voix_user -h 127.0.0.1 voix_du_peuple < /opt/backups/voix_DERNIER.sql +``` + +Les backups doivent être stockés **hors de la VM** (NFS, S3, objet distant…). + +### 14.4 Rollback de code + +```bash +cd /opt/voix-du-peuple + +# Voir l'historique des commits +git log --oneline -10 + +# Revenir à un commit précédent +git checkout + +# Reconstruire le frontend si nécessaire +bash scripts/set-domain.sh https://votredomaine.fr + +# Redémarrer l'API +systemctl restart voix-du-peuple-api +systemctl reload nginx +``` + +### 14.5 En cas de service hors ligne + +```bash +# Diagnostic rapide +systemctl status voix-du-peuple-api +journalctl -u voix-du-peuple-api -n 50 + +systemctl status nginx +journalctl -u nginx -n 20 + +systemctl status postgresql-15 + +# Relance forcée +systemctl restart voix-du-peuple-api +systemctl restart nginx +``` + +--- + +## 15. Maintenance et mises à jour + +### 15.1 Mise à jour du code + +```bash +cd /opt/voix-du-peuple + +# Récupérer les derniers commits depuis Gitea +git pull origin main + +# Si les dépendances Python ont changé (requirements.txt modifié) +.venv/bin/pip install -r artifacts/flask-api/requirements.txt + +# Si les dépendances Node ont changé (package.json modifié) +pnpm install + +# Reconstruire le frontend (toujours faire après un pull) +source .env && export VITE_APP_URL +pnpm --filter @workspace/voix-du-peuple run build --config vite.config.selfhost.ts + +# Appliquer +systemctl restart voix-du-peuple-api +systemctl reload nginx +``` + +### 15.2 Changer de nom de domaine + +```bash +cd /opt/voix-du-peuple +bash scripts/set-domain.sh https://nouveaudomaine.fr +systemctl reload nginx +``` + +Le QR code est régénéré automatiquement dans le build. + +### 15.3 Changer la clé API Mistral + +```bash +vim /opt/voix-du-peuple/.env +# Modifier MISTRAL_API_KEY + +systemctl restart voix-du-peuple-api +``` + +### 15.4 Purger les contributions + +```bash +psql -U voix_user -h 127.0.0.1 voix_du_peuple +``` + +```sql +-- Tout purger (irréversible sans backup) +TRUNCATE ideas; +TRUNCATE synthesis; + +-- Supprimer seulement les refusées +DELETE FROM ideas WHERE accepted = false; + +-- Forcer une re-synthèse au prochain envoi +DELETE FROM synthesis; +``` + +### 15.5 Consulter les logs + +```bash +# Logs de l'API en temps réel +journalctl -u voix-du-peuple-api -f + +# Logs d'accès Gunicorn +tail -f /var/log/voix-du-peuple/api-access.log + +# Logs d'erreur Gunicorn +tail -f /var/log/voix-du-peuple/api-error.log + +# Logs Nginx +tail -f /var/log/nginx/access.log +tail -f /var/log/nginx/error.log +``` + +### 15.6 Rotation des logs + +```bash +# Créer un fichier logrotate +cat > /etc/logrotate.d/voix-du-peuple < backup.sql` | +| Purger les données | `psql -U voix_user -h 127.0.0.1 voix_du_peuple -c "TRUNCATE ideas; TRUNCATE synthesis;"` | +| Health check | `curl http://127.0.0.1:8080/health` | diff --git a/scripts/set-domain.sh b/scripts/set-domain.sh new file mode 100644 index 0000000..ca46938 --- /dev/null +++ b/scripts/set-domain.sh @@ -0,0 +1,51 @@ +#!/usr/bin/env bash +# set-domain.sh — Configure le nom de domaine et reconstruit le frontend +# +# Usage : bash scripts/set-domain.sh https://votredomaine.fr +# +# Ce script : +# 1. Met à jour la variable VITE_APP_URL dans .env +# 2. Reconstruit le frontend (le QR code sera mis à jour) +# +# Prérequis : pnpm installé, être à la racine du projet + +set -e + +DOMAIN="${1:-}" + +if [ -z "$DOMAIN" ]; then + echo "Usage : bash scripts/set-domain.sh https://votredomaine.fr" + echo "" + echo "Exemples :" + echo " bash scripts/set-domain.sh https://lavoixdupeuple.fr" + echo " bash scripts/set-domain.sh http://192.168.1.10:8080" + exit 1 +fi + +ENV_FILE=".env" + +# Créer .env si absent +if [ ! -f "$ENV_FILE" ]; then + touch "$ENV_FILE" + echo "Fichier .env créé." +fi + +# Mettre à jour ou ajouter VITE_APP_URL +if grep -q "^VITE_APP_URL=" "$ENV_FILE"; then + sed -i "s|^VITE_APP_URL=.*|VITE_APP_URL=${DOMAIN}|" "$ENV_FILE" + echo "VITE_APP_URL mis à jour : ${DOMAIN}" +else + echo "VITE_APP_URL=${DOMAIN}" >> "$ENV_FILE" + echo "VITE_APP_URL ajouté : ${DOMAIN}" +fi + +# Exporter pour que Vite le lise pendant le build +export VITE_APP_URL="${DOMAIN}" + +echo "" +echo "Reconstruction du frontend..." +pnpm --filter @workspace/voix-du-peuple run build --config vite.config.selfhost.ts + +echo "" +echo "Terminé. Le QR code pointe maintenant vers : ${DOMAIN}" +echo "Redémarrez Nginx si le build est en production : systemctl reload nginx"