- Cataloguer en rafale — nouvel écran /ajout/rafale : on scanne une pile de livres à la suite dans une zone de texte, chaque code est traité (BnF puis OpenLibrary), les doublons connus sont passés automatiquement. La collecte marche même hors-ligne. Le compte rendu liste maintenant les livres créés, en lien vers leur fiche, et reste consultable en revenant sur l'écran même après une rafale entièrement réussie. Le catalogue reconnaît un ISBN dans sa barre de recherche (13 ou 10 chiffres, avec ou sans tirets) : scanner un livre en main ouvre directement sa fiche s'il n'y en a qu'un. Un bouton « Scanner » l'alimente, actif hors-ligne. - Ajouter un tome à une série accepte aussi un ISBN dans le champ manuel : le catalogue est cherché d'abord (rattachement direct si un seul exemplaire), sinon la BnF prend le relais. Séries et sagas - Numéro de tome distinct de la position de lecture : on peut indiquer « c'est le tome 7 » même si on ne possède pas les six premiers ; l'ordre de lecture reste un réglage séparé (utile pour les préquelles). - Tri par numéro en plus du tri par ordre de lecture quand des tomes en portent un. - Panneau « Ajouter » regroupé et repliable sur la fiche d'une série (manuellement / en rafale / depuis le catalogue / depuis les envies), au lieu de quatre formulaires ouverts en permanence. - Filtre catalogue « sans couverture » pour repérer les livres à illustrer. Le catalogue groupe les tomes d'une même série sous un bloc repliable, avec un décompte plus clair (affichés / possédés / total). - Corrections directes sur la fiche - Effacer un prêt saisi par erreur (bouton ✕ sur chaque ligne, avec confirmation), sans passer par « rendre ». - Corriger une couverture manquante ou cassée en cliquant dessus : le champ d'adresse s'ouvre focalisé, Entrée enregistre. Étendu aux numéros de revue dans la dernière modification. - Les thèmes déjà utilisés dans la bibliothèque sont proposés à la frappe. - La recherche d'un livre à rattacher montre des suggestions dès le focus, sans attendre de taper. - Visuel : un rendu manquant après une écriture asynchrone dans le formulaire de livre, une bascule de rôle cassée, des débordements à 320 px, et le style d'un bouton-lien qui restait souligné.
364 lines
17 KiB
Markdown
364 lines
17 KiB
Markdown
# Installer MaBibli
|
|
|
|
Deux voies. **Seule la première apporte une authentification** — voir l'avertissement de
|
|
la section « Ailleurs que sur YunoHost ».
|
|
|
|
| | |
|
|
|---|---|
|
|
| [Le paquet YunoHost](#le-paquet-yunohost) · [Contraintes](#contraintes-permanentes-du-paquet--ce-ne-sont-pas-des-tâches) | ce qu'il contient, ce qui ne se négocie pas |
|
|
| [Première mise en production](#première-mise-en-production) | pas à pas, sur un serveur neuf |
|
|
| [Désinstaller](#désinstaller) | avec ou sans les données |
|
|
| [Ailleurs que sur YunoHost](#installer-ailleurs-que-sur-yunohost-synology-nas-vps) | Synology, NAS, VPS — sans authentification |
|
|
|
|
Pour **monter de version**, voir [publier-une-version.md](publier-une-version.md).
|
|
Pour comprendre le code, voir [architecture.md](architecture.md).
|
|
|
|
## 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, **une archive par architecture**, 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.
|
|
|
|
Le packaging s'inspire de [radarr_ynh](https://github.com/YunoHost-Apps/radarr_ynh),
|
|
autre application .NET packagée sans Docker : c'est de là que vient le déploiement
|
|
self-contained, qui évite d'installer `dotnet-runtime` côté système.
|
|
|
|
**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`.
|
|
|
|
## 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.
|
|
|
|
---
|
|
|
|
## Mettre en production
|
|
|
|
La **première mise en production**, pas à pas. Pour la **montée de version**, voir
|
|
« Monter de version » dans [publier-une-version.md](publier-une-version.md).
|
|
`publier.sh` 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)
|
|
```
|
|
|
|
### Première mise en production
|
|
|
|
#### 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`).
|
|
|
|
#### 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 1.0.0
|
|
```
|
|
|
|
⚠️ **C'est le seul cas où `--version` s'impose.** Le script propose normalement
|
|
d'incrémenter la version précédente, qu'il lit dans les tags ou dans `manifest.toml` ;
|
|
à la toute première publication il n'y en a aucune, et il refuse plutôt que de deviner.
|
|
Ensuite, `./build/publier.sh` sans option suffit.
|
|
|
|
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 l'étape 3. Les étapes 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).
|
|
|
|
#### 3. Déposer la release
|
|
|
|
Le tag `v1.0.0` est déjà poussé par l'étape 2 (`git tag v1.0.0 && git push origin main --tags`
|
|
si l'on reprend à la main).
|
|
|
|
**Dans l'interface Gitea** : créer la release `v1.0.0` sur le dépôt `mabibli`, et y
|
|
téléverser **les deux archives** :
|
|
|
|
```
|
|
build/dist/mabibli-1.0.0-linux-x64.tar.gz
|
|
build/dist/mabibli-1.0.0-linux-arm64.tar.gz
|
|
```
|
|
|
|
⚠️ **Les deux, sans exception.** Le manifeste déclare `architectures = ["amd64", "arm64"]`
|
|
et YunoHost choisit la ligne correspondant à la machine : une archive manquante n'échoue
|
|
pas ici, elle échoue chez celui qui installe, dans `ynh_setup_source`.
|
|
|
|
Vérification, à faire **sans être authentifié** (autre navigateur, ou `curl` comme
|
|
ci-dessous) :
|
|
|
|
```bash
|
|
curl -fsSLI "https://forge.example.org/mabibli/releases/download/v1.0.0/mabibli-1.0.0-linux-x64.tar.gz" | head -1
|
|
curl -fsSLI "https://forge.example.org/mabibli/releases/download/v1.0.0/mabibli-1.0.0-linux-arm64.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é.
|
|
|
|
#### 4. Le paquet, sur Gitea
|
|
|
|
⚠️ **L'étape la plus facile à oublier quand elle est manuelle** `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. L'étape 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 1.0.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
|
|
```
|
|
|
|
#### 5. Installer
|
|
|
|
```bash
|
|
sudo yunohost app install https://git.akbar.nohost.me/mathieu/mabibli_ynh --debug
|
|
```
|
|
|
|
YunoHost demande le domaine (celui créé à l'étape 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.
|
|
|
|
#### 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.
|
|
|
|
---
|
|
|
|
### 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 ou arm64.** Contrairement au paquet YunoHost, cette voie ne
|
|
télécharge aucune archive : le `Dockerfile` compile depuis les sources, pour
|
|
l'architecture de l'hôte, sur des images .NET qui existent pour les deux. ⚠️ **Rien n'a
|
|
été essayé sur un NAS ARM réel** — ni Container Manager, ni le proxy inversé de DSM.
|
|
- **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`.
|
|
|
|
### 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.
|
|
|
|
---
|
|
|