Compare commits

..
28 Commits
Author SHA1 Message Date
mathieu 241f61f433 Publier la version 1.1.0 2026-09-09 01:30:08 +02:00
mathieu 81f885859b Publier la version 0.5.1 2026-08-22 14:34:51 +02:00
mathieuandClaude Opus 5 d0ebecc233 Restaure les vraies URL, et fixe l'acces a all_users sans le demander
Le manifeste de la 0.5.0, deja publiee, s'est mis a pointer une archive
introuvable — l'installation echouait sur ynh_setup_source, sans que le
symptome designe la cause.

Le remplacement global des URL avait reecrit amd64.url, c'est-a-dire
precisement le champ que la documentation ecrite le meme jour designait comme
« a ne jamais changer a la main », puisque publier.sh le regenere a chaque
publication.

⚠ L'erreur de conception est plus large : le placeholder avait sa place dans la
COPIE publique, pas dans le depot de travail, qui sert au deploiement reel et
doit rester operationnel. Anonymiser un depot qui sert encore, c'est le casser.

Ce qui reste, et qui vaut : depot_code commande toujours toutes les URL
derivees, et le garde-fou refuse toujours de publier avec une URL en
example.org — il ne se declenche simplement plus ici, puisqu'il sert a la copie
publique.

sha256 inchange : seule l'URL avait bouge, le manifeste retrouve exactement son
etat de publication.

--- L'installation ne demande plus qui a acces

init_main_permission quitte [install] au profit de
resources.permissions.main.allowed = "all_users". Verifie dans le coeur
YunoHost (src/utils/resources.py) :

    init_allowed = infos["allowed"] or self.get_setting(f"init_{perm}_permission") or []

allowed L'EMPORTE sur la question. La garder l'aurait donc posee pour rien, en
laissant croire que la reponse comptait.

⚠ Ce n'est pas un reglage de confort : visitors casserait deux invariants du
code. L'application n'a aucune authentification propre — elle fait confiance a
YNH_USER parce que SSOwat l'ecrase a chaque requete, exactement comme elle
n'ecoute que sur 127.0.0.1 — et toute personne autorisee peut SUPPRIMER
n'importe quel livre du catalogue commun, sans role ni lecture seule.

⚠ Pas de protected = true, delibere : restreindre a un groupe dedie reste
legitime, et c'est l'affaire de l'administrateur. C'est l'elargissement qui est
un contresens, pas le reglage.

⚠ allowed n'agit qu'a la CREATION de la permission (if perm not in
existing_perms) : une montee de version ne realigne rien. Un commentaire de
manifeste n'atteignant aucun administrateur, l'avertissement vit dans
doc/DESCRIPTION.md (page d'installation) et doc/ADMIN.md (interface
d'administration) — les deux seuls fichiers du paquet que YunoHost lit lui-meme.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-22 14:29:10 +02:00
mathieuandClaude Opus 5 5ebfd4a82b Remplace l'URL du depot par un placeholder, centralise, et refuse de publier avec
Sortie du projet : l'URL du Gitea prive disparait au profit de
https://forge.example.org/mabibli — example.org est reserve a la documentation
par la RFC 2606, donc definitivement injoignable.

Une seule ligne commande tout le reste : depot_code, en tete de publier.sh, d'ou
derivent les URL de release, d'archive et le message de fin. --depot-code fait la
meme chose sans modifier le fichier.

⚠ publier.sh REFUSE desormais de publier tant que le placeholder est en place, et
il refuse avant de compiler, comme ses autres garde-fous. Sans cela on ecrirait
dans manifest.toml une amd64.url que personne ne peut telecharger, et l'echec
surviendrait bien plus loin — a l'installation, dans ynh_setup_source, sans
rapport apparent avec la cause.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-22 13:03:45 +02:00
mathieuandClaude Opus 5 ff0c2d653d Raccourcit doc/ADMIN.md a ce qu'un administrateur doit faire
Le fichier expliquait POURQUOI les choix avaient ete faits — le mode WAL mesure
a 4 Ko contre 205 Ko, la justification du domaine entier, la portee commune ou
personnelle de chaque donnee. Ces raisons ont leur place dans le README du depot
du code, pas dans un panneau que YunoHost affiche apres installation.

Ne restent que les gestes : ou sont les donnees, ou se reglent les acces, la
commande de sauvegarde manuelle correcte, et ce dont le serveur a besoin pour
fonctionner. 82 lignes -> 47.

⚠ L'acces sortant vers catalogue.bnf.fr et openlibrary.org est conserve : c'est
le seul endroit du projet ou il soit documente.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-22 12:53:02 +02:00
mathieu 4b59149f36 Publier la version 0.5.0 2026-08-22 12:16:43 +02:00
mathieuandClaude Opus 5 1f115790ee Range --archive-seule au lieu de le supprimer, et mesure la RAM
Le mode etait justifie dans CLAUDE.md comme « la brique reutilisable en CI ».
C'etait faux : le README du depot du code s'en sert depuis toujours comme
chemin de reprise a la main quand publier.sh echoue en cours de route. Il a
donc failli partir sur une justification erronee.

Garde et range : 8 ramifications ramenees a 5, dont une seule assouplit encore
un garde-fou, contre trois auparavant. Deux defauts reels en sont sortis — un
depot sale n'etait PAS signale dans ce mode, et la version y retombait sur
celle deja publiee, piege que le README documentait au lieu de le corriger.

refuser() separe le constat du conseil : le constat vaut dans les deux modes,
« choisissez un numero libre » est faux en reprise, ou le tag vise est
justement celui qu'on veut retrouver.

Le depot git devient une exigence inconditionnelle en tete, ce qui supprime
quatre tests -d .git disperses plus bas.

Les cinq variables d'environnement jumelles disparaissent : chacune doublait
une option qu'elle repetait, l'aide en listait dix pour cinq reglages, et rien
ne disait laquelle l'emportait.

ram.runtime passe de 200M a 256M. Mesure sur le serveur : pic reel de
208 793 600 o, soit 199,1 Mio, contre 200M declares — 0,4 % de marge, c'est-a-
dire aucune. systemctl show -p MemoryPeak ne renvoie rien sur ce serveur et
reussit en silence ; c'est le cgroup qui garde le maximum.

Les deux doc/*_fr.md, identiques octet pour octet a leurs jumeaux, sont
supprimes : YunoHost retombe sur les fichiers par defaut, deja en francais.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-22 12:15:24 +02:00
mathieu fac694da03 Publier la version 0.4.3 2026-08-22 02:17:01 +02:00
mathieu 6d94080d8a Publier la version 0.4.2 2026-08-22 00:55:53 +02:00
mathieuandClaude Opus 5 7c7e686c7a Fonds les deux scripts de publication en un seul, qui refuse de mentir
Il y en avait deux, et le mauvais portait le nom le plus convaincant :
`publier-release.sh` compilait, archivait et réécrivait le manifeste, à
charge pour l'appelant de committer. Lancé seul, il a produit les deux
moitiés de la panne du 2026-08-21 — un manifeste corrigé mais non poussé,
et une release v0.4.0 contenant six commits de plus que son tag.

`publier.sh` absorbe l'étape et refuse désormais de publier, avant de
compiler, si le tag de la version existe déjà (en nommant le commit du tag
et celui de HEAD) ou si le dépôt du code n'est pas propre. `--archive-seule`
garde la brique réutilisable en CI, sous le même nom : ni manifeste, ni
commit, ni tag, donc rien à moitié publié.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 23:12:41 +02:00
mathieu 81108d7879 Publier la version 0.4.1 2026-08-21 23:06:58 +02:00
mathieuandClaude Opus 5 4a97b21ca3 Aligne l'empreinte du manifeste sur l'archive réellement publiée
L'asset de la release v0.4.0 a été remplacé sur Gitea ; le sha256 du
manifeste désignait encore l'archive précédente, d'où l'échec de
`yunohost app upgrade` (et de son retour arrière, qui retélécharge la
même source).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 23:01:16 +02:00
mathieuandClaude Opus 5 a805721f04 Injecte la version et l'horodatage de build dans l'assembly
La page « À propos » de l'application affiche la version publiée : encore
faut-il que le binaire la connaisse. Le publish reçoit donc, à côté de
`-p:Version` déjà présent, `-p:MaBibliDateBuild`.

⚠️ Cet horodatage est le témoin de l'injection : sans lui, l'application se
déclare « version de développement » plutôt que d'afficher le « 1.0.0 » que le
SDK pose par défaut, lequel se lirait comme une vraie version alors qu'il ne
désigne rien.

`SOURCE_DATE_EPOCH` est respecté, comme pour l'archive : sinon deux
compilations du même commit donneraient deux sha256 différents pour la seule
raison que l'heure a changé, et la reproductibilité visée par les options de
`tar` ne vaudrait plus rien.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 14:50:34 +02:00
mathieu 2daacd847f Publier la version 0.4.0 2026-08-21 11:48:48 +02:00
mathieu c00abb4006 Publier la version 0.3.3 2026-08-20 22:22:28 +02:00
mathieu 2858bd0755 Publier la version 0.3.2 2026-08-20 21:31:15 +02:00
mathieu 2d98f5c987 Publier la version 0.3.1 2026-08-20 19:51:27 +02:00
mathieuandClaude Opus 5 712741a883 Renvoie la documentation vers le README du dépôt du code
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 <noreply@anthropic.com>
2026-08-20 19:49:38 +02:00
mathieu 0c2e94431b Publier la version 0.3.0 2026-08-20 18:18:01 +02:00
LIMONIER MathieuandCopilot 3d4ccd32ed Ajouter le logo de l'application (256x256, fond transparent)
Meme pictogramme de livre ouvert que MaBibli.Client (favicon, icones PWA) : deux pages
en V dans le bleu d'accent de l'app, tranche centrale blanche. Convention du catalogue
d'applications YunoHost : logo.png a la racine du depot.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-08-20 13:03:21 +02:00
mathieuandClaude Opus 5 3fcb447bb8 Rappeler l'option -u à la mise à jour, l'app n'étant pas au catalogue
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 22:52:33 +02:00
mathieu 6f58527206 Publier la version 0.2.0 2026-08-19 22:48:50 +02:00
mathieuandClaude Opus 5 54a22b1579 Écrire la marche à suivre : mise en production et montée de version
PUBLICATION.md donne les gestes pas à pas, avec le contrôle attendu à
chaque étape : première mise en production, montée de version (0.1.0 →
0.1.1), retour arrière si la mise à jour échoue, désinstallation.

Deux points qui se paient cher s'ils sont oubliés y sont explicites :
`--version` est obligatoire pour une nouvelle version applicative (sans
lui le script reproduit celle du manifeste), et la sauvegarde de sécurité
pré-upgrade ne contient PAS le répertoire de données.

README et A_FAIRE renvoient au fichier plutôt que de tripler la
procédure. Dernière URL fictive éliminée : gitea.exemple.org subsistait
dans l'exemple CI de publier-release.sh.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 21:05:56 +02:00
mathieuandClaude Opus 5 7ab423783f Rendre le data_dir visible au service : ProtectHome=tmpfs + BindPaths
Le data_dir de YunoHost vit sous /home/yunohost.app/mabibli, or
ProtectHome=yes rend /home vide dans le namespace du service : le démarrage
mourait sur « SQLite Error 14: unable to open database file ».

ReadWritePaths ne perce pas ProtectHome — vérifié sur une unité de test :
`yes` + ReadWritePaths échoue, `tmpfs` + BindPaths réussit. La forme retenue
garde les répertoires personnels du serveur invisibles au service.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 19:26:48 +02:00
mathieuandClaude Opus 5 4e9bd8be71 Guillemeter la chaîne de connexion dans l'unité systemd
`Environment=` découpe sur les espaces : sans guillemets, la ligne
définissait `ConnectionStrings__MaBibli=Data` et une variable parasite
`Source=…`. L'application recevait la chaîne de connexion `Data` et mourait
au démarrage en ArgumentException, faisant échouer l'installation à l'étape
« Démarrage de mabibli » (constaté sur le serveur, reproduit isolément sur
une unité de test).

Version de paquet à ~ynh2 : l'archive est inchangée, seule la conf bouge.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 19:21:38 +02:00
mathieuandClaude Opus 5 33bab173bc Pointer réellement le manifeste vers la release Gitea
Le commit précédent portait le bon message mais pas les modifications :
manifest.toml, README.md, conf/systemd.service et la valeur par défaut de
--base-url référençaient encore gitea.example.org, ce qui faisait échouer
l'installation au Prefetching asset main.

A_FAIRE.md est réécrit : la visibilité privée du dépôt du code devient le
point bloquant restant (Gitea répond 404, pas 403, à un anonyme).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 19:16:13 +02:00
mathieu ffc3db2dcc Manifeste pointe vers la bonne release 2026-08-18 19:14:14 +02:00
mathieu f942f41f34 premier essaie 2026-08-18 15:15:53 +02:00
5 changed files with 85 additions and 142 deletions
+2 -2
View File
@@ -1,6 +1,6 @@
# Paquet YunoHost pour MaBibli # Paquet YunoHost pour MaBibli
Paquet d'installation de [MaBibli](https://forge.example.org/mabibli), application de Paquet d'installation de [MaBibli](https://git.akbar.nohost.me/mathieu/mabibli), application de
gestion de bibliothèque personnelle (catalogue, prêts, scan ISBN). 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 Ce dépôt **ne contient aucun code C#**. Il porte uniquement de quoi installer sur YunoHost une
@@ -9,7 +9,7 @@ d'installation, et les scripts de publication.
## 📖 Toute la documentation est dans le dépôt du code ## 📖 Toute la documentation est dans le dépôt du code
Elle a été regroupée dans **[le README de `mabibli`](https://forge.example.org/mabibli#readme)**, 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, 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, publication d'une version, mise en production pas à pas, montée de version, retour arrière,
désinstallation, et contraintes permanentes. désinstallation, et contraintes permanentes.
+70 -120
View File
@@ -3,17 +3,29 @@
# Publie une version de MaBibli de bout en bout, en une commande. # Publie une version de MaBibli de bout en bout, en une commande.
# #
# 1. choisir la version, à partir de celle déjà publiée # 1. choisir la version, à partir de celle déjà publiée
# 2. compiler self-contained POUR CHAQUE ARCHITECTURE, vérifier chaque publish, # 2. compiler self-contained, vérifier le publish, produire l'archive
# produire une archive par architecture # 3. réécrire manifest.toml (version, url, sha256)
# 3. réécrire manifest.toml (version, url et sha256 de chaque architecture)
# 4. committer et pousser mabibli_ynh (le serveur lit Gitea, jamais le local) # 4. committer et pousser mabibli_ynh (le serveur lit Gitea, jamais le local)
# 5. taguer et pousser mabibli (le tag porte le code de la release) # 5. taguer et pousser mabibli (le tag porte le code de la release)
# #
# La seule chose qui reste manuelle est le dépôt des archives dans la release Gitea : # La seule chose qui reste manuelle est le dépôt de l'archive dans la release Gitea :
# elles sont rappelées en fin de sortie, une par architecture. # l'URL est rappelée en fin de sortie.
# #
# Le mode `--archive-seule` compile et archive sans toucher ni # ⚠️ POURQUOI UN SEUL SCRIPT, ET PAS DEUX
# au manifeste ni à git #
# Ce fichier était jusqu'au 2026-08-21 un orchestrateur appelant `publier-release.sh`,
# qui compilait et réécrivait le manifeste. Deux scripts, dont le mauvais portait le nom
# le plus convaincant : lancé seul, `publier-release.sh` réécrivait le manifeste sans le
# committer, et acceptait de recompiler une version dont le tag existait déjà.
#
# Les deux moitiés de la panne du 2026-08-21 en sont sorties : une release `v0.4.0`
# contenant en réalité six commits de plus que son tag, et un `manifest.toml` corrigé
# mais jamais poussé — donc un `yunohost app upgrade` qui échoue sur le sha256, et dont
# le retour arrière échoue pour la même raison.
#
# D'où la fusion. Le mode `--archive-seule` conserve la brique réutilisable en CI
# (compiler et archiver, sans toucher ni au manifeste ni à git), mais sous CE nom : il
# n'y a plus deux commandes voisines entre lesquelles se tromper.
# #
set -euo pipefail set -euo pipefail
@@ -25,6 +37,11 @@ racine_paquet="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
manifeste="$racine_paquet/manifest.toml" manifeste="$racine_paquet/manifest.toml"
# ⚠️ Chaque réglage a UNE seule façon de se donner : son option en ligne de commande. # ⚠️ Chaque réglage a UNE seule façon de se donner : son option en ligne de commande.
# Cinq variables d'environnement jumelles (MABIBLI_SOURCE_DIR, MABIBLI_BASE_URL…) ont
# existé jusqu'au 2026-08-22, chacune doublant une option qu'elle ne faisait que
# répéter : l'aide en listait dix pour cinq réglages, et rien ne disait laquelle
# l'emportait sur l'autre. Ne pas les réintroduire pour une CI hypothétique — une CI
# passe des options aussi bien que des variables.
# ⚠️ LA SEULE LIGNE À CHANGER pour héberger ce projet ailleurs. # ⚠️ LA SEULE LIGNE À CHANGER pour héberger ce projet ailleurs.
# Tout ce qui suit en dérive : URL de release, URL d'archive, message de fin. # Tout ce qui suit en dérive : URL de release, URL d'archive, message de fin.
# #
@@ -44,35 +61,17 @@ version=""
increment="" increment=""
sans_confirmation=0 sans_confirmation=0
archive_seule=0 archive_seule=0
runtime="linux-x64"
projet="MaBibli.Api" projet="MaBibli.Api"
# ⚠️ Les DEUX architectures se compilent à chaque publication, et il n'y a pas d'option
# pour n'en faire qu'une. Le manifeste déclare `architectures = ["amd64", "arm64"]` : un
# serveur arm64 ira chercher `arm64.url`. Publier une seule archive laisserait le
# manifeste promettre une URL que personne n'a téléversée — et l'échec surviendrait chez
# celui qui installe, dans `ynh_setup_source`, sans rapport apparent avec la cause.
#
# ⚠️ Le RID désigne le SERVEUR, jamais la machine qui compile : le SDK télécharge le
# runtime pack de la cible, et un hôte arm64 produit un binaire x86-64 sans rien de plus.
runtimes=("linux-x64" "linux-arm64")
# Nom YunoHost de chaque RID .NET — c'est la clé du manifeste (`amd64.url`, `arm64.url`).
arch_ynh() {
case "$1" in
linux-x64) echo "amd64" ;;
linux-arm64) echo "arm64" ;;
*) echoerr "RID sans architecture YunoHost connue : $1"; exit 1 ;;
esac
}
usage() { usage() {
cat <<'FIN' cat <<'FIN'
Usage : publier.sh [options] Usage : publier.sh [options]
--version X.Y.Z Version à publier (sinon : demandée, à partir de la précédente) --version X.Y.Z Version à publier (sinon : demandée, à partir de la précédente)
--patch Incrémenter le dernier chiffre (1.0.0 -> 1.0.1) --patch Incrémenter le dernier chiffre (0.3.0 -> 0.3.1)
--minor Incrémenter le chiffre du milieu (1.0.0 -> 1.1.0) --minor Incrémenter le chiffre du milieu (0.3.0 -> 0.4.0)
--major Incrémenter le premier chiffre (1.0.0 -> 2.0.0) --major Incrémenter le premier chiffre (0.3.0 -> 1.0.0)
--oui, -y Ne rien demander (pour une CI) --oui, -y Ne rien demander (pour une CI)
--archive-seule Compiler et archiver SEULEMENT : ni manifeste, ni commit, ni tag. --archive-seule Compiler et archiver SEULEMENT : ni manifeste, ni commit, ni tag.
Pour REPRENDRE une publication à la main après un échec en cours Pour REPRENDRE une publication à la main après un échec en cours
@@ -191,10 +190,12 @@ suivante() { # suivante <version> <patch|minor|major>
esac esac
} }
# ⚠️ Le calcul de la version est le MÊME dans les deux modes, et c'est délibéré : une # ⚠️ Le calcul de la version est le MÊME dans les deux modes, et c'est délibéré.
# reprise après échec veut la version qu'on était en train de publier, c'est-à-dire ce # `--archive-seule` retombait auparavant sur la version du manifeste, c'est-à-dire sur
# que l'incrément propose. Faire retomber `--archive-seule` sur la version du manifeste # celle **déjà publiée** : reprendre une publication à la main reproduisait donc le
# reproduirait le numéro DÉJÀ publié tant qu'on ne passerait pas `--version`. # numéro précédent tant qu'on ne passait pas `--version`. Le README devait avertir de ce
# piège ; il n'existe plus. Une reprise après échec veut la version qu'on était en train
# de publier, et c'est exactement ce que l'incrément propose.
if [ -z "$precedente" ]; then if [ -z "$precedente" ]; then
echoerr "Aucune version précédente trouvée (ni tag, ni manifest.toml) ; utilisez --version." echoerr "Aucune version précédente trouvée (ni tag, ni manifest.toml) ; utilisez --version."
exit 1 exit 1
@@ -238,7 +239,7 @@ fi
# CE QUI DOIT ÊTRE VRAI AVANT DE COMPILER # CE QUI DOIT ÊTRE VRAI AVANT DE COMPILER
#================================================= #=================================================
# ⚠️ Recompiler une version dont le tag existe # ⚠️ Le garde-fou qui manquait le 2026-08-21. Recompiler une version dont le tag existe
# déjà produit une archive qui ne correspond PAS au code que ce tag désigne : la release # déjà produit une archive qui ne correspond PAS au code que ce tag désigne : la release
# se met alors à mentir, et l'écran « À propos » avec elle, sans que rien ne le signale. # se met alors à mentir, et l'écran « À propos » avec elle, sans que rien ne le signale.
if git -C "$source_dir" rev-parse "v$version" >/dev/null 2>&1; then if git -C "$source_dir" rev-parse "v$version" >/dev/null 2>&1; then
@@ -256,6 +257,11 @@ fi
# Le tag va désigner le HEAD du dépôt du code : un travail non committé ne serait donc # 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. # pas dans la release, alors même que l'archive, elle, le contiendrait.
#
# ⚠️ Ce contrôle était auparavant SAUTÉ en `--archive-seule`, qui produisait donc en
# silence une archive contenant du travail non committé. Il s'exécute désormais toujours,
# et se contente d'avertir quand rien n'est publié : c'est précisément l'écart qu'on veut
# voir quand on reprend une publication à la main.
if [ -n "$(git -C "$source_dir" status --porcelain)" ]; then if [ -n "$(git -C "$source_dir" status --porcelain)" ]; then
git -C "$source_dir" status --short >&2 git -C "$source_dir" status --short >&2
refuser "" \ refuser "" \
@@ -305,20 +311,10 @@ travail="$(mktemp -d)"
# shellcheck disable=SC2064 # shellcheck disable=SC2064
trap "rm -rf '$travail'" EXIT trap "rm -rf '$travail'" EXIT
# Une passe complète par architecture : compilation, contrôles, archive. Les trois publish="$travail/publish"
# blocs sont RIGOUREUSEMENT les mêmes d'une architecture à l'autre — c'est ce qui
# garantit qu'une archive arm64 est vérifiée aussi durement qu'une amd64.
declare -A sommes=()
declare -A urls=()
declare -A archives=()
for runtime in "${runtimes[@]}"; do
arch="$(arch_ynh "$runtime")"
publish="$travail/publish-$runtime"
echo echo
echo "=== Compilation self-contained $runtime ($arch) ===" echo "=== Compilation self-contained $runtime ==="
echo " source : $source_dir" echo " source : $source_dir"
echo " version: $version" echo " version: $version"
echo " build : $date_build" echo " build : $date_build"
@@ -329,12 +325,6 @@ echo " build : $date_build"
# `MaBibli.Api` référence `MaBibli.Client` : le client Blazor WebAssembly est compilé # `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 # au passage et atterrit dans `wwwroot/`. Un seul publish suffit donc pour les trois
# projets. # projets.
#
# ⚠️ Le client compilé est le MÊME quelle que soit l'architecture — c'est du WebAssembly,
# zbar compris. Seul l'hôte .NET change. On le recompile quand même par architecture
# plutôt que de recopier un `wwwroot/` d'une passe à l'autre : le publish est le seul
# geste dont on sait qu'il produit un ensemble cohérent, et les contrôles ci-dessous
# portent sur ce qu'on archive vraiment, pas sur ce qu'on croit y avoir mis.
dotnet publish "$source_dir/$projet" \ dotnet publish "$source_dir/$projet" \
--configuration Release \ --configuration Release \
--runtime "$runtime" \ --runtime "$runtime" \
@@ -348,10 +338,10 @@ dotnet publish "$source_dir/$projet" \
#================================================= #=================================================
echo echo
echo "=== Vérification du publish ($arch) ===" echo "=== Vérification du publish ==="
if [ ! -x "$publish/$projet" ]; then if [ ! -x "$publish/$projet" ]; then
echoerr "Binaire '$projet' absent ou non exécutable dans le publish $runtime." echoerr "Binaire '$projet' absent ou non exécutable dans le publish."
exit 1 exit 1
fi fi
@@ -359,7 +349,7 @@ fi
# C'est le défaut qui est passé inaperçu pendant trois phases du projet, faute de # 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. # regarder ailleurs que la racine. On le vérifie donc ici, à la source.
if [ ! -f "$publish/wwwroot/index.html" ]; then if [ ! -f "$publish/wwwroot/index.html" ]; then
echoerr "wwwroot/index.html absent : le client Blazor n'a pas été embarqué ($runtime)." echoerr "wwwroot/index.html absent : le client Blazor n'a pas été embarqué."
exit 1 exit 1
fi fi
@@ -367,8 +357,8 @@ fi
# substitué (`blazor.webassembly#[.{fingerprint}].js`) donnerait une page blanche # substitué (`blazor.webassembly#[.{fingerprint}].js`) donnerait une page blanche
# alors que `/` répondrait 200. # alors que `/` répondrait 200.
if grep -q '#\[\.{fingerprint}\]' "$publish/wwwroot/index.html"; then if grep -q '#\[\.{fingerprint}\]' "$publish/wwwroot/index.html"; then
echoerr "index.html contient des placeholders d'empreinte non substitués ($runtime)." echoerr "index.html contient des placeholders d'empreinte non substitués."
echoerr "Voir CLAUDE.md, « Les invariants à ne jamais casser »." echoerr "Voir CLAUDE.md, « Empreintes WASM désactivées »."
exit 1 exit 1
fi fi
@@ -376,23 +366,12 @@ for ressource in $(grep -oE '(src|href)="[^"]+"' "$publish/wwwroot/index.html" \
| sed -E 's/^(src|href)="//; s/"$//' \ | sed -E 's/^(src|href)="//; s/"$//' \
| grep -vE '^(https?:|data:|#|/$|\.$)'); do | grep -vE '^(https?:|data:|#|/$|\.$)'); do
if [ ! -f "$publish/wwwroot/$ressource" ]; then if [ ! -f "$publish/wwwroot/$ressource" ]; then
echoerr "index.html référence '$ressource', absent du publish ($runtime)." echoerr "index.html référence '$ressource', absent du publish."
exit 1 exit 1
fi fi
done done
# ⚠️ Le contrôle qui n'existait pas tant qu'il n'y avait qu'une architecture : rien echo " binaire, wwwroot et ressources d'index.html : OK"
# n'empêche un publish de rendre un binaire de l'architecture de la MACHINE plutôt que
# de la cible. Le symptôme serait un service qui refuse de démarrer sur le serveur, avec
# un « Exec format error » qui ne désigne pas la publication.
attendu_elf="$(case "$runtime" in linux-x64) echo "x86-64" ;; linux-arm64) echo "aarch64" ;; esac)"
if ! file -b "$publish/$projet" | grep -q "$attendu_elf"; then
echoerr "Le binaire produit n'est pas du $attendu_elf :"
echoerr " $(file -b "$publish/$projet")"
exit 1
fi
echo " binaire ($attendu_elf), wwwroot et ressources d'index.html : OK"
#================================================= #=================================================
# ARCHIVE # ARCHIVE
@@ -403,7 +382,7 @@ mkdir -p "$output_dir"
archive="$output_dir/$archive_nom" archive="$output_dir/$archive_nom"
echo echo
echo "=== Archive ($arch) ===" echo "=== Archive ==="
# Fichiers à la racine de l'archive, sans dossier intermédiaire : cohérent avec # Fichiers à la racine de l'archive, sans dossier intermédiaire : cohérent avec
# `in_subdir = false` dans manifest.toml. # `in_subdir = false` dans manifest.toml.
@@ -427,27 +406,16 @@ echo " fichier : $archive"
echo " taille : $taille ($taille_octets octets)" echo " taille : $taille ($taille_octets octets)"
echo " sha256 : $somme" echo " sha256 : $somme"
sommes[$arch]="$somme" url_archive="$base_url/v$version/$archive_nom"
archives[$arch]="$archive"
urls[$arch]="$base_url/v$version/$archive_nom"
# Le publish pèse ~150 Mo : le garder pendant la passe suivante doublerait l'occupation
# du disque pour rien, l'archive étant déjà produite.
rm -rf "$publish"
done
if [ "$archive_seule" -eq 1 ]; then if [ "$archive_seule" -eq 1 ]; then
echo
echo "=== Terminé (--archive-seule) ==="
echo
for runtime in "${runtimes[@]}"; do
arch="$(arch_ynh "$runtime")"
echo " $arch : ${archives[$arch]}"
echo " sha256 ${sommes[$arch]}"
done
cat <<FIN cat <<FIN
=== Terminé (--archive-seule) ===
Archive prête : $archive
sha256 : $somme
Rien n'a été publié : manifest.toml est inchangé, aucun commit, aucun tag. Rien n'a été publié : manifest.toml est inchangé, aucun commit, aucun tag.
Pour publier réellement cette version, relancez sans --archive-seule. Pour publier réellement cette version, relancez sans --archive-seule.
@@ -473,32 +441,18 @@ else
echo " version : $version_actuelle -> $version_paquet" echo " version : $version_actuelle -> $version_paquet"
fi fi
# Les architectures sont passées à plat : arch, url, sha256, arch, url, sha256… python3 - "$manifeste" "$version_paquet" "$url_archive" "$somme" <<'PY'
arguments=()
for runtime in "${runtimes[@]}"; do
arch="$(arch_ynh "$runtime")"
arguments+=("$arch" "${urls[$arch]}" "${sommes[$arch]}")
done
python3 - "$manifeste" "$version_paquet" "${arguments[@]}" <<'PY'
import re, sys import re, sys
chemin, version = sys.argv[1:3] chemin, version, url, somme = sys.argv[1:5]
reste = sys.argv[3:]
with open(chemin, encoding="utf-8") as f: with open(chemin, encoding="utf-8") as f:
texte = f.read() texte = f.read()
remplacements = [(r'^version = ".*"$', f'version = "{version}"')] remplacements = [
(r'^version = ".*"$', f'version = "{version}"'),
# ⚠️ Chaque motif DOIT correspondre exactement une fois. Une architecture déclarée au (r'^ amd64\.url = ".*"$', f' amd64.url = "{url}"'),
# manifeste mais absente d'ici laisserait une URL périmée pointer sur la version (r'^ amd64\.sha256 = ".*"$', f' amd64.sha256 = "{somme}"'),
# précédente : le serveur téléchargerait une archive réelle, dont le sha256 ne ]
# correspondrait pas — ou pire, correspondrait, et installerait l'ancienne version.
for arch, url, somme in zip(reste[0::3], reste[1::3], reste[2::3]):
remplacements += [
(rf'^ {arch}\.url = ".*"$', f' {arch}.url = "{url}"'),
(rf'^ {arch}\.sha256 = ".*"$', f' {arch}.sha256 = "{somme}"'),
]
for motif, valeur in remplacements: for motif, valeur in remplacements:
texte, n = re.subn(motif, valeur, texte, count=1, flags=re.MULTILINE) texte, n = re.subn(motif, valeur, texte, count=1, flags=re.MULTILINE)
@@ -509,11 +463,8 @@ with open(chemin, "w", encoding="utf-8") as f:
f.write(texte) f.write(texte)
PY PY
for runtime in "${runtimes[@]}"; do echo " url : $url_archive"
arch="$(arch_ynh "$runtime")" echo " sha256 : $somme"
echo " $arch : ${urls[$arch]}"
echo " sha256 ${sommes[$arch]}"
done
#================================================= #=================================================
# LE PAQUET (le serveur lit Gitea, jamais la copie locale) # LE PAQUET (le serveur lit Gitea, jamais la copie locale)
@@ -536,7 +487,7 @@ git -C "$racine_paquet" push
echo echo
echo "=== Code : tag v$version et push ===" echo "=== Code : tag v$version et push ==="
git -C "$source_dir" tag "v$version" git -C "$source_dir" tag "v$version"
git -C "$source_dir" push origin "$branche" "refs/tags/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. # Un push qui échoue à demi ne se voit pas dans la sortie de git : on redemande au distant.
verifier_pousse() { # verifier_pousse <dépôt> <ref> verifier_pousse() { # verifier_pousse <dépôt> <ref>
@@ -562,16 +513,15 @@ cat <<FIN
=== Publié : $version === === Publié : $version ===
Il ne reste qu'une chose, dans Gitea : créer la release v$version et y téléverser Il ne reste qu'une chose, dans Gitea : créer la release v$version et y téléverser
LES DEUX archives. Une seule des deux téléversée, et le manifeste promet à l'autre l'archive.
architecture une URL qui répondra 404 à l'installation.
Releases : $url_releases Releases : $url_releases
$(for r in "${runtimes[@]}"; do a="$(arch_ynh "$r")"; echo " $a : ${archives[$a]}"; done) Archive : $archive
Ensuite, vérifier que le manifeste pointe sur des fichiers réellement téléchargeables Ensuite, vérifier que le manifeste pointe sur un fichier réellement téléchargeable
(sans être authentifié) : (sans être authentifié) :
$(for r in "${runtimes[@]}"; do a="$(arch_ynh "$r")"; echo " curl -fsSLI \"${urls[$a]}\" | head -1"; done) curl -fsSLI "$url_archive" | head -1
Puis, sur le serveur : Puis, sur le serveur :
+1 -1
View File
@@ -1,6 +1,6 @@
[Unit] [Unit]
Description=MaBibli — gestion de bibliothèque personnelle Description=MaBibli — gestion de bibliothèque personnelle
Documentation=https://forge.example.org/mabibli Documentation=https://git.akbar.nohost.me/mathieu/mabibli
After=network.target After=network.target
[Service] [Service]
+1 -1
View File
@@ -65,4 +65,4 @@ sqlite3 /home/yunohost.app/mabibli/mabibli.db ".backup '/quelque/part/mabibli.db
--- ---
Le détail — conception du paquet, publication, mise à jour pas à pas, retour arrière — Le détail — conception du paquet, publication, mise à jour pas à pas, retour arrière —
est dans le [README du dépôt du code](https://forge.example.org/mabibli#readme). est dans le [README du dépôt du code](https://git.akbar.nohost.me/mathieu/mabibli#readme).
+11 -18
View File
@@ -7,7 +7,7 @@ name = "MaBibli"
description.fr = "Gestion de bibliothèque personnelle : catalogue, prêts et scan ISBN" description.fr = "Gestion de bibliothèque personnelle : catalogue, prêts et scan ISBN"
description.en = "Personal library manager: catalogue, loans and ISBN scanning" description.en = "Personal library manager: catalogue, loans and ISBN scanning"
version = "1.2.0~ynh1" version = "1.1.0~ynh1"
maintainers = ["mathieu"] maintainers = ["mathieu"]
@@ -19,13 +19,10 @@ code = "https://git.akbar.nohost.me/mathieu/mabibli"
yunohost = ">= 12.1.38" yunohost = ">= 12.1.38"
helpers_version = "2.1" helpers_version = "2.1"
# ⚠️ Une architecture déclarée ici DOIT avoir son archive dans la release : YunoHost # x86_64 uniquement : CLAUDE.md acte un serveur PC/VPS, et la chaîne de publication
# choisit `<arch>.url` d'après la machine, et une URL manquante échoue chez celui qui # ne produit qu'un binaire `linux-x64`. Déclarer d'autres architectures promettrait
# installe, dans `ynh_setup_source`. `build/publier.sh` compile les deux à chaque # des archives qui n'existent pas.
# publication, précisément pour qu'aucune des deux ne puisse être oubliée. architectures = ["amd64"]
#
# armhf (32 bits) n'est PAS déclarée : .NET ne la prend plus en charge.
architectures = ["amd64", "arm64"]
# Une seule instance : la bibliothèque est commune au foyer (CLAUDE.md, « Portée des # Une seule instance : la bibliothèque est commune au foyer (CLAUDE.md, « Portée des
# données »). Deux instances voudraient dire deux collections étanches, ce qui est # données »). Deux instances voudraient dire deux collections étanches, ce qui est
@@ -102,18 +99,14 @@ ram.runtime = "256M"
[resources] [resources]
[resources.sources.main] [resources.sources.main]
# Archives **déjà compilées** (publish self-contained), produites par # Archive **déjà compilée** (publish self-contained linux-x64), produite par
# `build/publier.sh` et déposées en release sur le Gitea de l'utilisateur. # `build/publier.sh` et déposée en release sur le Gitea de l'utilisateur.
# Le SDK .NET n'est jamais installé sur le serveur : c'est tout l'intérêt du # Le SDK .NET n'est jamais installé sur le serveur : c'est tout l'intérêt du
# self-contained (CLAUDE.md, « Décisions techniques actées »). # self-contained (CLAUDE.md, « Ne jamais compiler sur le serveur »).
# #
# YunoHost retient la ligne correspondant à l'architecture de la machine. # Ces deux valeurs sont réécrites par `build/publier.sh`.
# amd64.url = "https://git.akbar.nohost.me/mathieu/mabibli/releases/download/v1.1.0/mabibli-1.1.0-linux-x64.tar.gz"
# ⚠️ Ces quatre valeurs sont réécrites par `build/publier.sh`, jamais à la main. amd64.sha256 = "cb151491b408176ff67ed5dccda47737aaa13227ea6d84dac4340f9a60e5b83a"
amd64.url = "https://git.akbar.nohost.me/mathieu/mabibli/releases/download/v1.2.0/mabibli-1.2.0-linux-x64.tar.gz"
amd64.sha256 = "daac8651a51b252aa1d8e569b195a5b62fc5de4149589d52e21bf0ad7b699acd"
arm64.url = "https://git.akbar.nohost.me/mathieu/mabibli/releases/download/v1.2.0/mabibli-1.2.0-linux-arm64.tar.gz"
arm64.sha256 = "d3c3e31338745d8373664a50c1bb72f849d04f05c824a6c31801f5ecf8365db6"
# L'archive dépose ses fichiers à la racine, sans dossier intermédiaire. # L'archive dépose ses fichiers à la racine, sans dossier intermédiaire.
in_subdir = false in_subdir = false