#!/bin/bash # # Produit l'archive de release de MaBibli et met à jour manifest.toml. # # Pourquoi ce script vit dans `mabibli_ynh` et non dans `mabibli` : # sa sortie utile n'est pas l'archive, c'est le couple (url, sha256) que le # manifeste doit porter. Le garder ici permet de compiler, d'archiver et de mettre à # jour le manifeste en une commande, puis de committer le tout d'un bloc — le # manifeste ne peut pas se désynchroniser de l'archive qu'il décrit. # # Réutilisable en CI (Gitea Actions) sans modification : aucune interaction, aucune # variable propre à un environnement d'intégration, tout passe par des options ou des # variables d'environnement, et le script échoue au premier problème. # # Exemple d'usage en CI : # ./build/publier-release.sh \ # --source-dir "$GITHUB_WORKSPACE/mabibli" \ # --version 0.2.0 \ # --base-url "https://gitea.exemple.org/mathieu/mabibli/releases/download" # set -euo pipefail #================================================= # VALEURS PAR DÉFAUT #================================================= racine_paquet="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" source_dir="${MABIBLI_SOURCE_DIR:-$(cd "$racine_paquet/../mabibli" 2>/dev/null && pwd || true)}" version="${MABIBLI_VERSION:-}" base_url="${MABIBLI_BASE_URL:-https://git.akbar.nohost.me/mathieu/mabibli/releases/download}" output_dir="${MABIBLI_OUTPUT_DIR:-$racine_paquet/build/dist}" maj_manifeste=1 runtime="linux-x64" projet="MaBibli.Api" #================================================= # OPTIONS #================================================= usage() { cat <<'FIN' Usage : publier-release.sh [options] --source-dir CHEMIN Dépôt du code C# (défaut : ../mabibli) --version X.Y.Z Version de la release (défaut : déduite de manifest.toml) --base-url URL Racine des URL de release, sans le tag ni le fichier --output-dir CHEMIN Où déposer l'archive (défaut : build/dist) --no-manifest-update Ne pas réécrire manifest.toml -h, --help Cette aide Variables d'environnement équivalentes : MABIBLI_SOURCE_DIR, MABIBLI_VERSION, MABIBLI_BASE_URL, MABIBLI_OUTPUT_DIR. FIN } while [ $# -gt 0 ]; do case "$1" in --source-dir) source_dir="$2"; shift 2 ;; --version) version="$2"; shift 2 ;; --base-url) base_url="$2"; shift 2 ;; --output-dir) output_dir="$2"; shift 2 ;; --no-manifest-update) maj_manifeste=0; shift ;; -h|--help) usage; exit 0 ;; *) echo "Option inconnue : $1" >&2; usage >&2; exit 2 ;; esac done manifeste="$racine_paquet/manifest.toml" #================================================= # CONTRÔLES PRÉALABLES #================================================= echoerr() { echo "$@" >&2; } if [ -z "$source_dir" ] || [ ! -d "$source_dir" ]; then echoerr "Dépôt du code introuvable : '${source_dir:-}'" echoerr "Précisez-le avec --source-dir." exit 1 fi source_dir="$(cd "$source_dir" && pwd)" if [ ! -d "$source_dir/$projet" ]; then echoerr "'$source_dir' ne contient pas de projet $projet." exit 1 fi if ! command -v dotnet >/dev/null; then echoerr "Le SDK .NET est introuvable. Il n'est nécessaire QUE sur cette machine :" echoerr "le serveur YunoHost reçoit un publish self-contained et n'a pas de SDK." exit 1 fi # La version par défaut se lit dans le manifeste, en retirant le suffixe ~ynhN : # le tag et le nom de fichier portent la version applicative, pas celle du paquet. if [ -z "$version" ]; then version="$(sed -n 's/^version *= *"\([^"~]*\).*"/\1/p' "$manifeste" | head -1)" if [ -z "$version" ]; then echoerr "Impossible de déduire la version depuis $manifeste ; utilisez --version." exit 1 fi echo "Version déduite de manifest.toml : $version" fi #================================================= # COMPILATION #================================================= travail="$(mktemp -d)" # shellcheck disable=SC2064 trap "rm -rf '$travail'" EXIT publish="$travail/publish" echo echo "=== Compilation self-contained $runtime ===" echo " source : $source_dir" echo " version: $version" # `--self-contained` embarque le runtime .NET dans le dossier publié : c'est ce qui # permet au paquet YunoHost de ne dépendre d'aucun `dotnet-runtime` côté serveur. # # `MaBibli.Api` référence `MaBibli.Client` : le client Blazor WebAssembly est compilé # au passage et atterrit dans `wwwroot/`. Un seul publish suffit donc pour les trois # projets. dotnet publish "$source_dir/$projet" \ --configuration Release \ --runtime "$runtime" \ --self-contained \ --output "$publish" \ -p:Version="$version" #================================================= # CONTRÔLES SUR LE RÉSULTAT #================================================= echo echo "=== Vérification du publish ===" if [ ! -x "$publish/$projet" ]; then echoerr "Binaire '$projet' absent ou non exécutable dans le publish." exit 1 fi # Sans `wwwroot`, le service démarrerait et répondrait — en ne servant aucune page. # C'est le défaut qui est passé inaperçu pendant trois phases du projet, faute de # regarder ailleurs que la racine. On le vérifie donc ici, à la source. if [ ! -f "$publish/wwwroot/index.html" ]; then echoerr "wwwroot/index.html absent : le client Blazor n'a pas été embarqué." exit 1 fi # `index.html` doit référencer un script réellement présent. Un placeholder non # substitué (`blazor.webassembly#[.{fingerprint}].js`) donnerait une page blanche # alors que `/` répondrait 200. if grep -q '#\[\.{fingerprint}\]' "$publish/wwwroot/index.html"; then echoerr "index.html contient des placeholders d'empreinte non substitués." echoerr "Voir CLAUDE.md, « Empreintes WASM désactivées »." exit 1 fi for ressource in $(grep -oE '(src|href)="[^"]+"' "$publish/wwwroot/index.html" \ | sed -E 's/^(src|href)="//; s/"$//' \ | grep -vE '^(https?:|data:|#|/$|\.$)'); do if [ ! -f "$publish/wwwroot/$ressource" ]; then echoerr "index.html référence '$ressource', absent du publish." exit 1 fi done echo " binaire, wwwroot et ressources d'index.html : OK" #================================================= # ARCHIVE #================================================= archive_nom="mabibli-$version-$runtime.tar.gz" mkdir -p "$output_dir" archive="$output_dir/$archive_nom" echo echo "=== Archive ===" # Fichiers à la racine de l'archive, sans dossier intermédiaire : cohérent avec # `in_subdir = false` dans manifest.toml. # # Options de reproductibilité : sans elles, deux compilations identiques donneraient # deux sha256 différents (dates, ordre de parcours, uid/gid). En CI comme en local, # on veut pouvoir vérifier qu'une archive correspond bien à un commit. tar --create --gzip \ --file="$archive" \ --directory="$publish" \ --owner=0 --group=0 --numeric-owner \ --mtime="@${SOURCE_DATE_EPOCH:-0}" \ --sort=name \ . somme="$(sha256sum "$archive" | cut -d' ' -f1)" taille="$(du -h "$archive" | cut -f1)" taille_octets="$(stat -c%s "$archive")" echo " fichier : $archive" echo " taille : $taille ($taille_octets octets)" echo " sha256 : $somme" #================================================= # MISE À JOUR DU MANIFESTE #================================================= url="$base_url/v$version/$archive_nom" if [ "$maj_manifeste" -eq 1 ]; then echo echo "=== Mise à jour de manifest.toml ===" # Le suffixe ~ynhN est conservé s'il existe déjà pour cette version, sinon # remis à ~ynh1 : un changement de version applicative repart toujours de 1. version_actuelle="$(sed -n 's/^version *= *"\([^"]*\)".*/\1/p' "$manifeste" | head -1)" if [[ "$version_actuelle" == "$version~ynh"* ]]; then version_paquet="$version_actuelle" echo " version : $version_paquet (inchangée)" else version_paquet="$version~ynh1" echo " version : $version_actuelle -> $version_paquet" fi python3 - "$manifeste" "$version_paquet" "$url" "$somme" <<'PY' import re, sys chemin, version, url, somme = sys.argv[1:5] with open(chemin, encoding="utf-8") as f: texte = f.read() remplacements = [ (r'^version = ".*"$', f'version = "{version}"'), (r'^ amd64\.url = ".*"$', f' amd64.url = "{url}"'), (r'^ amd64\.sha256 = ".*"$', f' amd64.sha256 = "{somme}"'), ] for motif, valeur in remplacements: texte, n = re.subn(motif, valeur, texte, count=1, flags=re.MULTILINE) if n != 1: sys.exit(f"Motif introuvable dans le manifeste : {motif}") with open(chemin, "w", encoding="utf-8") as f: f.write(texte) PY echo " url : $url" echo " sha256 : $somme" fi #================================================= # SUITE À DONNER #================================================= cat <