From 712741a88369fb778369025ad429cf4dbbd87d25 Mon Sep 17 00:00:00 2001 From: mathieu Date: Thu, 20 Aug 2026 19:49:38 +0200 Subject: [PATCH] =?UTF-8?q?Renvoie=20la=20documentation=20vers=20le=20READ?= =?UTF-8?q?ME=20du=20d=C3=A9p=C3=B4t=20du=20code?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit publier.sh enchaîne la publication de bout en bout : version déduite de la précédente, archive, manifeste, commit et push du paquet, tag et push du code, avec vérification côté distant. Seul le dépôt de l'archive dans la release Gitea reste manuel — l'URL est rappelée en fin de sortie. PUBLICATION.md et A_FAIRE.md sont supprimés : leur contenu vit désormais dans le README de mabibli, point d'entrée unique du projet. doc/DESCRIPTION.md et doc/ADMIN.md restent ici, YunoHost les lisant lui-même. Co-Authored-By: Claude Opus 5 --- A_FAIRE.md | 84 --------------- PUBLICATION.md | 262 ----------------------------------------------- README.md | 82 ++++----------- build/publier.sh | 223 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 243 insertions(+), 408 deletions(-) delete mode 100644 A_FAIRE.md delete mode 100644 PUBLICATION.md create mode 100755 build/publier.sh diff --git a/A_FAIRE.md b/A_FAIRE.md deleted file mode 100644 index 02c71b8..0000000 --- a/A_FAIRE.md +++ /dev/null @@ -1,84 +0,0 @@ -# État du paquet - -Mis à jour le 2026-08-18, après une installation, une sauvegarde et une mise à jour -réussies sur le serveur réel. - -**Aucune tâche bloquante.** Ce fichier garde les contraintes permanentes et le -journal de ce qui a été réglé — le *pourquoi* resservira à la prochaine version. - ---- - -## Publier une nouvelle version - -📖 La marche à suivre est dans [`PUBLICATION.md`](PUBLICATION.md) — première mise en -production, montée de version, retour arrière, désinstallation. - -En résumé : `./build/publier-release.sh --version X.Y.Z`, tag et release sur le dépôt -du code, téléversement de l'archive, puis commit **et push** du manifeste. - -Le suffixe `~ynhN` se bump à la main quand seul le paquet change (conf, scripts) sans -nouvelle archive — c'est ce qui a été fait pour `~ynh2` et `~ynh3`. - ---- - -# Contraintes permanentes — ce ne sont pas des tâches - -## Le dépôt du code doit rester public - -`ynh_setup_source` télécharge **sans jeton**. Sur un dépôt privé, Gitea répond -**404 et non 403** à un anonyme : le symptôme est identique à « la release n'existe -pas », ce qui envoie chercher au mauvais endroit. Ne pas mettre de jeton dans l'URL -du manifeste — il serait lisible sur le serveur. - -## L'application exige un domaine entier, pas un sous-chemin - -MaBibli s'installe sur `mabibli.mondomaine.tld`, **pas** sur `mondomaine.tld/mabibli`. - -Deux éléments sont figés **à la compilation** du client Blazor WebAssembly : la balise -`` de `index.html`, et les empreintes d'intégrité de -`service-worker-assets.js`. Les réécrire sur le serveur casserait le service worker, -donc le mode hors-ligne — et rien n'est recompilé sur le serveur, c'est tout l'intérêt -du self-contained. Le paquet déclare donc l'application en `full_domain`. - -Lever cette contrainte demanderait une archive **par chemin d'installation**, ou une -compilation sur le serveur. Les deux annulent le bénéfice du self-contained. - -## `install_dir` finit par appartenir à l'application, pas à root - -Le manifeste demande `owner = "root:rwx"` pour que le service ne puisse pas réécrire -ses binaires, mais le helper `_ynh_apply_default_permissions` repasse derrière avec un -`chown -R mabibli:mabibli`. L'intention tient quand même, portée par -`ProtectSystem=strict` dans l'unité systemd : tout est en lecture seule sauf -`ReadWritePaths=`, qui ne liste que le `data_dir`. - -⚠️ Ne pas retirer `ProtectSystem=strict` en croyant que la propriété des fichiers -protège encore. - ---- - -# Fait - -## Les deux pièges systemd — 2026-08-18 - -Découverts à la première installation réelle, chacun a coûté un cycle complet. Ni -l'un ni l'autre ne peut sortir d'un lancement du binaire à la main : ils tiennent au -gestionnaire de services. - -| Piège | Symptôme | Correction | -|---|---|---| -| `Environment=` découpe sur les espaces | `ArgumentException … at index 0` | guillemeter toute la ligne | -| `ProtectHome=yes` masque `/home` | `SQLite Error 14: unable to open database file` | `ProtectHome=tmpfs` + `BindPaths=` | - -Détail et mesures dans les commentaires de `conf/systemd.service`, à ne pas retirer. - -## L'URL de démonstration a été remplacée — 2026-08-18 - -`manifest.toml` portait `gitea.example.org` : l'installation s'arrêtait net au -`Prefetching asset main`. Corrigé partout (`manifest.toml`, `README.md`, -`conf/systemd.service`, et la valeur par défaut de `--base-url`). Contrôle : -`grep -rn "gitea.example.org" .` ne doit plus rien remonter que ce fichier-ci. - -## L'archive ne va plus dans git — 2026-08-18 - -`build/dist/` est ignoré. L'archive vit dans la release Gitea ; committée, elle -ajouterait 69 Mo d'historique **par version**, définitivement. diff --git a/PUBLICATION.md b/PUBLICATION.md deleted file mode 100644 index 2ee499a..0000000 --- a/PUBLICATION.md +++ /dev/null @@ -1,262 +0,0 @@ -# Mettre MaBibli en production, et la mettre à jour - -Deux marches à suivre, éprouvées sur un serveur réel le 2026-08-18 : la **première mise -en production**, puis la **montée de version**. Les contraintes qui les gouvernent sont -expliquées dans `A_FAIRE.md` — ici, ce sont les gestes. - -Principe qui explique toute la chaîne : **le serveur ne compile jamais**. Il télécharge -une archive déjà produite, vérifie son empreinte, et la déploie. Le SDK .NET n'est -nécessaire que sur la machine de développement. - -``` -machine de dev Gitea serveur YunoHost -────────────── ───── ──────────────── -publier-release.sh - → archive .tar.gz ────────► release du dépôt `mabibli` - → manifest.toml ────────► dépôt `mabibli_ynh` ──────► yunohost app install - (url + sha256) (vérifie le sha256) -``` - ---- - -# A. Première mise en production - -## A.1 Préalables, une seule fois - -- Le dépôt du code (`mabibli`) est **public** sur Gitea. `ynh_setup_source` télécharge - sans jeton ; sur un dépôt privé Gitea répond 404 — indiscernable d'une release - absente. -- Le domaine dédié existe côté YunoHost. MaBibli s'installe sur un **domaine entier** - (`mabibli.mondomaine.tld`), jamais sur un sous-chemin : - -```bash -sudo yunohost domain add mabibli.mondomaine.tld -``` - -- Le SDK .NET est installé sur la machine de développement (`dotnet --version`). - -## A.2 Produire l'archive - -Depuis `mabibli_ynh`, avec le dépôt du code à côté (`../mabibli`) : - -```bash -./build/publier-release.sh -``` - -Le script compile en `Release` self-contained, **vérifie le publish** (binaire présent, -`wwwroot/` embarqué, toutes les ressources d'`index.html` réellement sur disque, aucun -placeholder d'empreinte non substitué), produit l'archive de façon reproductible, en -calcule le `sha256`, et réécrit `version`, `amd64.url` et `amd64.sha256` dans -`manifest.toml`. - -À contrôler dans sa sortie : la ligne `binaire, wwwroot et ressources d'index.html : OK`, -et le `sha256` affiché — c'est celui que le manifeste porte désormais. - -## A.3 Déposer la release - -```bash -cd ../mabibli && git tag v0.1.0 && git push origin main --tags -``` - -Puis, **dans l'interface Gitea** : créer la release `v0.1.0` sur le dépôt `mabibli`, et y -téléverser `build/dist/mabibli-0.1.0-linux-x64.tar.gz`. - -Vérification, à faire **sans être authentifié** (autre navigateur, ou `curl` comme -ci-dessous) : - -```bash -curl -fsSLI "https://git.akbar.nohost.me/mathieu/mabibli/releases/download/v0.1.0/mabibli-0.1.0-linux-x64.tar.gz" | head -1 -``` - -Attendu : `HTTP/2 200`. Un 404 signifie soit que la release n'est pas déposée, soit que -le dépôt est privé — les deux se ressemblent, commencer par vérifier la visibilité. - -## A.4 Pousser le paquet - -⚠️ **L'étape la plus facile à oublier.** `yunohost app install ` lit le manifeste -**depuis Gitea**, jamais la copie locale : un manifeste corrigé mais non poussé n'existe -pas pour le serveur. - -```bash -cd ../mabibli_ynh && git add -A && git commit -m "Publier la version 0.1.0" && git push -``` - -## A.5 Installer - -```bash -sudo yunohost app install https://git.akbar.nohost.me/mathieu/mabibli_ynh --debug -``` - -YunoHost demande le domaine (celui créé en A.1) et le groupe autorisé (`all_users`). - -**Ce qu'il faut voir passer :** `Prefetching asset main` sur la bonne URL, puis -l'installation des fichiers, nginx, systemd, et enfin `The service mabibli has correctly -executed the action start` — le script attend la ligne `Application started` du journal, -ce qui fait échouer franchement l'installation si les migrations EF Core ne passent pas. - -## A.6 Vérifier - -```bash -sudo ss -tlnp | grep -i mabibli -``` - -Doit montrer **`127.0.0.1:` uniquement**. C'est une frontière de sécurité : sur -`0.0.0.0`, n'importe qui sur le réseau pourrait forger l'en-tête `YNH_USER` et se faire -passer pour un membre du foyer. - -```bash -sudo ls -l /home/yunohost.app/mabibli -``` - -`mabibli.db` doit exister : les migrations se sont appliquées seules au premier -démarrage. (Pas de `-wal` ni `-shm` au repos, c'est normal — SQLite fait un checkpoint à -la fermeture de la dernière connexion.) - -Enfin, dans un navigateur sur `https://mabibli.mondomaine.tld` : le portail authentifie, -et l'application affiche **ton** nom d'utilisateur YunoHost — pas « anonyme ». C'est le -seul contrôle qui éprouve réellement l'intégration SSO. - ---- - -# B. Monter de version — exemple : 0.1.0 → 0.1.1 - -## B.1 Ce qui distingue une version applicative d'une révision de paquet - -| Ce qui change | Version | Nouvelle archive ? | -|---|---|---| -| Le code C# | `0.1.0` → `0.1.1~ynh1` | **oui** | -| Seulement le paquet (conf systemd/nginx, scripts) | `0.1.0~ynh1` → `0.1.0~ynh2` | non | - -Le second cas est le plus simple : bump du suffixe `~ynhN` **à la main** dans -`manifest.toml`, commit, push, puis directement l'étape B.5. L'archive et son `sha256` -ne bougent pas. (C'est ce qui a été fait pour `~ynh2` et `~ynh3`.) - -La suite décrit le premier cas. - -## B.2 Compiler et vérifier la nouvelle version - -Le code est prêt et committé dans `mabibli`. Depuis `mabibli_ynh` : - -```bash -./build/publier-release.sh --version 0.1.1 -``` - -⚠️ **`--version` est obligatoire ici.** Sans lui, le script déduit la version de -`manifest.toml` et reproduirait 0.1.0. Il remet aussi le suffixe à `~ynh1` : une -nouvelle version applicative repart toujours de 1. - -Note le `sha256` affiché — il ne sera plus jamais le même, même à code identique si les -dépendances bougent. - -## B.3 Tag et release - -```bash -cd ../mabibli && git tag v0.1.1 && git push origin main --tags -``` - -Créer la release `v0.1.1` dans Gitea, y téléverser -`build/dist/mabibli-0.1.1-linux-x64.tar.gz`, puis vérifier sans authentification : - -```bash -curl -fsSLI "https://git.akbar.nohost.me/mathieu/mabibli/releases/download/v0.1.1/mabibli-0.1.1-linux-x64.tar.gz" | head -1 -``` - -## B.4 Pousser le paquet - -```bash -cd ../mabibli_ynh && git add -A && git commit -m "Publier la version 0.1.1" && git push -``` - -## B.5 Sauvegarder, puis mettre à jour - -YunoHost prend **lui-même** une sauvegarde de sécurité avant la mise à jour -(`mabibli-pre-upgrade1`), mais elle **ne contient pas le répertoire de données** -(`BACKUP_CORE_ONLY`) : elle sert à restaurer l'application, pas la bibliothèque. Prendre -une sauvegarde complète reste donc utile avant une version qui touche au schéma : - -```bash -sudo yunohost backup create --apps mabibli -``` - -```bash -sudo yunohost app upgrade mabibli -u https://git.akbar.nohost.me/mathieu/mabibli_ynh --debug -``` - -⚠️ **L'option `-u` n'est pas facultative ici.** MaBibli n'est pas dans le catalogue -officiel : sans elle, YunoHost ne sait pas où retrouver le paquet et refuse d'emblée — -« mabibli is not in the catalog (anymore?) », puis « No apps can be upgraded ». Rien -n'est cassé pour autant, la commande n'a simplement pas commencé. C'est la **même URL** -qu'à l'installation, celle du dépôt `_ynh`, jamais celle de l'archive. - -(Ajouter `--force` seulement pour réappliquer une version identique, par exemple en -mise au point du paquet.) - -Le script arrête le service **avant** de remplacer les binaires — deux processus sur la -même base SQLite pendant une migration est exactement ce qu'il faut éviter — puis -attend `Application started`, avec un délai de 120 s : une migration sur base remplie -prend plus de temps que la création d'un schéma vide. - -## B.6 Vérifier après mise à jour - -```bash -sudo systemctl status mabibli --no-pager && sudo journalctl -u mabibli -n 30 --no-pager -``` - -Puis, dans le navigateur, le contrôle qui compte vraiment : **les livres sont toujours -là**. C'est ce qui valide que la base vit bien dans le répertoire de données et non à -côté du binaire — `upgrade` remplace intégralement `/var/www/mabibli`. - -Vider le cache du navigateur n'est pas nécessaire : le service worker compare les -empreintes et propose « Mettre à jour ». Sur mobile, un onglet resté ouvert peut -retarder la bascule — c'est précisément ce que le bandeau de mise à jour sert à -débloquer. - -## B.7 Si la mise à jour échoue - -YunoHost restaure automatiquement la sauvegarde de sécurité quand le script échoue. Si -le service démarre mais que l'application se comporte mal, revenir en arrière à la main : - -```bash -sudo yunohost backup list -``` - -```bash -sudo yunohost app remove mabibli --purge -``` - -```bash -sudo yunohost backup restore --apps mabibli -``` - -⚠️ `--purge` efface le répertoire de données. Ne le faire qu'avec une archive contenant -la bibliothèque sous la main — celle de B.5, pas `mabibli-pre-upgrade1`. - ---- - -# C. Désinstaller - -```bash -sudo yunohost app remove mabibli -``` - -Retire le service, la conf nginx, la permission SSO, l'utilisateur système, le port et -`/var/www/mabibli`. **`/home/yunohost.app/mabibli` survit**, donc la bibliothèque aussi : -une désinstallation faite trop vite ne doit pas être irréversible. - -⚠️ Corollaire à connaître en phase d'essai : réinstaller après un `remove` sans purge -**retrouve l'ancienne base**. Ce n'est pas une installation vierge, même si tout le -reste est neuf. - -Pour tout effacer, données comprises : - -```bash -sudo yunohost app remove mabibli --purge -``` - -Contrôle qu'il ne reste rien : - -```bash -systemctl status mabibli; sudo ls -d /var/www/mabibli /home/yunohost.app/mabibli 2>&1; getent passwd mabibli -``` - -Les trois doivent être négatifs. Le domaine, lui, reste déclaré dans YunoHost. diff --git a/README.md b/README.md index 79ae86f..5ff51bd 100644 --- a/README.md +++ b/README.md @@ -4,74 +4,32 @@ Paquet d'installation de [MaBibli](https://git.akbar.nohost.me/mathieu/mabibli), gestion de bibliothèque personnelle (catalogue, prêts, scan ISBN). Ce dépôt **ne contient aucun code C#**. Il porte uniquement de quoi installer sur YunoHost une -archive déjà compilée : le manifeste, les configurations nginx et systemd, et les scripts -d'installation. +archive déjà compilée : le manifeste, les configurations nginx et systemd, les scripts +d'installation, et les scripts de publication. -## Contenu +## 📖 Toute la documentation est dans le dépôt du code -| Fichier | Rôle | -|---|---| -| `manifest.toml` | Identité, version, URL de l'archive et son `sha256`, ressources (utilisateur système, répertoires, port, permissions) | -| `conf/systemd.service` | Unité du service — écoute sur `127.0.0.1`, base dans le répertoire de données, durcissement | -| `conf/nginx.conf` | Reverse proxy et intégration SSOwat | -| `scripts/_common.sh` | Variables partagées et sauvegarde/restauration cohérente de la base SQLite | -| `scripts/install` `remove` `upgrade` `backup` `restore` | Cycle de vie de l'application | -| `build/publier-release.sh` | Compile, archive, calcule le `sha256` et met à jour `manifest.toml` | +Elle a été regroupée dans **[le README de `mabibli`](https://git.akbar.nohost.me/mathieu/mabibli#readme)**, +qui est le point d'entrée unique du projet : contenu et points de conception du paquet, +publication d'une version, mise en production pas à pas, montée de version, retour arrière, +désinstallation, et contraintes permanentes. + +Il n'y a qu'une seule chaîne de publication et un seul projet : deux jeux de documentation +finissaient par diverger, et l'on ne savait plus lequel faisait autorité. + +Ne restent ici que les deux fichiers que **YunoHost lit lui-même** et qui doivent donc vivre +à côté du manifeste : + +- `doc/DESCRIPTION.md` — présentation affichée dans le catalogue d'applications +- `doc/ADMIN.md` — authentification, emplacement des données, sauvegarde ; affichée dans + l'interface d'administration une fois l'application installée ## Publier une nouvelle version -Le serveur ne compile jamais : il télécharge une archive déjà produite. La chaîne est donc -« compiler ici, déposer une release, mettre à jour le manifeste ». - ```bash # Depuis ce dépôt, avec le dépôt du code à côté (../mabibli) -./build/publier-release.sh --version 0.1.1 +./build/publier.sh ``` -Le script : - -1. lance `dotnet publish MaBibli.Api -c Release -r linux-x64 --self-contained` ; -2. vérifie que le binaire est là, que `wwwroot/` a bien été embarqué, et que **toutes** les - ressources référencées par `index.html` existent réellement dans le publish ; -3. produit `build/dist/mabibli--linux-x64.tar.gz`, de façon reproductible ; -4. en calcule le `sha256` ; -5. réécrit `version`, `amd64.url` et `amd64.sha256` dans `manifest.toml`. - -Restent à faire à la main : créer le tag et la release sur Gitea, y téléverser l'archive, puis -committer et **pousser** `manifest.toml` — YunoHost lit le manifeste depuis Gitea, pas la copie -locale. - -📖 **La marche à suivre complète est dans [`PUBLICATION.md`](PUBLICATION.md)** : première mise en -production, montée de version (avec les contrôles à chaque étape), retour arrière, et -désinstallation. - -Le script ne dépend d'aucun environnement de CI : tout passe par des options ou des variables -d'environnement (`MABIBLI_SOURCE_DIR`, `MABIBLI_VERSION`, `MABIBLI_BASE_URL`, -`MABIBLI_OUTPUT_DIR`), il ne pose aucune question et s'arrête à la première erreur. Il est donc -utilisable tel quel dans Gitea Actions le jour où un runner sera disponible. - -## Points de conception - -**Le service n'écoute que sur `127.0.0.1`.** L'application déduit l'identité de l'en-tête -`YNH_USER` injecté par SSOwat ; cet en-tête n'est digne de confiance que si nginx est le seul -chemin d'accès. Un service exposé sur le réseau permettrait de forger `YNH_USER` et de -contourner le portail. La contrainte est écrite dans `conf/systemd.service` (`ASPNETCORE_URLS`) -et le port n'est pas ouvert au pare-feu. - -**Aucune compilation sur le serveur.** Le publish est *self-contained* : il embarque son propre -runtime .NET, donc aucun paquet `dotnet-runtime` n'est nécessaire. `ynh_setup_source` vérifie le -`sha256` de l'archive avant de la déployer. - -**Les données survivent aux mises à jour.** La base SQLite vit dans le répertoire de données, pas -à côté du binaire ; `upgrade` ne remplace que le répertoire d'installation. - -**La sauvegarde passe par l'API de sauvegarde en ligne de SQLite.** La base est en mode WAL : -copier le seul fichier `.db` d'une base active peut ne rien sauvegarder du tout. Voir -`doc/ADMIN.md`. - -## Documentation - -- `doc/DESCRIPTION.md` — présentation affichée dans le catalogue YunoHost -- `doc/ADMIN.md` — authentification, emplacement des données, sauvegarde, contraintes -- `PUBLICATION.md` — mise en production et montée de version, pas à pas -- `A_FAIRE.md` — contraintes permanentes et journal des points réglés +Le détail — ce que le script vérifie, ce qui reste manuel, et la marche à suivre côté serveur — +est dans le README de `mabibli`. diff --git a/build/publier.sh b/build/publier.sh new file mode 100755 index 0000000..4404e1d --- /dev/null +++ b/build/publier.sh @@ -0,0 +1,223 @@ +#!/bin/bash +# +# Publie une version de MaBibli de bout en bout, en une commande. +# +# Enchaîne ce qui était jusqu'ici tapé à la main, toujours dans le même ordre : +# +# 1. produire l'archive et mettre à jour manifest.toml (publier-release.sh) +# 2. committer et pousser mabibli_ynh (le serveur lit Gitea, pas le local) +# 3. taguer et pousser mabibli (le tag porte le code de la release) +# +# La version se choisit ici, à partir de celle déjà publiée : rien à aller chercher. +# La seule chose qui reste manuelle est le dépôt de l'archive dans la release Gitea — +# l'URL est rappelée en fin de sortie. +# +set -euo pipefail + +racine_paquet="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +manifeste="$racine_paquet/manifest.toml" + +source_dir="${MABIBLI_SOURCE_DIR:-$(cd "$racine_paquet/../mabibli" 2>/dev/null && pwd || true)}" +base_url="${MABIBLI_BASE_URL:-https://git.akbar.nohost.me/mathieu/mabibli/releases/download}" +url_releases="${MABIBLI_URL_RELEASES:-https://git.akbar.nohost.me/mathieu/mabibli/releases}" +version="" +increment="" +sans_confirmation=0 + +usage() { + cat <<'FIN' +Usage : publier.sh [options] + + --version X.Y.Z Version à publier (sinon : demandée, à partir de la précédente) + --patch Incrémenter le dernier chiffre (0.3.0 -> 0.3.1) + --minor Incrémenter le chiffre du milieu (0.3.0 -> 0.4.0) + --major Incrémenter le premier chiffre (0.3.0 -> 1.0.0) + --oui Ne rien demander (pour une CI) + -h, --help Cette aide +FIN +} + +while [ $# -gt 0 ]; do + case "$1" in + --version) version="$2"; shift 2 ;; + --patch|--minor|--major) increment="${1#--}"; shift ;; + --oui|-y) sans_confirmation=1; shift ;; + -h|--help) usage; exit 0 ;; + *) echo "Option inconnue : $1" >&2; usage >&2; exit 2 ;; + esac +done + +echoerr() { echo "$@" >&2; } + +if [ -z "$source_dir" ] || [ ! -d "$source_dir/.git" ]; then + echoerr "Dépôt du code introuvable : '${source_dir:-}' (MABIBLI_SOURCE_DIR)." + exit 1 +fi + +#================================================= +# LA VERSION PRÉCÉDENTE, DEUX SOURCES QUI DOIVENT S'ACCORDER +#================================================= + +# Le tag fait foi : c'est lui qui désigne le code publié. Le manifeste ne porte que la +# dernière version *empaquetée*, qui peut être en retard si une publication a échoué en +# cours de route. On prend le plus élevé des deux pour ne jamais proposer un numéro déjà pris. +version_tag="$(git -C "$source_dir" tag --list 'v[0-9]*' --sort=-v:refname | head -1 | sed 's/^v//')" +version_manifeste="$(sed -n 's/^version *= *"\([^"~]*\).*"/\1/p' "$manifeste" | head -1)" + +precedente="$(printf '%s\n%s\n' "$version_tag" "$version_manifeste" \ + | grep -E '^[0-9]+(\.[0-9]+)*$' | sort -V | tail -1)" + +if [ -z "$precedente" ]; then + echoerr "Aucune version précédente trouvée (ni tag, ni manifest.toml) ; utilisez --version." + exit 1 +fi + +echo "Dernière version publiée : $precedente" +[ "$version_tag" != "$version_manifeste" ] && + echo " ⚠ tag v${version_tag:-—} et manifest.toml ${version_manifeste:-—} divergent" + +suivante() { # suivante + IFS=. read -r maj min cor <<<"$1" + case "$2" in + major) echo "$((maj + 1)).0.0" ;; + minor) echo "$maj.$((min + 1)).0" ;; + patch) echo "$maj.$min.$((cor + 1))" ;; + esac +} + +if [ -z "$version" ] && [ -n "$increment" ]; then + version="$(suivante "$precedente" "$increment")" +fi + +if [ -z "$version" ]; then + if [ "$sans_confirmation" -eq 1 ] || [ ! -t 0 ]; then + echoerr "Pas de terminal pour demander la version ; utilisez --version ou --patch/--minor/--major." + exit 1 + fi + echo + echo " 1) $(suivante "$precedente" patch) (correction)" + echo " 2) $(suivante "$precedente" minor) (nouveautés)" + echo " 3) $(suivante "$precedente" major) (rupture)" + echo " 4) autre — à saisir" + echo + read -rp "Version à publier [1] ? " choix + case "${choix:-1}" in + 1) version="$(suivante "$precedente" patch)" ;; + 2) version="$(suivante "$precedente" minor)" ;; + 3) version="$(suivante "$precedente" major)" ;; + 4) read -rp "Numéro de version : " version ;; + *) version="$choix" ;; # un numéro tapé directement passe aussi + esac +fi + +if ! [[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echoerr "Version invalide : '$version' (attendu X.Y.Z)." + exit 1 +fi + +if git -C "$source_dir" rev-parse "v$version" >/dev/null 2>&1; then + echoerr "Le tag v$version existe déjà dans $source_dir." + exit 1 +fi + +#================================================= +# CE QUI DOIT ÊTRE VRAI AVANT DE COMPILER +#================================================= + +# Le tag va désigner le HEAD du dépôt du code : un travail non committé ne serait donc +# pas dans la release, alors même que l'archive, elle, le contiendrait. +if [ -n "$(git -C "$source_dir" status --porcelain)" ]; then + echoerr "Le dépôt du code a des modifications non committées :" + git -C "$source_dir" status --short >&2 + exit 1 +fi + +branche="$(git -C "$source_dir" rev-parse --abbrev-ref HEAD)" + +echo +echo "=== À publier ===" +echo " version : $precedente -> $version" +echo " code : $source_dir ($branche, $(git -C "$source_dir" rev-parse --short HEAD))" +echo " paquet : $racine_paquet" + +if [ "$sans_confirmation" -ne 1 ]; then + read -rp "Continuer ? [O/n] " reponse + case "${reponse:-o}" in + [oO]*) ;; + *) echo "Abandon."; exit 1 ;; + esac +fi + +#================================================= +# 1. ARCHIVE + MANIFESTE +#================================================= + +"$racine_paquet/build/publier-release.sh" --version "$version" --source-dir "$source_dir" --base-url "$base_url" + +archive_nom="mabibli-$version-linux-x64.tar.gz" +archive="${MABIBLI_OUTPUT_DIR:-$racine_paquet/build/dist}/$archive_nom" + +#================================================= +# 2. LE PAQUET (le serveur lit Gitea, jamais la copie locale) +#================================================= + +echo +echo "=== Paquet : commit et push ===" +git -C "$racine_paquet" add -A +if git -C "$racine_paquet" diff --cached --quiet; then + echo " rien à committer (manifeste déjà à jour)" +else + git -C "$racine_paquet" commit -m "Publier la version $version" +fi +git -C "$racine_paquet" push + +#================================================= +# 3. LE CODE : tag et push +#================================================= + +echo +echo "=== Code : tag v$version et push ===" +git -C "$source_dir" tag "v$version" +git -C "$source_dir" push origin "$branche" --tags + +# Un push qui échoue à demi ne se voit pas dans la sortie de git : on redemande au distant. +verifier_pousse() { # verifier_pousse + if git -C "$1" ls-remote --exit-code origin "$2" >/dev/null 2>&1; then + echo " ✓ $2 présent sur origin" + else + echoerr " ✗ $2 ABSENT sur origin — le push n'a pas abouti." + exit 1 + fi +} + +echo +echo "=== Vérification côté distant ===" +verifier_pousse "$source_dir" "refs/tags/v$version" +verifier_pousse "$racine_paquet" "refs/heads/$(git -C "$racine_paquet" rev-parse --abbrev-ref HEAD)" + +#================================================= +# CE QUI RESTE À FAIRE À LA MAIN +#================================================= + +url_archive="$base_url/v$version/$archive_nom" + +cat <