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:
pironantoine
2026-04-04 14:26:14 +00:00
parent 50bc1f5ce9
commit e279dab70a
5 changed files with 623 additions and 87 deletions
+6
View File
@@ -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 dinstallation RockyLinux"
+16 -42
View File
@@ -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
View File
@@ -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";
# }
# }
+538
View File
@@ -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` |
+51
View File
@@ -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"