Sortie du projet. Le README ouvre desormais sur une section « A faire avant de publier ce projet quelque part » : ce que commande la ligne unique de publier.sh, ce qui reste a changer a la main, et ce qu'il ne faut surtout PAS changer a la main (amd64.url, reecrite a chaque publication). ⚠ Le remplacement de fichiers ne regle pas l'historique git, qui porte ces URL dans chaque ancien commit. Seul un depot neuf sans historique en vient a bout. Restent volontairement : maintainers, l'adresse de contact et le site de l'auteur dans /a-propos — l'AGPL attend qu'on puisse joindre la source, donc l'auteur. 626 tests au vert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
692 lines
32 KiB
Markdown
692 lines
32 KiB
Markdown
# 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](#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 |
|
|
|
|
## ⚠️ À faire avant de publier ce projet quelque part
|
|
|
|
Les URL de dépôt sont un **placeholder volontaire**, `https://forge.example.org/mabibli`
|
|
(le domaine `example.org` est réservé à la documentation par la RFC 2606, donc
|
|
définitivement injoignable). Rien ne fonctionnera tant qu'il n'est pas remplacé.
|
|
|
|
**Une seule ligne commande tout le reste** — `depot_code`, en tête de
|
|
`mabibli_ynh/build/publier.sh`. Elle produit les URL de release, celles d'archive, et le
|
|
message de fin. Pour un essai ponctuel, `--depot-code https://…` fait la même chose sans
|
|
modifier le fichier.
|
|
|
|
⚠️ **`publier.sh` refuse de publier tant que le placeholder est en place**, et il refuse
|
|
*avant* de compiler. C'est délibéré : publier avec une URL morte écrirait dans
|
|
`manifest.toml` une `amd64.url` que personne ne peut télécharger, et l'échec surviendrait
|
|
bien plus tard, à l'installation, sous la forme d'un `ynh_setup_source` en erreur — sans
|
|
rapport apparent avec la cause.
|
|
|
|
Restent à changer à la main, une fois seulement :
|
|
|
|
| Fichier | Quoi |
|
|
|---|---|
|
|
| `mabibli_ynh/manifest.toml` | `code = "…"`, et `maintainers` |
|
|
| `mabibli_ynh/conf/systemd.service` | `Documentation=…` |
|
|
| `mabibli_ynh/doc/ADMIN.md` | le lien de bas de page |
|
|
| `mabibli_ynh/README.md`, ce fichier | les liens et les commandes d'exemple |
|
|
| `MaBibli.Client/Pages/APropos.razor` | `Depot`, `Site`, et l'adresse de contact |
|
|
|
|
⚠️ **`amd64.url` dans le manifeste n'est PAS à changer à la main** : `publier.sh` la
|
|
réécrit à chaque publication. La corriger donnerait l'illusion que c'est fait alors que la
|
|
source du problème est ailleurs.
|
|
|
|
⚠️ **Le dépôt du code doit être PUBLIC.** `ynh_setup_source` télécharge sans jeton, et
|
|
Gitea comme GitHub répondent **404** — pas 403 — à un anonyme sur un dépôt privé. Le
|
|
symptôme est alors rigoureusement identique à « la release n'existe pas ». Contrôle qui
|
|
tranche, hors session authentifiée : `curl -fsSLI "<url de l'archive>"`.
|
|
|
|
## 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](https://github.com/YunoHost-Apps/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
|
|
|
|
```bash
|
|
dotnet build
|
|
dotnet test
|
|
```
|
|
|
|
Pour lancer l'application en local (l'API sert aussi le client Blazor compilé) :
|
|
|
|
```bash
|
|
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 :
|
|
|
|
```bash
|
|
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.
|
|
|
|
Pour un hébergement **sans YunoHost** (Synology, VPS), voir « Installer ailleurs que sur
|
|
YunoHost » plus bas : le `Dockerfile` de la racine fait ce même publish, mais sans
|
|
`--self-contained` — dans un conteneur, l'image `aspnet` fournit déjà le runtime.
|
|
|
|
⚠️ **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`](https://forge.example.org/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` | **Seul script de publication** : version, archive, manifeste, commit et push du paquet, tag et push du code. `--archive-seule` compile et archive sans rien publier, pour reprendre après un échec |
|
|
| `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 :
|
|
|
|
```bash
|
|
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.
|
|
|
|
⚠️ **Il n'y a qu'un seul script, et c'est délibéré.** Un second, `publier-release.sh`,
|
|
a existé jusqu'au 2026-08-21 : il compilait, archivait et réécrivait le manifeste, à
|
|
charge pour l'appelant de committer. Son nom inspirait plus confiance que celui du vrai
|
|
point d'entrée, et lancé seul il produisait exactement la panne du 2026-08-21 — un
|
|
`manifest.toml` corrigé mais non poussé, et une release `v0.4.0` contenant en réalité six
|
|
commits de plus que son tag. Les deux scripts sont fondus ; ce que le second savait
|
|
faire subsiste sous la même commande :
|
|
|
|
```bash
|
|
./build/publier.sh --archive-seule
|
|
```
|
|
|
|
Ce mode compile et archive, **sans toucher ni au manifeste ni à git** : il ne peut donc
|
|
rien laisser à moitié publié.
|
|
|
|
⚠️ **À quoi il sert vraiment** : à **reprendre une publication à la main** quand
|
|
`publier.sh` a échoué en cours de route (voir B.3). Ce n'est pas « une brique pour une
|
|
CI » — c'était la justification écrite jusqu'au 2026-08-22, et elle désignait un usage
|
|
qui n'a jamais eu lieu, là où la reprise après échec, elle, est documentée pas à pas.
|
|
|
|
⚠️ **Les contrôles ne sont PAS désactivés dans ce mode**, contrairement à ce qui a été
|
|
vrai jusqu'au 2026-08-22 : ils s'exécutent tous, et **avertissent** au lieu de refuser,
|
|
puisque rien n'est publié et que rien ne peut donc mentir. Le dépôt sale, en
|
|
particulier, n'était alors pas signalé du tout — l'archive contenait en silence du
|
|
travail non committé.
|
|
|
|
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 en
|
|
`--archive-seule`.
|
|
|
|
Il injecte au passage, dans l'assembly, la **version** (`-p:Version`) et un
|
|
**horodatage de build** (`-p:MaBibliDateBuild`), que la page « À propos » de
|
|
l'application affiche via `GET /api/version`.
|
|
|
|
⚠️ **L'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 .NET pose par
|
|
défaut. Ce numéro-là se lirait comme une vraie version alors qu'il ne désigne rien — or
|
|
c'est précisément la valeur qu'on va chercher pour diagnostiquer un appareil dont le
|
|
cache est dépareillé. Un `dotnet build` local n'annonce donc aucune version, et c'est
|
|
voulu.
|
|
|
|
⚠️ 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.
|
|
|
|
Il refuse par ailleurs de publier dans deux cas, **avant de compiler** :
|
|
|
|
| Refus | Ce qu'il évite |
|
|
|---|---|
|
|
| le tag `vX.Y.Z` existe déjà | une release qui ne contient pas le code que son tag désigne — arrivé le 2026-08-21, six commits d'écart, sans que rien ne le signale |
|
|
| le dépôt du code a des modifications non committées | une archive contenant du travail que le tag, lui, ne contient pas |
|
|
|
|
Dans les deux cas il nomme le commit en cause et rappelle `--patch`. ⚠️ En
|
|
`--archive-seule`, les mêmes contrôles **avertissent** au lieu de refuser — le constat
|
|
s'affiche, le conseil « choisissez un numéro libre » non : en reprise, le tag visé
|
|
est justement celui qu'on veut retrouver.
|
|
|
|
**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, le manifeste et le tag
|
|
|
|
Depuis `mabibli_ynh`, avec le dépôt du code à côté (`../mabibli`) :
|
|
|
|
```bash
|
|
./build/publier.sh --version 0.1.0
|
|
```
|
|
|
|
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`, réécrit `version`, `amd64.url` et `amd64.sha256` dans
|
|
`manifest.toml`, **commite et pousse le paquet**, puis **tague et pousse le code**.
|
|
|
|
À contrôler dans sa sortie : la ligne `binaire, wwwroot et ressources d'index.html : OK`,
|
|
le `sha256` affiché — c'est celui que le manifeste porte désormais — et les deux `✓` de
|
|
la vérification côté distant.
|
|
|
|
Il ne reste alors que A.3. Les étapes A.4 et suivantes décrivent ce que le script vient
|
|
de faire, pour le cas où l'on reprend à la main après un échec en cours de route
|
|
(`--archive-seule` s'arrête après l'archive, sans toucher au manifeste ni à git).
|
|
|
|
#### A.3 Déposer la release
|
|
|
|
Le tag `v0.1.0` est déjà poussé par A.2 (`git tag v0.1.0 && git push origin main --tags`
|
|
si l'on reprend à la main).
|
|
|
|
**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://forge.example.org/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 Le paquet, sur Gitea
|
|
|
|
⚠️ **L'étape la plus facile à oublier quand elle est manuelle** — et celle qui a fait
|
|
échouer la mise à jour du 2026-08-21. `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. A.2 s'en charge et vérifie l'arrivée par `git ls-remote` ; en
|
|
reprise à la main :
|
|
|
|
```bash
|
|
cd ../mabibli_ynh && git add -A && git commit -m "Publier la version 0.1.0" && git push
|
|
```
|
|
|
|
Le contrôle qui tranche, quel que soit le chemin suivi — c'est le manifeste **tel que le
|
|
serveur le lira** :
|
|
|
|
```bash
|
|
curl -fsSL "https://forge.example.org/mabibli_ynh/raw/branch/main/manifest.toml" | grep amd64
|
|
```
|
|
|
|
#### A.5 Installer
|
|
|
|
```bash
|
|
sudo yunohost app install https://forge.example.org/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:<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.
|
|
|
|
```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.sh --archive-seule --patch
|
|
```
|
|
|
|
⚠️ **`--version` n'est plus obligatoire ici, depuis le 2026-08-22.** Le mode reprenait
|
|
alors la version de `manifest.toml` — c'est-à-dire celle **déjà publiée** — et
|
|
reproduisait donc 0.1.0 tant qu'on ne le corrigeait pas à la main. Les deux modes
|
|
calculent maintenant la version de la même façon : `--patch` propose 0.1.1, et
|
|
`--version` ne sert plus qu'à viser un autre numéro (par exemple celui d'un tag déjà
|
|
créé, si la publication a échoué après le tag).
|
|
|
|
⚠️ **`--archive-seule` ne touche pas au manifeste** : `version` (avec son suffixe
|
|
`~ynh1` — une nouvelle version applicative repart toujours de 1), `amd64.url` et
|
|
`amd64.sha256` sont alors à réécrire à la main. C'est le prix de la marche à pied ;
|
|
`publier.sh` sans option le fait, et ne peut pas l'oublier.
|
|
|
|
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://forge.example.org/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://forge.example.org/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 <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
|
|
|
|
```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.
|
|
|
|
---
|
|
|
|
## Installer ailleurs que sur YunoHost (Synology, NAS, VPS)
|
|
|
|
Le déploiement de référence reste le paquet `mabibli_ynh` : c'est lui qui est éprouvé, et
|
|
c'est le seul qui apporte une **authentification**. Ce qui suit sert aux hébergements qui
|
|
n'ont pas de portail SSO. `Dockerfile`, `.dockerignore` et `compose.yaml`, à la racine de
|
|
ce dépôt, existent pour ça.
|
|
|
|
L'application n'est qu'**un processus et un fichier SQLite** : `MaBibli.Api` sert lui-même
|
|
le client Blazor. Il n'y a donc rien à orchestrer.
|
|
|
|
### ⚠️ Hors YunoHost, il n'y a AUCUNE authentification
|
|
|
|
C'est le point à comprendre avant tout le reste, et il ne se contourne pas par la
|
|
configuration. `FournisseurUtilisateurSsowat` lit l'en-tête `YNH_USER` que le portail
|
|
injecte ; sans portail, il n'y a pas d'identité, et l'application **ne s'en invente pas**.
|
|
|
|
Conséquence exacte, vérifiée en exécution :
|
|
|
|
| | Sans identité |
|
|
|---|---|
|
|
| Catalogue, auteurs, séries, revues, prêts (**communs** au foyer) | fonctionnent |
|
|
| Statuts de lecture, listes d'envies (**personnels**) | refusés — `400 « Impossible d'ajouter une envie sans savoir à qui elle appartient. »` |
|
|
|
|
Deux façons de rendre une identité, **aucune des deux n'authentifie qui que ce soit** :
|
|
|
|
- `Identite__UtilisateurSimule` en variable d'environnement. C'est le mécanisme prévu pour
|
|
le développement, mais c'est de la configuration ordinaire : elle vaut aussi en
|
|
production. Mono-utilisateur.
|
|
- l'en-tête `YNH_USER` injecté par le proxy inversé (DSM : *Portail des applications >
|
|
Proxy inversé > En-tête personnalisé*). Même niveau de sécurité, mais si un proxy
|
|
authentifiant est ajouté un jour (Authelia, authentik), il n'y a plus qu'à lui faire
|
|
recopier son `Remote-User` vers `YNH_USER` : l'application n'a rien à changer.
|
|
|
|
⚠️ Dans les deux cas, **quiconque atteint le port EST cet utilisateur**. Le port ne doit
|
|
donc être publié que sur la boucle locale (`127.0.0.1:8080:8080`), le proxy inversé restant
|
|
le seul chemin d'accès. C'est la transposition exacte de la contrainte d'écoute que le
|
|
paquet YunoHost pose comme dure.
|
|
|
|
### Trois prérequis qui ne se négocient pas
|
|
|
|
- **Une machine x86_64.** Le publish visé est `linux-x64` : les NAS ARM (les modèles « j »
|
|
notamment) demanderaient de republier en `linux-arm64`, ce qui n'est pas éprouvé ici.
|
|
- **HTTPS**, sans quoi le scan du code-barres ne s'ouvrira **jamais** : la caméra exige un
|
|
contexte sécurisé. Sur Synology, cela veut dire proxy inversé + certificat Let's Encrypt
|
|
sur un nom DDNS (`mabibli.xxx.synology.me`).
|
|
- **Un nom d'hôte entier, pas un sous-chemin** — `mabibli.xxx.synology.me`, jamais
|
|
`nas.xxx.synology.me/mabibli`. La raison est la même que pour le `full_domain` de
|
|
YunoHost, et elle est développée plus bas : `<base href="/">` et les empreintes du
|
|
service worker sont figés à la compilation.
|
|
|
|
### La marche à suivre
|
|
|
|
```bash
|
|
git clone <ce dépôt> mabibli && cd mabibli
|
|
# éditer compose.yaml : nom d'utilisateur, chemin du volume
|
|
docker compose up -d --build
|
|
```
|
|
|
|
Sur Synology, le même `compose.yaml` se colle dans *Container Manager > Projet*, en
|
|
remplaçant `./donnees` par un chemin réel (`/volume1/docker/mabibli`).
|
|
|
|
Les migrations EF Core s'appliquent seules au premier démarrage : la base se crée dans le
|
|
volume, et c'est **le volume seul** qui la fait survivre au remplacement de l'image.
|
|
|
|
### Ce qu'il faut savoir avant de s'y mettre
|
|
|
|
- **La sauvegarde ne se fait pas en copiant le `.db`.** SQLite tourne en mode WAL : copier
|
|
le seul fichier d'une base active peut ne rien sauvegarder du tout. Il faut arrêter le
|
|
conteneur, ou passer par `sqlite3 … ".backup"` — c'est ce que fait le paquet YunoHost, et
|
|
la raison est écrite dans `mabibli_ynh/doc/ADMIN.md`.
|
|
- **Il n'y a pas de mécanisme de mise à jour.** Ni `yunohost app upgrade`, ni release, ni
|
|
vérification de `sha256` : à chaque version, `git pull` puis `docker compose up -d
|
|
--build`. Les données ne bougent pas, elles sont dans le volume.
|
|
- **L'image est construite depuis les sources, pas depuis une release.** Elle ne porte donc
|
|
aucun numéro de version, et « À propos » affiche honnêtement « version de développement ».
|
|
Pour un vrai numéro, passer `-p:Version=X.Y.Z -p:MaBibliDateBuild=…` au `dotnet publish`
|
|
du `Dockerfile` — voir `CLAUDE.md`, « Y — l'application dit sa version ».
|
|
|
|
### Vérifié en exécution le 2026-08-21
|
|
|
|
Image construite et démarrée localement, base neuve dans un volume monté :
|
|
|
|
| Cas | Résultat |
|
|
|---|---|
|
|
| Migrations au premier démarrage | appliquées, `mabibli.db` créé dans le volume |
|
|
| `GET /` et `_framework/blazor.webassembly.js` | **200**, `<base href="/">` intact |
|
|
| `GET /api/moi` avec `Identite__UtilisateurSimule` | `{"identifiant":"prenom","simule":true}` |
|
|
| `GET /api/moi` sans rien | `{"identifiant":null,"affichage":"inconnu"}` |
|
|
| `GET /api/moi` avec en-tête `YNH_USER: camille` | `{"identifiant":"camille","simule":false}` |
|
|
| `POST /api/souhaits` sans identité | **400**, message lisible |
|
|
|
|
⚠️ **Ce qui n'a PAS été vérifié** : l'installation sur un Synology réel, le proxy inversé de
|
|
DSM et le certificat. Ce sont des gestes de DSM, pas du code — mais personne ne les a
|
|
éprouvés ici.
|
|
|
|
---
|
|
|
|
## 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.
|