Merge remote-tracking branch 'origin/main' into plateau

This commit is contained in:
Pierre MARTIN
2026-11-02 21:09:33 +01:00
8 changed files with 268 additions and 2 deletions
+5
View File
@@ -50,6 +50,11 @@ Les contenus pédagogiques (leçons, textes) sont sous licence CC BY-SA 4.0.
fournit aussi **Node.js** (MIT), qui rejoue la suite de contrat sur le
moteur WebAssembly et lance `make bench`.
## Serveur de production
- **Caddy** (Apache-2.0), image `caddy:2.8.4-alpine`, serveur statique du
site publié sur CapRover (`deploy/`).
## Remerciements
À l'Atelier du Huit, café associatif de Cugnaux, et à Philippe, qui y transmet le jeu.
+29 -2
View File
@@ -22,15 +22,17 @@ BUILD_C_IMAGE := ustagammon/build-c:2
WASM_IMAGE := emscripten/emsdk:3.1.64
# Port de la page de test servie sur le réseau local (make deploy-web).
WEB_PORT := 8080
# Serveur statique de production (deploy/Dockerfile) : même version ici.
CADDY_IMAGE := caddy:2.8.4-alpine
export SHELLCHECK_IMAGE GHERKIN_IMAGE BUILD_C_IMAGE WASM_IMAGE
export SHELLCHECK_IMAGE GHERKIN_IMAGE BUILD_C_IMAGE WASM_IMAGE CADDY_IMAGE
# Lance une commande dans la chaîne C, sous l'utilisateur courant pour que
# build/ lui appartienne.
DOCKER_C := docker run --rm --user "$$(id -u):$$(id -g)" -v "$(CURDIR):/src" -w /src $(BUILD_C_IMAGE)
DOCKER_WASM := docker run --rm --user "$$(id -u):$$(id -g)" -e HOME=/tmp -v "$(CURDIR):/src" -w /src $(WASM_IMAGE)
.PHONY: help setup build engine engine-wasm bench test lint deploy deploy-web clean reference-image reference
.PHONY: help setup build engine engine-wasm bench test lint deploy deploy-web site package deploy-prod clean reference-image reference
help: ## Liste les commandes disponibles
@echo "Ustagammon : commandes disponibles"
@@ -48,6 +50,8 @@ setup: ## Installe la chaîne d'outils (images Docker) ; sans effet si déjà fa
|| docker build -t $(BUILD_C_IMAGE) docker/build-c
@docker image inspect $(WASM_IMAGE) >/dev/null 2>&1 \
|| docker pull $(WASM_IMAGE)
@docker image inspect $(CADDY_IMAGE) >/dev/null 2>&1 \
|| docker pull $(CADDY_IMAGE)
@echo "Chaîne d'outils prête."
build: engine ## Compile le moteur (si nécessaire) puis l'application
@@ -92,6 +96,29 @@ deploy-web: engine-wasm ## Sert la page de test WebAssembly sur le réseau local
sh -c 'cp -r /web /tmp/site && cp -r /wasm /tmp/site/wasm \
&& exec python3 -m http.server 8080 --directory /tmp/site'
# Le site tel qu'il sera publié : web/ à la racine, le moteur sous wasm/, et
# version.json (commit déployé). Reconstruit entièrement à chaque appel.
site: engine-wasm ## Assemble le site publié dans build/site/
rm -rf $(BUILD_DIR)/site
mkdir -p $(BUILD_DIR)/site/wasm
cp -r web/. $(BUILD_DIR)/site/
cp $(BUILD_DIR)/wasm/engine.mjs $(BUILD_DIR)/wasm/engine.wasm $(BUILD_DIR)/wasm/engine.data \
$(BUILD_DIR)/wasm/positions.json $(BUILD_DIR)/site/wasm/
printf '{"commit": "%s", "dirty": %s}\n' "$$(git rev-parse HEAD)" \
"$$(git diff --quiet HEAD && echo false || echo true)" > $(BUILD_DIR)/site/version.json
# L'archive envoyée à CapRover : captain-definition, Dockerfile, Caddyfile et
# le site. CapRover construit l'image (une simple copie) sur le serveur.
package: site ## Prépare l'archive de déploiement build/ustagammon.tar
rm -rf $(BUILD_DIR)/deploy
cp -r deploy $(BUILD_DIR)/deploy
cp -r $(BUILD_DIR)/site $(BUILD_DIR)/deploy/site
tar -cf $(BUILD_DIR)/ustagammon.tar -C $(BUILD_DIR)/deploy .
@echo "Archive prête : $(BUILD_DIR)/ustagammon.tar"
deploy-prod: ## Publie main sur CapRover (voir docs/deploiement.md)
@scripts/deploy.sh
clean: ## Supprime les artefacts de build (garde les images Docker)
rm -rf $(BUILD_DIR)
+39
View File
@@ -0,0 +1,39 @@
# Serveur statique d'Ustagammon. CapRover fournit le HTTPS devant ; ici on
# écoute en HTTP sur le port 80 du conteneur.
{
admin off
auto_https off
}
:80 {
root * /srv
# Types compressés : ceux de Caddy par défaut, plus les données du moteur
# (application/octet-stream, 8,7 Mo, 7,2 Mo compressés).
encode {
zstd
gzip
match {
header Content-Type text/*
header Content-Type application/json*
header Content-Type application/javascript*
header Content-Type application/wasm*
header Content-Type application/octet-stream*
header Content-Type application/manifest+json*
header Content-Type image/svg+xml*
}
}
# Les noms de fichiers ne changent pas d'une version à l'autre : le
# navigateur revalide chaque fichier (ETag) au lieu de garder une copie
# périmée.
header Cache-Control "no-cache"
header X-Content-Type-Options "nosniff"
header Referrer-Policy "no-referrer"
# Données du moteur (Emscripten) : extension inconnue de Caddy, qui
# devinerait text/xml d'après le début du fichier.
@engine_data path *.data
header @engine_data Content-Type "application/octet-stream"
file_server
}
+9
View File
@@ -0,0 +1,9 @@
# Image de production d'Ustagammon : un serveur statique (Caddy) et le site
# déjà construit et testé sur le PC (make site). Rien n'est compilé sur le
# serveur : voir docs/deploiement.md.
FROM caddy:2.8.4-alpine
COPY Caddyfile /etc/caddy/Caddyfile
COPY site/ /srv/
EXPOSE 80
+1
View File
@@ -0,0 +1 @@
{"schemaVersion": 2, "dockerfilePath": "./Dockerfile"}
+85
View File
@@ -0,0 +1,85 @@
# Déploiement
Ustagammon est un site statique (PWA, ADR 0009) : des fichiers HTML,
JavaScript, le moteur en WebAssembly et ses données. Il est publié sur
CapRover, machine `sans-pub` (https://captain.cloud.sans.pub), app
`ustagammon`, sur https://ustagammon.sans.pub.
**Le site est construit et testé sur le PC, pas sur le serveur.** La
construction est lourde (Emscripten, génération des données du moteur) et déjà
outillée en local. `make deploy-prod` envoie à CapRover une archive qui
contient le site prêt, un `Dockerfile` et un `Caddyfile` (`deploy/`). Le serveur
ne fait que copier les fichiers dans une image Caddy (`caddy:2.8.4-alpine`),
qui les sert en HTTP derrière le HTTPS de CapRover.
## Prérequis
- La chaîne d'outils du projet : `make setup`.
- **CLI CapRover**, installée avec Node par `fnm` (le script la trouve seul) :
```bash
eval "$(fnm env)"; fnm use lts-latest
```
- **Session CapRover valide.** Le jeton expire ; pour vérifier :
```bash
caprover api -n sans-pub -t /user/system/info -m GET -d '{}' -o false
```
En cas de `not valid anymore` : `caprover login` dans un vrai terminal.
- **L'app `ustagammon` existe dans CapRover** (créée le 2026-11-02) : sans
volume (rien n'est stocké côté serveur), port du conteneur 80, domaine
`ustagammon.sans.pub` avec HTTPS forcé.
## Publier
Depuis `main`, à jour avec `origin/main` (après fusion de la MR) :
```
make deploy-prod
```
Le script refuse de publier si l'arbre n'est pas propre, si l'on n'est pas sur
`main`, si `main` diffère d'`origin/main`, ou si la session CapRover a expiré.
Il lance `make lint` et `make test`, assemble le site (`make package`), puis
`caprover deploy -t build/ustagammon.tar`.
Pour une autre machine ou une autre app : `CAPROVER_MACHINE=… CAPROVER_APP=…
make deploy-prod`.
## Ce qui est publié
- `make site` assemble `build/site/` : le contenu de `web/` à la racine, le
moteur (`engine.mjs`, `engine.wasm`, `engine.data`, `positions.json`) sous
`wasm/`, et `version.json` avec le commit publié.
- `make package` ajoute `deploy/` et produit `build/ustagammon.tar` (environ
9 Mo).
- `tests/test_site.sh` (dans `make test`) construit l'image de production en
local, la lance et vérifie ce que reçoit un navigateur : pages, types MIME
(`application/wasm` pour le moteur), compression gzip des données, en-têtes,
et commit de `version.json`.
Réglages du serveur (`deploy/Caddyfile`) : compression zstd ou gzip, y compris
pour les données du moteur ; `Cache-Control: no-cache` (les noms de fichiers ne
changent pas d'une version à l'autre, le navigateur revalide chaque fichier) ;
`X-Content-Type-Options: nosniff` ; `Referrer-Policy: no-referrer`.
## Vérifier
```bash
curl -s https://ustagammon.sans.pub/version.json # commit publié
curl -sI https://ustagammon.sans.pub/wasm/engine.wasm # application/wasm
ssh sans.pub 'sudo docker service ps srv-captain--ustagammon --format "{{.Image}} {{.CurrentState}}" | head -2'
```
Puis ouvrir le site sur le téléphone : la page de test doit afficher « Moteur
prêt ».
## Retour arrière
Dans CapRover, onglet *Deployment* de l'app `ustagammon` : redéployer une image
précédente (`img-captain-ustagammon:N`). Rien n'est stocké côté serveur : aucun
risque pour des données.
## Incidents connus
Voir `la-main-libre` (`docs/technique/deploiement.md`) pour le registre Docker
de CapRover dont le certificat expire : le build réussit, l'envoi échoue, la
production n'est pas touchée ; redémarrer `captain-registry` puis relancer.
+46
View File
@@ -0,0 +1,46 @@
#!/usr/bin/env bash
# Publie la branche main sur CapRover (voir docs/deploiement.md).
#
# Le site est construit et testé sur le PC, puis envoyé sous forme d'archive :
# le serveur ne fait que copier les fichiers dans une image Caddy.
#
# Prérequis : CLI caprover (via fnm) connecté à la machine CapRover
# (caprover login).
set -euo pipefail
CAPROVER_MACHINE="${CAPROVER_MACHINE:-sans-pub}"
CAPROVER_APP="${CAPROVER_APP:-ustagammon}"
die() {
echo "Erreur : $1" >&2
exit 1
}
cd "$(git rev-parse --show-toplevel)"
# La CLI caprover est installée avec Node par fnm.
if ! command -v caprover >/dev/null && command -v fnm >/dev/null; then
eval "$(fnm env --shell bash)"
fnm use --silent-if-unchanged lts-latest >/dev/null || true
fi
command -v caprover >/dev/null || die "CLI caprover introuvable (voir docs/deploiement.md)"
# On ne publie que main, propre et identique à origin/main.
[ "$(git branch --show-current)" = "main" ] || die "lancer le déploiement depuis la branche main"
git diff --quiet HEAD || die "des modifications ne sont pas commitées"
git fetch -q origin main
[ "$(git rev-parse main)" = "$(git rev-parse origin/main)" ] \
|| die "main diffère d'origin/main : git pull (ou fusionner la MR) d'abord"
# Le jeton CapRover expire ; caprover ls ne le vérifie pas.
caprover api -n "$CAPROVER_MACHINE" -t /user/system/info -m GET -d '{}' -o false >/dev/null 2>&1 \
|| die "session CapRover expirée : lancer caprover login dans un terminal"
echo "Vérification avant publication : make lint et make test"
make lint
make test
make package
echo "Publication de main ($(git rev-parse --short main)) sur l'app $CAPROVER_APP"
caprover deploy -n "$CAPROVER_MACHINE" -a "$CAPROVER_APP" -t build/ustagammon.tar
+54
View File
@@ -0,0 +1,54 @@
#!/usr/bin/env bash
# Vérifie l'image de production (deploy/) avec le site assemblé (make package) :
# construit l'image en local, la lance, et contrôle ce que reçoit un
# navigateur (pages, types MIME du moteur, compression, version).
set -euo pipefail
cd "$(dirname "$0")/.."
unset MAKEFLAGS MFLAGS MAKELEVEL
make -s package >/dev/null
image="ustagammon/site-test:local"
docker build -q -t "$image" build/deploy >/dev/null
container="$(docker run -d --rm -p 127.0.0.1::80 "$image")"
trap 'docker stop "$container" >/dev/null' EXIT
port="$(docker port "$container" 80/tcp | head -n1 | sed 's/.*://')"
base="http://127.0.0.1:$port"
# Caddy démarre en une fraction de seconde : on attend qu'il réponde.
for _ in $(seq 1 50); do
curl -sf -o /dev/null "$base/" && break
sleep 0.1
done
errors=0
report() {
echo "$1"
errors=$((errors + 1))
}
# check <chemin> <début du type MIME attendu>
check() {
local headers
headers="$(curl -s -D - -o /dev/null "$base/$1")"
grep -qE "^HTTP/1.1 200" <<<"$headers" || report "/$1 : pas de réponse 200"
grep -qiE "^Content-Type: $2" <<<"$headers" || report "/$1 : type MIME attendu $2"
}
check "" "text/html"
check "bench.html" "text/html"
check "engine-worker.mjs" "text/javascript"
check "wasm/engine.mjs" "text/javascript"
check "wasm/engine.wasm" "application/wasm"
check "wasm/engine.data" "application/octet-stream"
check "version.json" "application/json"
curl -s -H "Accept-Encoding: gzip" -D - -o /dev/null "$base/wasm/engine.data" \
| grep -qi "^Content-Encoding: gzip" || report "/wasm/engine.data : pas de compression gzip"
curl -s -D - -o /dev/null "$base/" | grep -qi "^Cache-Control: no-cache" \
|| report "/ : en-tête Cache-Control absent"
[ "$(curl -s "$base/version.json" | sed -n 's/.*"commit": "\([0-9a-f]*\)".*/\1/p')" = "$(git rev-parse HEAD)" ] \
|| report "version.json ne donne pas le commit courant"
[ "$errors" -eq 0 ]