diff --git a/CLAUDE.md b/CLAUDE.md index 354b461..261c684 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1890,8 +1890,16 @@ service, `ReadWritePaths=` ne listant que le `data_dir`. ⚠️ Ne pas retirer neuf : piège à connaître en phase d'essai. **La marche à suivre — première mise en production, montée de version, retour arrière, -désinstallation — est écrite pas à pas dans `mabibli_ynh/PUBLICATION.md`.** Elle n'est -pas recopiée ici : ce sont des gestes, pas des décisions. +désinstallation — est écrite pas à pas dans `README.md`, section « Mettre en +production ».** Elle n'est pas recopiée ici : ce sont des gestes, pas des décisions. + +⚠️ Depuis le 2026-08-20, **toute la documentation du projet vit dans `README.md`**, y +compris ce qui décrit le paquet YunoHost (`PUBLICATION.md` et `A_FAIRE.md` y ont été +fondus, `mabibli_ynh/README.md` n'est plus qu'un renvoi). Ne pas recréer de guide dans +`mabibli_ynh` : deux jeux de documentation pour une seule chaîne de publication +finissaient par diverger, sans qu'on sache lequel faisait autorité. Les seules +exceptions sont `mabibli_ynh/doc/DESCRIPTION.md` et `doc/ADMIN.md`, que **YunoHost lit +lui-même** pour les afficher dans son catalogue et son interface d'administration. ### Ce que l'installation impose et qui ne se contourne pas diff --git a/README.md b/README.md index 3a68ac1..2b676cd 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,19 @@ 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](#demande-initiale) · [Contraintes techniques](#contraintes-techniques) | ce que l'application doit faire, et avec quoi | +| [Compilation](#compilation) | développer et produire l'artefact | +| [Le paquet YunoHost](#le-paquet-yunohost) | contenu du dépôt `mabibli_ynh`, points de conception | +| [Publication](#publication) | sortir une version, en une commande | +| [Mettre en production](#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 : @@ -62,30 +75,416 @@ côté serveur. 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`](https://git.akbar.nohost.me/mathieu/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 -MaBibli se publie et se déploie via un **second dépôt**, `mabibli_ynh` (le paquet -YunoHost), qui contient le script réutilisable `build/publier-release.sh` : +Le geste courant tient en une commande, `build/publier.sh` du dépôt du paquet : ```bash cd ../mabibli_ynh -./build/publier-release.sh +./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 : + +```bash +./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`. +`amd64.url` et `amd64.sha256` dans `mabibli_ynh/manifest.toml` — sauf avec +`--no-manifest-update`. -Reste ensuite, à la main : créer le tag et la release Git correspondants sur ce dépôt, -y téléverser l'archive, puis committer et pousser `manifest.toml` dans `mabibli_ynh` -(YunoHost lit le manifeste **depuis Gitea**, jamais une copie locale). +⚠️ 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** — première mise en production, montée de -version, retour arrière, désinstallation — est dans `mabibli_ynh/PUBLICATION.md`. Ne -pas la reproduire ici : ce sont des gestes propres au paquet YunoHost, pas au code de -ce dépôt. +**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 : + +```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 En une commande : `publier.sh` + +C'est le geste courant, décrit en « [Publication](#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é. + +```bash +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` : + +```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.4 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.5 Pousser le paquet + +```bash +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 : + +```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.7 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.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 : + +```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. + +--- + +## 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 +`` 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