alpinux.presentations/deploy/webhook.md
Cédrix 02cf82447b Créer le site des présentations d'Alpinux
Un dossier par présentation en Markdown, converti en diaporama HTML autonome
par Marp CLI, et une page d'accueil générée qui les liste toutes.

- scripts/build.mjs : build strict (fiche validée, images vérifiées, aucune
  ressource externe hormis le logo), page d'accueil filtrable,
  presentations.json et .htaccess pour l'en-tête CORS
- theme/alpinux.css : thème commun pour la vidéoprojection, classes titre et demo
- slides/_modele : modèle commenté, non publié
- slides/pdf-signer : « Lire, remplir et signer ses PDF », avec notes d'orateur
- scripts/deploy-presentations.sh et deploy/ : déploiement par webhook Gitea,
  sur le modèle du wiki et de www

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01M713yNBL8cssBA1Zri5QLT
2026-10-10 21:44:32 +02:00

181 lines
9.2 KiB
Markdown

# Déploiement automatique : mise en place côté serveur
Cette page s'adresse aux mainteneurs qui ont un accès SSH à `alpinux.org`. Les
contributeurs n'ont rien à faire ici : leur travail s'arrête à la pull request.
La chaîne est la même que celle du wiki et de www.alpinux.org :
```
push sur main → webhook Gitea → https://presentations.alpinux.org/deploy
│ Apache relaie vers 127.0.0.1:9878
▼
service d'écoute (signature HMAC vérifiée)
▼
deploy-presentations.sh : git pull → npm ci → build dans le staging → rsync
▼
https://presentations.alpinux.org
```
Si le build échoue — front matter invalide, image manquante — rien n'est copié : le
site en ligne reste celui du dernier build réussi.
---
## Un second service plutôt qu'une nouvelle route
Le service d'écoute du wiki (`/opt/wiki-mkdocs/webhook.py`) ne connaît qu'un secret et
qu'un script. Y ajouter une route aurait demandé de le réécrire, de faire cohabiter
deux secrets dans la même unité systemd, et de redémarrer le déploiement du wiki à
chaque retouche de celui des présentations.
Ce dépôt apporte donc **son propre service**, comme www.alpinux.org l'a fait avant
lui. Les trois sont des copies du même petit programme :
| Site | Service systemd | Port | Secret |
|----------------|--------------------------|-------|-----------------------------------------|
| wiki | `wiki-webhook` | 9876 | dans l'unité systemd |
| www | `www-webhook` | 9877 | `/etc/default/www-webhook` |
| présentations | `presentations-webhook` | 9878 | `/etc/default/presentations-webhook` |
Chaque dépôt a **son** secret : celui des présentations n'ouvre pas le déploiement du
wiki, et inversement.
---
## Ce qui est installé, et où
| Quoi | Où |
|-----------------------------------|----------------------------------------------------------|
| Clone du dépôt | `/opt/presentations-alpinux/repo` |
| Répertoire de staging | `/opt/presentations-alpinux/staging` |
| Node.js (LTS, version figée) | `/opt/presentations-alpinux/node` |
| Script de déploiement (copie) | `/opt/presentations-alpinux/deploy-presentations.sh` |
| Service d'écoute (copie) | `/opt/presentations-alpinux/webhook.py` |
| Unité systemd | `/etc/systemd/system/presentations-webhook.service` |
| Secret HMAC (`0600`, root) | `/etc/default/presentations-webhook` |
| Clé de déploiement, lecture seule | `~abonnelc/.ssh/id_ed25519_presentations_deploy` |
| DocumentRoot | `/var/www/clients/client1/web22/web` |
| Journal | `/var/log/presentations-deploy.log` |
Fichiers fournis par ce dossier : [`installer.sh`](installer.sh),
[`webhook.py`](webhook.py), [`presentations-webhook.service`](presentations-webhook.service),
[`apache-vhost.exemple.conf`](apache-vhost.exemple.conf), et
[`../scripts/deploy-presentations.sh`](../scripts/deploy-presentations.sh).
**Aucun secret n'est dans le dépôt.** Le secret du webhook est tiré au hasard sur le
serveur par `installer.sh` ; la clé privée de déploiement y est créée et n'en sort pas.
---
## Installation
Tout tient dans `installer.sh`, à lancer en root. Il est sans danger de le relancer :
chaque étape vérifie d'abord ce qui existe.
```bash
sudo bash deploy/installer.sh
```
1. **Node.js** — téléchargé depuis nodejs.org, somme SHA-256 vérifiée, déposé dans
`/opt/presentations-alpinux/node`. Rien n'est ajouté aux dépôts apt du serveur, et
pas de Chromium : le build HTML de Marp n'en a pas besoin.
2. **Clé de déploiement** — une clé SSH propre à ce dépôt, et un alias
`gitea-presentations` dans `~abonnelc/.ssh/config`.
3. **Clone du dépôt.** La première fois, le script s'arrête ici et affiche la clé
publique : la déclarer dans Gitea, *Paramètres → Clés de déploiement* du dépôt,
**sans** l'accès en écriture, puis relancer.
4. **Scripts** — copie de `deploy-presentations.sh` et de `webhook.py` dans
`/opt/presentations-alpinux`, création du journal.
5. **Droit d'écriture** sur le DocumentRoot, par une ACL pour `abonnelc`.
6. **Secret et service** — secret tiré au hasard s'il n'existe pas, unité systemd
installée et démarrée.
7. **Relais Apache** — les deux lignes `ProxyPass` dans le vhost, puis rechargement
d'Apache si la configuration est valide.
### Déclarer le webhook dans Gitea
Dans le dépôt `alpinux-presentations` : *Paramètres → Webhooks → Ajouter un webhook →
Gitea*.
| Champ | Valeur |
|---------------------|-----------------------------------------------------|
| URL cible | `https://presentations.alpinux.org/deploy` |
| Méthode HTTP | `POST` |
| Type de contenu | `application/json` |
| Secret | la valeur lue par `sudo cat /etc/default/presentations-webhook` |
| Déclencheur | *Push* seulement |
| Filtre de branche | `main` |
Le filtre de branche évite des appels inutiles ; le service ignore de toute façon tout
ce qui ne vise pas `refs/heads/main`.
### Rendre le relais Apache durable
Le vhost est écrit par ISPConfig, qui le **régénère** à chaque modification du site
(certificat, alias, version de PHP…). Les deux lignes posées par `installer.sh`
disparaissent ce jour-là.
Pour qu'elles tiennent : dans ISPConfig, *Sites → presentations.alpinux.org → Options →
Directives Apache*, coller les deux lignes de
[`apache-vhost.exemple.conf`](apache-vhost.exemple.conf). ISPConfig les réécrira alors
avec le reste du vhost.
---
## Vérifier
```bash
# Le service tourne et n'écoute que sur la boucle locale
systemctl status presentations-webhook
ss -ltn | grep 9878
# Sans signature valable, le relais répond 403 — c'est la bonne réponse
curl -s -o /dev/null -w '%{http_code}\n' -X POST -d '{}' https://presentations.alpinux.org/deploy
# Le dernier déploiement
tail -20 /var/log/presentations-deploy.log
```
Une exécution réussie encadre le build par une ligne `Deploy started` et une ligne
`Deploy done`. Une ligne `ABANDON` signale un échec : la cause est juste au-dessus, et
le site en ligne n'a pas bougé.
Dans Gitea, la page du webhook liste les envois récents avec la réponse reçue, et son
bouton *Tester l'envoi* rejoue un push.
---
## Au quotidien
| Je veux… | Je fais… |
|--------------------------------------------|--------------------------------------------------------------------------|
| Savoir pourquoi ce n'est pas en ligne | `tail -30 /var/log/presentations-deploy.log` |
| Republier sans nouveau commit | `sudo -u abonnelc /opt/presentations-alpinux/deploy-presentations.sh` |
| Appliquer une modification de `deploy/` | `sudo bash /opt/presentations-alpinux/repo/deploy/installer.sh` |
| Changer de version de Node.js | modifier `NODE_VERSION` dans `installer.sh`, pousser, relancer `installer.sh` |
| Changer le secret | supprimer `/etc/default/presentations-webhook`, relancer `installer.sh`, mettre à jour le webhook dans Gitea |
---
## Les pièges à connaître
**Le serveur exécute des copies.** `deploy-presentations.sh` et `webhook.py` sont
copiés dans `/opt/presentations-alpinux` par `installer.sh`. Les modifier dans le dépôt
ne change rien tant que `installer.sh` n'a pas été relancé. C'est voulu : une pull
request fusionnée ne peut pas, à elle seule, changer ce que le service exécute.
**Mais le build, lui, exécute le dépôt.** `scripts/build.mjs` et les dépendances de
`package-lock.json` tournent sur le serveur à chaque push, sous le compte du service.
Une pull request qui touche à `scripts/`, à `package.json` ou à `package-lock.json` se
relit donc comme du code qui s'exécutera sur le serveur — pas comme une diapo.
**`rsync --delete` épargne deux dossiers.** `error/` et `stats/` appartiennent à
ISPConfig et sont exclus de la synchronisation. Tout autre fichier déposé à la main
dans le DocumentRoot est supprimé au déploiement suivant.
**Le droit d'écriture tient à une ACL.** Si ISPConfig réinitialise les droits du site,
le déploiement échoue sur une erreur de permission : relancer `installer.sh` la repose.
**Node.js ne se met pas à jour tout seul.** Il est installé hors d'apt. Suivre les
versions LTS sur <https://nodejs.org/fr/about/previous-releases> et changer
`NODE_VERSION` de temps en temps.