mathieuandClaude Opus 5 4c452200e0 Recueille les retours d'usage du 2026-08-21, et ce qu'ils supposent
Dix demandes remontées en usage, rangées en lots Q à Y dans IDEES.md.
Rien n'est acté : le fichier garde la matière brute et ce que la
relecture du code a déjà établi.

Trois demandes changent de nature une fois le code relu : éditer une
envie suppose un PUT /api/souhaits/{id} qui n'existe pas, souhaiter une
revue ou un numéro n'a aucun modèle, et la version à afficher dans une
page « À propos » ne vit nulle part dans le binaire. À l'inverse,
modifier un numéro de revue ne demande qu'un écran, l'API sachant déjà
le faire depuis le lot O.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 13:27:38 +02:00

MaBibli

Application de gestion de bibliothèque personnelle, self-hosted sur YunoHost.

Ce fichier est le point d'entrée unique du projet : ce qu'il fait, comment le compiler, comment le publier, et comment l'installer sur un serveur. Deux fichiers restent à part, chacun pour une bonne raison — CLAUDE.md, le contexte et l'historique des décisions à destination de Claude Code, et IDEES.md, les pistes non actées.

Demande initiale · Contraintes techniques ce que l'application doit faire, et avec quoi
Compilation développer et produire l'artefact
Le paquet YunoHost contenu du dépôt mabibli_ynh, points de conception
Publication sortir une version, en une commande
Mettre en production installer, mettre à jour, revenir en arrière, désinstaller

Demande initiale

Gérer une bibliothèque personnelle (livres physiques et numériques), avec :

  • Liste des livres physiques
  • Liste des livres ebooks
  • Gestion de prêts — prêter un livre à quelqu'un, marquer comme récupéré, historique des prêts
  • Récupération automatique des infos via ISBN (titre, auteur, éditeur, couverture) — scan caméra + saisie manuelle
  • Consultation hors-ligne de la bibliothèque existante (le lookup ISBN, lui, nécessite le réseau)
  • Statuts de lecture — à lire, en cours, lu, etc.

Contraintes techniques

  • Langage : C# / ASP.NET Core
  • Frontend : Blazor WebAssembly, en PWA (installable, utilisable hors-ligne pour la consultation de la bibliothèque déjà enregistrée)
  • Scan code-barres : zbar (WebAssembly, LGPL-2.1) — voir CLAUDE.md pour l'historique de la bascule depuis ZXing.Net
  • Accès : smartphone (GSM) et PC, via navigateur
  • Multi-utilisateur : géré via le SSO de YunoHost (en-têtes SSOwat, pas d'auth custom) — collection commune au foyer, avec traçabilité de qui a ajouté chaque livre
  • Base de données : SQLite
  • Hébergement : YunoHost, en installation native (sans Docker) — packaging façon _ynh, inspiré de radarr_ynh (déploiement .NET self-contained, pas de dépendance dotnet-runtime côté système)
  • Sources ISBN : cascade BnF (SRU) puis OpenLibrary — bases libres, sans clé API, pas de dépendance à Google Books. La BnF passe en premier pour la couverture du fonds francophone. Voir CLAUDE.md pour le détail
  • Ebooks : fiches d'inventaire uniquement, les fichiers ne sont pas hébergés par l'application

Compilation

Prérequis : le SDK .NET 10 (dotnet --version).

En développement

dotnet build
dotnet test

Pour lancer l'application en local (l'API sert aussi le client Blazor compilé) :

dotnet run --project MaBibli.Api

Publication self-contained (ce que le serveur reçoit réellement)

Le serveur YunoHost ne compile jamais : il télécharge une archive déjà publiée et la déploie telle quelle (voir « Publication » ci-dessous). Pour produire ce même artefact en local, à des fins de vérification :

dotnet publish MaBibli.Api --configuration Release --runtime linux-x64 --self-contained

MaBibli.Api référence MaBibli.Client : le client Blazor WebAssembly est compilé au passage et atterrit dans wwwroot/ du publish. Un seul projet à publier suffit donc pour les trois (MaBibli.Client, MaBibli.Shared, MaBibli.Api). --self-contained embarque le runtime .NET dans le dossier produit : aucun dotnet-runtime n'est requis côté serveur.

⚠️ Ne jamais compiler sur le serveur YunoHost lui-même : ce serait imposer le SDK complet à une machine qui n'en a pas besoin, pour une compilation lente. Voir CLAUDE.md, section « Chaîne de publication ».

Le paquet YunoHost

MaBibli se déploie via un second dépôt, mabibli_ynh, qui ne contient aucun code C# : il porte le manifeste, les configurations nginx et systemd, et les scripts d'installation. Toute la documentation, elle, vit ici — ce fichier est le point d'entrée unique du projet.

Contenu

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.sh Publie une version de bout en bout : version, archive, manifeste, commits, tag, push
build/publier-release.sh Compile, archive, calcule le sha256 et met à jour manifest.toml
doc/DESCRIPTION.md doc/ADMIN.md Textes affichés par YunoHost lui-même (catalogue et interface d'administration) — ils doivent rester dans ce dépôt

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 mabibli_ynh/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 mabibli_ynh/doc/ADMIN.md.

Publication

Le geste courant tient en une commande, build/publier.sh du dépôt du paquet :

cd ../mabibli_ynh
./build/publier.sh

Il reprend la version déjà publiée (tag le plus élevé, recoupé avec manifest.toml), propose l'incrément, puis enchaîne tout ce qui suit : archive, manifeste, commit et push du paquet, tag et push du code, avec vérification côté distant. Il ne reste qu'à créer la release dans Gitea et y téléverser l'archive — le script en rappelle l'URL.

Sous le capot, c'est build/publier-release.sh qui produit l'artefact ; il s'utilise seul quand on veut une archive sans rien publier :

./build/publier-release.sh --version 0.4.0 --no-manifest-update

Ce script compile ce dépôt (mabibli) en Release self-contained, vérifie le résultat (binaire présent, wwwroot/ embarqué, toutes les ressources d'index.html réellement sur disque, aucun placeholder d'empreinte non substitué), produit une archive .tar.gz reproductible, calcule son sha256, et met à jour version, amd64.url et amd64.sha256 dans mabibli_ynh/manifest.toml — sauf avec --no-manifest-update.

⚠️ YunoHost lit le manifeste depuis Gitea, jamais une copie locale : un manifeste corrigé mais non poussé n'existe pas pour le serveur. C'est précisément ce que publier.sh empêche d'oublier.

La marche à suivre complète et éprouvée, pour les cas que publier.sh ne couvre pas — première mise en production, montée de version pas à pas, retour arrière, désinstallation — est en « Mettre en production » ci-dessous.

Mettre en production

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. publier.sh (ci-dessus) enchaîne les étapes courantes ; ce qui suit les détaille une à une, pour les comprendre et pour les cas qu'il ne couvre pas.

machine de dev                    Gitea                        serveur YunoHost
──────────────                    ─────                        ────────────────
publier.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 :
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) :

./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

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) :

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 <url> lit le manifeste depuis Gitea, jamais la copie locale : un manifeste corrigé mais non poussé n'existe pas pour le serveur.

cd ../mabibli_ynh && git add -A && git commit -m "Publier la version 0.1.0" && git push

A.5 Installer

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

sudo ss -tlnp | grep -i mabibli

Doit montrer 127.0.0.1:<port> 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.

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.00.1.1~ynh1 oui
Seulement le paquet (conf systemd/nginx, scripts) 0.1.0~ynh10.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 En une commande : publier.sh

C'est le geste courant, décrit en « Publication » plus haut : il enchaîne B.3, B.4 et B.5 (archive, manifeste, commit et push du paquet, tag et push du code), et vérifie sur le distant que tout est arrivé.

cd ../mabibli_ynh && ./build/publier.sh

La suite décrit ces mêmes étapes à la main, pour comprendre ce que le script fait et pour les cas qu'il ne couvre pas : révision ~ynhN seule, reprise après un échec en cours de route.

B.3 Compiler et vérifier la nouvelle version

Le code est prêt et committé dans mabibli. Depuis mabibli_ynh :

./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.4 Tag et release

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 :

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.5 Pousser le paquet

cd ../mabibli_ynh && git add -A && git commit -m "Publier la version 0.1.1" && git push

B.6 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 :

sudo yunohost backup create --apps mabibli
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.7 Vérifier après mise à jour

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.8 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 :

sudo yunohost backup list
sudo yunohost app remove mabibli --purge
sudo yunohost backup restore <nom-de-l-archive> --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

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 :

sudo yunohost app remove mabibli --purge

Contrôle qu'il ne reste rien :

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.


Contraintes permanentes du paquet — 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 <base href="/"> 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.


Journal du paquet — ce qui a été réglé

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 mabibli_ynh/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, ce fichier, 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.

Statut

Voir CLAUDE.md pour le contexte complet et l'historique des décisions à destination de Claude Code.

S
Description
No description provided
Readme AGPL-3.0
6 MiB
2026-08-23 17:36:06 +02:00
Languages
C# 67.8%
HTML 25.8%
CSS 4%
JavaScript 2.2%
Dockerfile 0.2%