Trois items ouverts d'IDEES.md instruits sur pieces, et deux conclusions renversees en chemin — c'est ce que ces notes servent a ne pas refaire. Le paquet n'avait presque rien a simplifier : neuf fichiers, 209 lignes de code, de 9 a 49 lignes chacun. Ce qui donnait l'impression de volume, ce sont les 306 lignes de commentaires, qui documentent chacune une panne reellement constatee. Tout le poids etait dans publier.sh, 1,4 fois le paquet reuni. Le menu interactif de choix de version paraissait le meilleur candidat a la coupe, jusqu'a ce que les neuf montees de version montrent six correctifs ET trois sauts mineurs. Il sert une fois sur trois : mesurer avant de tailler vaut aussi pour l'outillage. L'icone du paquet : logo.png n'est lu par personne. Les logos du catalogue vivent dans le depot YunoHost/apps, ou MaBibli n'est pas ; la tuile du portail accepte un logo televerse depuis la 12.1, et le manifeste exige deja 12.1.38. Il n'y a donc rien a corriger dans le depot. La RAM est mesuree et ram.runtime porte a 256M. Le pic n'est pas le lookup ISBN, contrairement a l'intuition, mais « Nouveautes » sur un auteur tres reedite — cas qui n'a pas encore ete mesure et qui croit avec le fonds. Le README cesse de decrire --archive-seule comme une brique de CI et retire le « --version est obligatoire ici », piege qui n'existe plus. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
656 lines
30 KiB
Markdown
656 lines
30 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 |
|
|
|
|
## 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://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` | **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://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 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://git.akbar.nohost.me/mathieu/mabibli_ynh/raw/branch/main/manifest.toml" | grep amd64
|
|
```
|
|
|
|
#### 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:<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://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 <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.
|