- Liens et assets via {base}, polices déplacées vers src/lib/assets (hachage Vite)
- Service worker base-aware, précache sur les pages prérendues
- CSP hash documentée : l'hôte ne doit pas fixer script-src
- Docs : déploiement Apache/YunoHost (DEPLOYMENT, APACHE_AUTOINDEX)
8.6 KiB
Déployer lage-chat-control sur un VPS
Le site est 100 % statique (ADR-001) : le déploiement consiste à poser le
contenu du dossier frontend/build/ derrière un serveur web. Pas de base de
données, pas de service Node à maintenir, pas de tâche cron. Un miroir du site
= une copie du dossier.
0. Prérequis
- Un VPS (1 vCPU / 512 Mo suffisent très largement) sous Debian/Ubuntu
- Un nom de domaine pointant sur le VPS (enregistrement A/AAAA)
- En local : Node ≥ 22.12 et pnpm (le build se fait sur ta machine, le VPS n'a besoin ni de Node ni de pnpm)
1. Construire le site
git clone https://labola.o-k-i.net/cyber-mawonaj/lage-chat-control.git
cd lage-chat-control/frontend
pnpm install
pnpm build # produit frontend/build/ (~60 pages statiques)
Racine de domaine : le site est servi à la racine de
https://chatcontrol.o-k-i.net/(YunoHost, voir §5) — aucunpaths.base. Pour un déploiement sous un sous-chemin, ajoutepaths: { base: '/prefixe' }dansfrontend/svelte.config.jsavantpnpm buildet adapte les règlestry_filescôté serveur (ex. nginx :location /prefixe/ { … }).
Mutualisé Apache (o2switch) : si les liens internes affichent « Index of /… » (listing de dossier) au lieu des pages, c'est le conflit fichier/dossier classique — voir APACHE_AUTOINDEX.md.
CSP et hydratation (critique) : SvelteKit démarre côté navigateur via un script inline (qui embarque les données de la page). Un en-tête HTTP
Content-Security-Policycontenantscript-src 'self'— comme celui posé par le compte o2switch — bloque ce script et tue toute l'interactivité (recherche, filtres, sélecteur de langue), sans message visible. Chaque page prérendue porte donc sa propre<meta>CSP avec l'empreinte (sha256-…) de son script inline (csp.mode: 'hash'dansfrontend/svelte.config.js). Règle côté serveur : ne jamais fixerscript-srcdans l'en-tête HTTP — le retirer de la config cPanel/.htaccess/reverse proxy (le reste de la CSP peut y rester). Si l'hébergeur impose unscript-srcen en-tête, le régler surscript-src 'self' 'unsafe-inline'(plus faible, mais fonctionnel).
2. Option A — Caddy (recommandé : TLS automatique)
# sur le VPS
sudo apt install caddy
sudo mkdir -p /srv/lage-chat-control
/etc/caddy/Caddyfile :
chatcontrol.o-k-i.net {
root * /srv/lage-chat-control
file_server
encode zstd gzip
# SvelteKit adapter-static : pages pré-générées + fallback
try_files {path} {path}.html {path}/index.html /404.html
header {
# le service worker doit pouvoir se mettre à jour
Cache-Control "no-cache" /service-worker.js
# assets immuables (noms hachés)
Cache-Control "public, max-age=31536000, immutable" /_app/immutable/*
# en-têtes de sécurité
Strict-Transport-Security "max-age=31536000; includeSubDomains"
X-Content-Type-Options "nosniff"
Referrer-Policy "no-referrer"
Permissions-Policy "camera=(), microphone=(), geolocation=()"
# CSP stricte : le site n'a aucune dépendance externe.
# NE PAS remettre script-src ici : il est porté par la <meta> CSP de
# chaque page (empreinte du script inline de démarrage, différente
# d'une page à l'autre — inexprimable dans un en-tête statique).
Content-Security-Policy "default-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; connect-src 'self'; frame-ancestors 'none'; base-uri 'self'"
}
}
Le TLS (Let's Encrypt) est automatique, rien d'autre à faire.
3. Option B — nginx
sudo apt install nginx certbot python3-certbot-nginx
/etc/nginx/sites-available/lage-chat-control :
server {
server_name chatcontrol.o-k-i.net;
root /srv/lage-chat-control;
index index.html;
# pages pré-générées : /outils/ → outils/index.html, fallback 404
location / {
try_files $uri $uri/ $uri.html $uri/index.html /404.html;
}
location /_app/immutable/ {
add_header Cache-Control "public, max-age=31536000, immutable";
}
location = /service-worker.js {
add_header Cache-Control "no-cache";
}
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "no-referrer" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
add_header Content-Security-Policy "default-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; connect-src 'self'; frame-ancestors 'none'; base-uri 'self'" always;
# NB : pas de script-src ici — voir le commentaire CSP de l'option A.
gzip on;
gzip_types text/html text/css application/javascript application/json image/svg+xml;
}
sudo ln -s /etc/nginx/sites-available/lage-chat-control /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
sudo certbot --nginx -d chatcontrol.o-k-i.net # TLS
4. Publier (et republier)
# depuis ta machine, après chaque `pnpm build`
rsync -avz --delete frontend/build/ vps:/srv/lage-chat-control/
C'est l'intégralité du « pipeline de déploiement ». Pour automatiser :
un runner Forgejo Actions sur labola peut exécuter build + rsync à chaque
push sur main (à ajouter quand le besoin se présente, pas avant).
5. Option C — YunoHost (cible de production)
Le site tourne en production sur chatcontrol.o-k-i.net via l'app
my_webapp (site statique) :
- Installe
my_webappdepuis le catalogue YunoHost avec le domainechatcontrol.o-k-i.net, le chemin/, un accès public (visiteurs compris), sans PHP ni base de données. - Copie le contenu de
frontend/build/dans lewww/de l'app, avec l'utilisateur SFTP dédié créé par l'app (son nom exact figure dans la page de l'app, section « Accès SFTP ») :rsync -avz --delete frontend/build/ my_webapp@chatcontrol.o-k-i.net:www/ - C'est tout : la conf nginx générée sert déjà
<route>/index.html(avec redirection automatique vers le/final). YunoHost gère TLS, HSTS et le renouvellement des certificats.
Durcissements optionnels (panneau de config de l'app, ou
/etc/nginx/conf.d/chatcontrol.o-k-i.net.d/my_webapp.conf) :
error_page 404 /404.html;— notre page 404 plutôt que celle de nginx ;- cache long sur les assets immuables :
location /_app/immutable/ { add_header Cache-Control "public, max-age=31536000, immutable"; } - revalidation du service worker :
location = /service-worker.js { add_header Cache-Control "no-cache"; }
Ne pose pas d'en-tête CSP globale côté YunoHost : la <meta> CSP de
chaque page suffit (voir l'encadré CSP en §1). À noter : l'overlay du portail
YunoHost, injecté uniquement pour les visiteurs déjà connectés au SSO, est
bloqué par cette CSP — cosmétique, sans effet pour les visiteurs anonymes.
6. Option D — Docker
Le dépôt n'embarque volontairement aucun Dockerfile (un site 100 % statique
n'en a pas besoin — c'est un dossier de fichiers). Si le reste de l'infra OKI
tourne déjà en conteneurs, ce Dockerfile minimal (nginx durci, sans build
step) suffit :
FROM nginx:alpine
COPY frontend/build/ /usr/share/nginx/html/
COPY docs/nginx.conf.example /etc/nginx/conf.d/default.conf
Reprends le bloc server {} de l'option B ci-dessus dans
docs/nginx.conf.example (à créer), en retirant server_name/certbot
(le TLS se termine en amont, sur un reverse proxy ou le load balancer).
Sinon, les options A/B sont plus simples et suffisent dans l'immense
majorité des cas.
7. Vérifier après mise en ligne
curl -s https://chatcontrol.o-k-i.net/outils/ | grep -o 'http-equiv="Content-Security-Policy"'→ la<meta>CSP par page est présente (elle portescript-src+ empreinte)curl -sI https://chatcontrol.o-k-i.net/outils/ | grep -i content-security→ l'en-tête HTTP éventuelle ne contient pas descript-srcbloquant- Ouvrir
/outils, taper « signal » dans la recherche → la liste se filtre sans rechargement (preuve que l'hydratation fonctionne) ; cliquer une catégorie → l'URL gagne?cat=…et la liste se filtre - DevTools > Console : aucune erreur CSP (« violates the following Content Security Policy directive ») au chargement
- DevTools > Application > Service worker : enregistré ; passer hors-ligne et recharger une fiche déjà visitée → elle s'affiche
- observatory.mozilla.org : note A/A+ attendue avec les en-têtes ci-dessus
- Aucune requête sortante vers un domaine tiers (onglet Réseau)