diff --git a/CLAUDE.md b/CLAUDE.md index 16131ed..e3869b0 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -838,6 +838,96 @@ extérieur ou par Échap. Le calque est focalisé à l'ouverture (`FocusAsync`) **Le substitut à initiale n'est jamais cliquable** : le bouton déclencheur n'est rendu que lorsqu'une URL de couverture existe. Sans couverture, il n'y a rien à agrandir. +## Installation YunoHost — éprouvée en production le 2026-08-18 + +Le paquet vit dans le dépôt `mabibli_ynh` (voir « Deux dépôts distincts »). Installé, +mis à jour et sauvegardé sur un vrai serveur ; ce qui suit est ce que l'exercice a +appris, pas ce qu'on en attendait. + +### La chaîne, telle qu'elle tourne + +``` +publier-release.sh → archive tar.gz (69 Mo, self-contained) → release Gitea + → manifest.toml (amd64.url + amd64.sha256) + → yunohost app install +``` + +Le script est la seule source des trois valeurs qui doivent rester cohérentes : +version, URL, sha256. ⚠️ Il ne réécrit **que** `manifest.toml` — les autres mentions +de l'URL (`README.md`, `Documentation=` de l'unité systemd) sont à traiter à la main. + +⚠️ **Le dépôt du code doit être public.** `ynh_setup_source` télécharge sans jeton. +Gitea répond **404 et non 403** à un anonyme sur un dépôt privé : le symptôme est +rigoureusement identique à « la release n'existe pas », ce qui envoie chercher au +mauvais endroit. Contrôle qui tranche, hors session authentifiée : +`curl -fsSLI ""`. + +⚠️ **YunoHost lit le manifeste depuis Gitea**, jamais la copie locale. Une correction +non poussée est une correction qui n'existe pas — constaté deux fois. + +### Deux pièges systemd, tous deux invisibles hors d'un vrai serveur + +Ni l'un ni l'autre ne peut sortir d'un `dotnet run` ou d'un lancement du publish à la +main : ils tiennent au gestionnaire de services, pas à l'application. C'est ce qui +justifie de tester l'installation réelle plutôt que le seul binaire. + +| Piège | Symptôme | Correction | +|---|---|---| +| `Environment=` **découpe sur les espaces** | `ArgumentException: Format of the initialization string … at index 0` | guillemeter **toute** la ligne : `Environment="ConnectionStrings__MaBibli=Data Source=…"` | +| `ProtectHome=yes` masque `/home` | `SQLite Error 14: unable to open database file` | `ProtectHome=tmpfs` + `BindPaths=__DATA_DIR__` | + +Le premier ne définissait pas une variable mais **deux** : `…__MaBibli=Data` et un +`Source=…` parasite. Le second est contre-intuitif parce que le `data_dir` de +YunoHost vit sous `/home/yunohost.app/` : `ReadWritePaths=` **ne perce pas** +`ProtectHome` — mesuré sur une unité de test, `yes` + `ReadWritePaths` échoue, +`tmpfs` + `BindPaths` réussit. `tmpfs` garde l'essentiel du bénéfice : les répertoires +personnels du serveur restent invisibles au service. + +### `install_dir` n'appartient pas à root, contrairement au manifeste + +Le manifeste déclare `owner = "root:rwx"` pour que le service ne puisse pas réécrire +ses propres binaires. En pratique le helper `_ynh_apply_default_permissions` repasse +derrière avec `chown -R :`. Observé : `/var/www/mabibli` appartient à +`mabibli`. + +**L'intention tient quand même**, mais par un seul mécanisme au lieu de deux : +`ProtectSystem=strict` met tout le système en lecture seule dans le namespace du +service, `ReadWritePaths=` ne listant que le `data_dir`. ⚠️ Ne pas retirer +`ProtectSystem=strict` en croyant que la propriété des fichiers protège encore. + +### Ce qui a été vérifié en exécution + +- **Installation** : archive téléchargée, sha256 contrôlé, migrations EF Core + appliquées au premier démarrage (`Application started` en 5 s, timeout de 60 s + largement suffisant), base créée dans `data_dir`. +- **Écoute sur `127.0.0.1` uniquement** — la contrainte dure de « Intégration SSO » + est tenue en production, vérifiée par `ss -tlnp`. +- **Mode WAL confirmé sur le serveur** (`PRAGMA journal_mode` → `wal`) : la + justification du `.backup` de `scripts/backup` n'est pas théorique. Les fichiers + `-wal`/`-shm` n'existent pas au repos (SQLite fait un checkpoint à la fermeture de + la dernière connexion) — leur absence dans un listing ne veut pas dire que le mode + a changé. +- **Sauvegarde** : l'archive contient bien `mabibli-instantane.db` **à côté** de la + base vivante et de ses `-wal`/`-shm`. C'est la restauration qui écarte ces derniers. +- **Mise à jour** : la sauvegarde de sécurité pré-upgrade **saute `data_dir`** + (`BACKUP_CORE_ONLY`), comme prévu, et la bibliothèque survit au remplacement + intégral d'`install_dir`. +- **Désinstallation** : `remove` et `remove --purge` fonctionnent tous deux. Sans + `--purge`, `data_dir` survit — donc **réinstaller après un `remove` retrouve + l'ancienne base**. Ce n'est pas une installation vierge, même si tout le reste est + 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. + +### Ce que l'installation impose et qui ne se contourne pas + +**Un domaine entier** (`mabibli.mondomaine.tld`), pas un sous-chemin : le +`` et les empreintes de `service-worker-assets.js` sont figés à la +compilation, et rien n'est recompilé sur le serveur. Le manifeste déclare donc +l'application en `full_domain`. + ## Historique du projet (pourquoi ces choix) L'utilisateur a testé deux solutions existantes avant de se lancer dans un projet custom : @@ -857,7 +947,7 @@ Conclusion : aucune des deux solutions existantes ne coche toutes les cases (pr 6. Gestion des prêts 7. Intégration scan caméra (**ZXing.Net** + interop caméra minimal) 8. ~~Cache hors-ligne pour la consultation~~ — fait le 2026-08-18 (voir « Stratégie hors-ligne ») -9. Packaging YunoHost (`manifest.toml`, `conf/systemd.service`, `conf/nginx.conf`, `scripts/install`) en s'inspirant de radarr_ynh +9. ~~Packaging YunoHost~~ — fait le 2026-08-18, installé et mis à jour sur un serveur réel (voir « Installation YunoHost ») ## Questions ouvertes