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
This commit is contained in:
@@ -30,3 +30,9 @@ id = "kJNXgVnYp_LQmPcWr6Osb"
|
|||||||
uri = "file://docs/WIKI.md"
|
uri = "file://docs/WIKI.md"
|
||||||
type = "text"
|
type = "text"
|
||||||
title = "Wiki v1.3"
|
title = "Wiki v1.3"
|
||||||
|
|
||||||
|
[[outputs]]
|
||||||
|
id = "4toQ5OnXtI6PicOZHDKnP"
|
||||||
|
uri = "file://docs/INSTALL_ROCKY.md"
|
||||||
|
type = "text"
|
||||||
|
title = "Guide d’installation RockyLinux"
|
||||||
|
|||||||
@@ -1,53 +1,27 @@
|
|||||||
import React, { useState } from "react";
|
import React from "react";
|
||||||
import { QRCodeSVG } from "qrcode.react";
|
import { QRCodeSVG } from "qrcode.react";
|
||||||
import { Printer, ExternalLink } from "lucide-react";
|
import { Printer, ExternalLink } from "lucide-react";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Input } from "@/components/ui/input";
|
|
||||||
import { Link } from "wouter";
|
import { Link } from "wouter";
|
||||||
|
|
||||||
// ══════════════════════════════════════════════════════════
|
// URL du QR code — définie via VITE_APP_URL dans le fichier .env
|
||||||
// URL encodée dans le QR code — modifiez cette ligne
|
// Modifier .env puis exécuter : bash scripts/set-domain.sh https://votredomaine.fr
|
||||||
const DEFAULT_QR_URL = "https://lavoixdupeuple.fr";
|
const QR_URL = (new URLSearchParams(window.location.search).get("url"))
|
||||||
// ══════════════════════════════════════════════════════════
|
|| import.meta.env.VITE_APP_URL
|
||||||
|
|| "https://lavoixdupeuple.fr";
|
||||||
|
|
||||||
export default function Flyer() {
|
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 (
|
return (
|
||||||
<div className="min-h-screen bg-muted/20 flex flex-col items-center py-8 px-4 gap-6">
|
<div className="min-h-screen bg-muted/20 flex flex-col items-center py-8 px-4 gap-6">
|
||||||
|
|
||||||
{/* Barre de contrôle — masquée à l'impression */}
|
{/* Barre de contrôle — masquée à l'impression */}
|
||||||
<div className="no-print w-full max-w-xl bg-white border border-border/50 p-4 rounded-sm space-y-3">
|
<div className="no-print w-full max-w-xl bg-white border border-border/50 p-4 rounded-sm flex gap-2">
|
||||||
<p className="text-xs font-mono text-muted-foreground uppercase tracking-widest">
|
<Button onClick={() => window.print()} size="sm" className="gap-2">
|
||||||
Destination du QR code
|
<Printer className="h-3.5 w-3.5" /> Imprimer / Exporter PDF
|
||||||
</p>
|
</Button>
|
||||||
<div className="flex gap-2">
|
<Link href="/">
|
||||||
<Input
|
<Button variant="ghost" size="sm">← Retour</Button>
|
||||||
value={inputValue}
|
</Link>
|
||||||
onChange={(e) => setInputValue(e.target.value)}
|
|
||||||
onKeyDown={(e) => e.key === "Enter" && applyUrl()}
|
|
||||||
placeholder="https://lavoixdupeuple.fr"
|
|
||||||
className="font-mono text-sm"
|
|
||||||
/>
|
|
||||||
<Button onClick={applyUrl} variant="outline" size="sm">
|
|
||||||
Appliquer
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
<div className="flex gap-2">
|
|
||||||
<Button onClick={() => window.print()} size="sm" className="gap-2">
|
|
||||||
<Printer className="h-3.5 w-3.5" /> Imprimer / Exporter PDF
|
|
||||||
</Button>
|
|
||||||
<Link href="/">
|
|
||||||
<Button variant="ghost" size="sm">← Retour</Button>
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Flyer imprimable */}
|
{/* Flyer imprimable */}
|
||||||
@@ -83,7 +57,7 @@ export default function Flyer() {
|
|||||||
<div className="flex flex-col items-center gap-4 my-4">
|
<div className="flex flex-col items-center gap-4 my-4">
|
||||||
<div className="p-4 border border-border/40 bg-white">
|
<div className="p-4 border border-border/40 bg-white">
|
||||||
<QRCodeSVG
|
<QRCodeSVG
|
||||||
value={qrUrl}
|
value={QR_URL}
|
||||||
size={220}
|
size={220}
|
||||||
bgColor="#ffffff"
|
bgColor="#ffffff"
|
||||||
fgColor="#1a1a2e"
|
fgColor="#1a1a2e"
|
||||||
@@ -93,7 +67,7 @@ export default function Flyer() {
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-1.5 text-xs font-mono text-muted-foreground">
|
<div className="flex items-center gap-1.5 text-xs font-mono text-muted-foreground">
|
||||||
<ExternalLink className="h-3 w-3" />
|
<ExternalLink className="h-3 w-3" />
|
||||||
<span>{qrUrl}</span>
|
<span>{QR_URL}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -106,7 +80,7 @@ export default function Flyer() {
|
|||||||
className="text-lg font-bold text-primary tracking-wide"
|
className="text-lg font-bold text-primary tracking-wide"
|
||||||
style={{ fontFamily: "'Bahnschrift', 'DIN Alternate', sans-serif" }}
|
style={{ fontFamily: "'Bahnschrift', 'DIN Alternate', sans-serif" }}
|
||||||
>
|
>
|
||||||
{qrUrl.replace(/^https?:\/\//, "")}
|
{QR_URL.replace(/^https?:\/\//, "")}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
+12
-45
@@ -1,9 +1,6 @@
|
|||||||
server {
|
server {
|
||||||
listen 80;
|
listen 8080;
|
||||||
server_name voix-du-peuple.example.com;
|
server_name _; # remplacer par votre nom de domaine si nécessaire
|
||||||
|
|
||||||
# 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;
|
root /opt/voix-du-peuple/artifacts/voix-du-peuple/dist/public;
|
||||||
index index.html;
|
index index.html;
|
||||||
@@ -14,7 +11,13 @@ server {
|
|||||||
add_header X-XSS-Protection "1; mode=block" always;
|
add_header X-XSS-Protection "1; mode=block" always;
|
||||||
add_header Referrer-Policy "strict-origin-when-cross-origin" 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/ {
|
location /api/ {
|
||||||
proxy_pass http://127.0.0.1:8000;
|
proxy_pass http://127.0.0.1:8000;
|
||||||
proxy_set_header Host $host;
|
proxy_set_header Host $host;
|
||||||
@@ -24,7 +27,7 @@ server {
|
|||||||
proxy_read_timeout 60s;
|
proxy_read_timeout 60s;
|
||||||
}
|
}
|
||||||
|
|
||||||
# Frontend React — Single Page Application
|
# Frontend React — SPA
|
||||||
location / {
|
location / {
|
||||||
try_files $uri $uri/ /index.html;
|
try_files $uri $uri/ /index.html;
|
||||||
}
|
}
|
||||||
@@ -34,42 +37,6 @@ server {
|
|||||||
expires 1y;
|
expires 1y;
|
||||||
add_header Cache-Control "public, immutable";
|
add_header Cache-Control "public, immutable";
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
# HTTPS — décommentez et adaptez après avoir obtenu votre certificat SSL
|
client_max_body_size 1m;
|
||||||
# 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,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 <<EOF
|
||||||
|
CREATE USER voix_user WITH PASSWORD 'MOT_DE_PASSE_FORT';
|
||||||
|
CREATE DATABASE voix_du_peuple OWNER voix_user;
|
||||||
|
GRANT ALL PRIVILEGES ON DATABASE voix_du_peuple TO voix_user;
|
||||||
|
EOF
|
||||||
|
|
||||||
|
exit # revenir à root
|
||||||
|
```
|
||||||
|
|
||||||
|
Tester la connexion :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
psql -U voix_user -d voix_du_peuple -h 127.0.0.1 -c '\l'
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Environnement Python
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /opt/voix-du-peuple
|
||||||
|
|
||||||
|
# Créer le virtualenv
|
||||||
|
python3.11 -m venv .venv
|
||||||
|
|
||||||
|
# Installer les dépendances
|
||||||
|
.venv/bin/pip install --upgrade pip
|
||||||
|
.venv/bin/pip install -r artifacts/flask-api/requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Configuration du domaine et variables d'environnement
|
||||||
|
|
||||||
|
### 8.1 Créer le fichier `.env`
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cp .env.example .env # si le fichier exemple existe, sinon créer manuellement
|
||||||
|
vim .env
|
||||||
|
```
|
||||||
|
|
||||||
|
Contenu minimal du `.env` :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Base de données
|
||||||
|
DATABASE_URL=postgresql://voix_user:MOT_DE_PASSE_FORT@127.0.0.1:5432/voix_du_peuple
|
||||||
|
|
||||||
|
# IA Mistral
|
||||||
|
MISTRAL_API_KEY=votre_cle_mistral_ici
|
||||||
|
|
||||||
|
# Sécurité Flask
|
||||||
|
SESSION_SECRET=generez-une-chaine-aleatoire-longue-ici
|
||||||
|
|
||||||
|
# Modèles IA (valeurs par défaut si omis)
|
||||||
|
# FILTER_MODEL=mistral-small-latest
|
||||||
|
# SYNTHESIS_MODEL=mistral-large-latest
|
||||||
|
|
||||||
|
# Domaine public — utilisé par le QR code du flyer
|
||||||
|
VITE_APP_URL=https://votredomaine.fr
|
||||||
|
```
|
||||||
|
|
||||||
|
Générer un SESSION_SECRET :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 -c "import secrets; print(secrets.token_hex(32))"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.2 Changer le domaine (QR code)
|
||||||
|
|
||||||
|
Le flyer génère automatiquement son QR code à partir de `VITE_APP_URL`. Pour changer le domaine :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Depuis /opt/voix-du-peuple
|
||||||
|
bash scripts/set-domain.sh https://votredomaine.fr
|
||||||
|
```
|
||||||
|
|
||||||
|
Ce script met à jour `VITE_APP_URL` dans `.env` et reconstruit le frontend.
|
||||||
|
Appliquer ensuite : `systemctl reload nginx`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Build du frontend
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /opt/voix-du-peuple
|
||||||
|
|
||||||
|
# Installer les dépendances Node
|
||||||
|
pnpm install
|
||||||
|
|
||||||
|
# Construire le frontend (lit VITE_APP_URL depuis .env)
|
||||||
|
source .env
|
||||||
|
export VITE_APP_URL
|
||||||
|
pnpm --filter @workspace/voix-du-peuple run build --config vite.config.selfhost.ts
|
||||||
|
|
||||||
|
# Vérifier que le build est présent
|
||||||
|
ls artifacts/voix-du-peuple/dist/public/
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. Service Gunicorn (systemd)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Copier le fichier de service
|
||||||
|
cp deploy/voix-du-peuple-api.service /etc/systemd/system/
|
||||||
|
|
||||||
|
# Recharger systemd et activer le service
|
||||||
|
systemctl daemon-reload
|
||||||
|
systemctl enable voix-du-peuple-api
|
||||||
|
systemctl start voix-du-peuple-api
|
||||||
|
systemctl status voix-du-peuple-api
|
||||||
|
```
|
||||||
|
|
||||||
|
Vérifier que Gunicorn écoute bien sur le port interne :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ss -tlnp | grep 8000
|
||||||
|
# doit afficher : 127.0.0.1:8000
|
||||||
|
```
|
||||||
|
|
||||||
|
Tester l'API directement :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl http://127.0.0.1:8000/health
|
||||||
|
# réponse attendue : {"status": "ok"}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11. Nginx
|
||||||
|
|
||||||
|
### 11.1 Installer la configuration
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Copier la config fournie
|
||||||
|
cp deploy/nginx.conf /etc/nginx/conf.d/voix-du-peuple.conf
|
||||||
|
|
||||||
|
# (Optionnel) Ajuster le nom de serveur dans la config
|
||||||
|
vim /etc/nginx/conf.d/voix-du-peuple.conf
|
||||||
|
# Modifier : server_name _;
|
||||||
|
# En : server_name votredomaine.fr;
|
||||||
|
|
||||||
|
# Tester la configuration
|
||||||
|
nginx -t
|
||||||
|
|
||||||
|
# Démarrer Nginx
|
||||||
|
systemctl start nginx
|
||||||
|
systemctl status nginx
|
||||||
|
```
|
||||||
|
|
||||||
|
### 11.2 Vérifier que Nginx écoute sur 8080
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ss -tlnp | grep 8080
|
||||||
|
# doit afficher : 0.0.0.0:8080
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 12. Firewall
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Autoriser le port 8080 (HTTP)
|
||||||
|
firewall-cmd --permanent --add-port=8080/tcp
|
||||||
|
firewall-cmd --reload
|
||||||
|
|
||||||
|
# Vérifier
|
||||||
|
firewall-cmd --list-ports
|
||||||
|
```
|
||||||
|
|
||||||
|
> 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 <COMMIT_HASH>
|
||||||
|
|
||||||
|
# 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 <<EOF
|
||||||
|
/var/log/voix-du-peuple/*.log {
|
||||||
|
daily
|
||||||
|
rotate 14
|
||||||
|
compress
|
||||||
|
missingok
|
||||||
|
notifempty
|
||||||
|
postrotate
|
||||||
|
systemctl kill -s USR1 voix-du-peuple-api
|
||||||
|
endscript
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
```
|
||||||
|
|
||||||
|
### 15.7 Vérification de santé (cron)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Ajouter un health check toutes les 5 minutes
|
||||||
|
crontab -e
|
||||||
|
# Ajouter :
|
||||||
|
# */5 * * * * curl -sf http://127.0.0.1:8080/health || systemctl restart voix-du-peuple-api
|
||||||
|
```
|
||||||
|
|
||||||
|
### 15.8 Tableau de bord des commandes rapides
|
||||||
|
|
||||||
|
| Opération | Commande |
|
||||||
|
|-----------|----------|
|
||||||
|
| Statut global | `systemctl status voix-du-peuple-api nginx postgresql-15` |
|
||||||
|
| Relancer l'API | `systemctl restart voix-du-peuple-api` |
|
||||||
|
| Recharger Nginx | `systemctl reload nginx` |
|
||||||
|
| Logs API live | `journalctl -u voix-du-peuple-api -f` |
|
||||||
|
| Mise à jour complète | `git pull && bash scripts/set-domain.sh $VITE_APP_URL && systemctl restart voix-du-peuple-api && systemctl reload nginx` |
|
||||||
|
| Changer de domaine | `bash scripts/set-domain.sh https://domaine.fr` |
|
||||||
|
| Backup BDD | `pg_dump -U voix_user -h 127.0.0.1 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` |
|
||||||
@@ -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"
|
||||||
Reference in New Issue
Block a user