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
181 lines
9.2 KiB
Markdown
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.
|