Ouvre une voie d'hébergement hors YunoHost, en disant ce qu'elle coûte

Un tiers veut installer le projet sur un Synology, où rien du paquet
`_ynh` n'existe. L'application n'étant qu'un processus et un fichier
SQLite, un `Dockerfile` suffit — mais l'essentiel n'est pas là.

Sans portail SSO, il n'y a pas d'identité, et l'application ne s'en
invente pas : les données personnelles se ferment, les communes non.
Mesuré plutôt que supposé, et écrit comme tel : les deux replis
(utilisateur simulé, en-tête injecté) n'authentifient personne, et le
port ne doit être publié que sur la boucle locale.

Le déploiement de référence reste `mabibli_ynh`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
mathieu
2026-08-21 23:23:49 +02:00
co-authored by Claude Opus 5
parent 63bf6be3e1
commit abaa520f59
5 changed files with 234 additions and 0 deletions
+11
View File
@@ -0,0 +1,11 @@
# Le contexte de build ne doit porter que les sources : recopier les `bin/` et `obj/` de
# la machine de développement ralentit le build et, pire, peut faire réutiliser des
# artefacts compilés pour un autre système.
**/bin/
**/obj/
.git/
.idea/
.claude/
*.db
*.db-wal
*.db-shm
+56
View File
@@ -2054,6 +2054,62 @@ lui-même** pour les afficher dans son catalogue et son interface d'administrati
compilation, et rien n'est recompilé sur le serveur. Le manifeste déclare donc compilation, et rien n'est recompilé sur le serveur. Le manifeste déclare donc
l'application en `full_domain`. l'application en `full_domain`.
## Héberger hors YunoHost — voie SECONDAIRE, ajoutée le 2026-08-21
Demande venue d'un tiers voulant installer le projet sur un **Synology**. `Dockerfile`,
`.dockerignore` et `compose.yaml` sont à la racine du dépôt du code, et la marche à suivre
est dans `README.md` (« Installer ailleurs que sur YunoHost »).
⚠️ **Le déploiement de référence reste `mabibli_ynh`, et cela ne se discute pas** : c'est le
seul qui apporte une **authentification**. Cette voie-ci n'en apporte aucune, et il ne faut
pas la présenter comme une seconde façon d'installer « la même chose ».
### Ce que l'absence de portail coûte exactement
Mesuré, pas supposé : sans `YNH_USER`, `UtilisateurCourant.Anonyme` circule, et les données
**personnelles** se ferment (`400` avec un message lisible sur les envies, aucun statut de
lecture), tandis que tout ce qui est **commun** au foyer continue de fonctionner. C'est la
triple portée du projet — commune / personnelle / trace — qui devient visible à l'usage.
Deux replis, et **aucun des deux n'authentifie** :
| Repli | Quand |
|---|---|
| `Identite__UtilisateurSimule` en variable d'environnement | mono-utilisateur ; c'est le mécanisme de développement, qui n'a rien de spécifique à `Development` — c'est de la configuration ordinaire |
| en-tête `YNH_USER` injecté par le proxy inversé | dès qu'un proxy authentifiant existe (Authelia…), il n'y a plus qu'à recopier son `Remote-User`**l'application ne change pas** |
⚠️ Le corollaire est le même que sur YunoHost et il est **plus facile à rater** ici : le port
ne doit être publié que sur `127.0.0.1`. Quiconque atteint le port *est* l'utilisateur
déclaré. Dans un conteneur, écouter sur `+:8080` est sans danger — c'est la **publication**
du port qui décide de l'exposition, pas l'écoute.
### Trois décisions d'image
- **Pas de `--self-contained`** dans le conteneur, contrairement au paquet YunoHost. Le
self-contained sert à ne rien exiger d'un serveur qu'on ne maîtrise pas ; l'image `aspnet`
fournit déjà le runtime, et l'embarquer une seconde fois ne servirait qu'à grossir.
- **Pas d'`ASPNETCORE_URLS`** : l'image écoute déjà sur 8080, et le poser journalise un
avertissement `Overriding HTTP_PORTS` à chaque démarrage — du bruit dans les logs, sur la
voie précisément destinée à qui ne connaît pas le projet.
- **Aucun `dotnet workload install`** n'est nécessaire : vérifié, la machine de
développement n'a **aucune** charge de travail installée et le publish Blazor WASM
(trimming compris) passe avec le SDK nu.
### Les trois contraintes reconduites telles quelles
x86_64 (le publish visé est `linux-x64`), **HTTPS** sans quoi le scan caméra ne s'ouvre
jamais, et un **nom d'hôte entier**`<base href="/">` et les empreintes du service worker
sont figés à la compilation, exactement ce qui impose `full_domain` à YunoHost.
### Vérifié en exécution le 2026-08-21
Image construite, conteneur démarré sur une base neuve : migrations appliquées dans le
volume, `/` et `_framework/blazor.webassembly.js` en 200, et les trois états d'identité
(simulée, absente, en-tête `YNH_USER`) rendus par `GET /api/moi` conformément au tableau
ci-dessus. ⚠️ **Rien n'a été éprouvé sur un Synology réel** — ni Container Manager, ni le
proxy inversé de DSM, ni son certificat. Ce sont des gestes de DSM, pas du code, mais le
README doit continuer de le dire.
## Historique du projet (pourquoi ces choix) ## Historique du projet (pourquoi ces choix)
L'utilisateur a testé deux solutions existantes avant de se lancer dans un projet custom : L'utilisateur a testé deux solutions existantes avant de se lancer dans un projet custom :
+37
View File
@@ -0,0 +1,37 @@
# Image pour héberger MaBibli AILLEURS que sur YunoHost (Synology, NAS, VPS…).
#
# ⚠️ Ce n'est PAS le chemin de déploiement de référence : celui-ci reste le paquet
# `mabibli_ynh`, qui déploie un publish self-contained sans conteneur (voir README,
# « Mettre en production »). Cette image existe pour les hébergements qui n'ont pas de
# portail SSO — et elle n'apporte donc AUCUNE authentification. Lire impérativement
# « Installer ailleurs que sur YunoHost » dans le README avant de l'exposer.
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
WORKDIR /src
# `MaBibli.Api` référence `MaBibli.Client` : le client Blazor WebAssembly est compilé au
# passage et atterrit dans `wwwroot/`. Un seul projet à publier suffit pour les trois.
# Aucun `dotnet workload install` n'est nécessaire : le SDK .NET 10 résout l'outillage
# WebAssembly tout seul (vérifié — aucune charge de travail n'est installée en local).
COPY . .
RUN dotnet publish MaBibli.Api --configuration Release --output /app
# Pas de `--self-contained` ici, contrairement au paquet YunoHost : l'image `aspnet`
# embarque déjà le runtime. Le self-contained sert à ne rien exiger d'un serveur qu'on
# ne maîtrise pas ; dans un conteneur, la question ne se pose pas.
FROM mcr.microsoft.com/dotnet/aspnet:10.0
WORKDIR /app
COPY --from=build /app .
# La base vit dans un volume, jamais à côté des binaires : c'est ce qui la fait survivre
# au remplacement de l'image. Même règle que le `data_dir` de YunoHost.
ENV ConnectionStrings__MaBibli="Data Source=/data/mabibli.db"
VOLUME ["/data"]
# L'image `aspnet` écoute déjà sur 8080 : poser `ASPNETCORE_URLS` ne ferait qu'écraser ce
# réglage et journaliser un avertissement à chaque démarrage. Pour changer de port,
# `ASPNETCORE_HTTP_PORTS`. ⚠️ Écouter sur toutes les interfaces est sûr DANS un conteneur —
# c'est la publication du port qui décide de l'exposition, voir `compose.yaml`.
EXPOSE 8080
ENTRYPOINT ["dotnet", "MaBibli.Api.dll"]
+101
View File
@@ -71,6 +71,10 @@ pour les trois (`MaBibli.Client`, `MaBibli.Shared`, `MaBibli.Api`). `--self-cont
embarque le runtime .NET dans le dossier produit : aucun `dotnet-runtime` n'est requis embarque le runtime .NET dans le dossier produit : aucun `dotnet-runtime` n'est requis
côté serveur. 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 ⚠️ **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 complet à une machine qui n'en a pas besoin, pour une compilation lente. Voir
`CLAUDE.md`, section « Chaîne de publication ». `CLAUDE.md`, section « Chaîne de publication ».
@@ -468,6 +472,103 @@ Les trois doivent être négatifs. Le domaine, lui, reste déclaré dans YunoHos
--- ---
## 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 ## Contraintes permanentes du paquet — ce ne sont pas des tâches
### Le dépôt du code doit rester public ### Le dépôt du code doit rester public
+29
View File
@@ -0,0 +1,29 @@
# Déploiement hors YunoHost — voir README, « Installer ailleurs que sur YunoHost ».
# Sur Synology : Container Manager > Projet > créer un projet à partir de ce fichier.
services:
mabibli:
build: .
container_name: mabibli
restart: unless-stopped
# ⚠️ Le port n'est publié QUE sur la boucle locale du NAS. L'application n'a aucune
# authentification (voir ci-dessous) : quiconque atteint ce port EST l'utilisateur
# déclaré. Le seul chemin d'accès doit être le proxy inversé, en HTTPS.
ports:
- "127.0.0.1:8080:8080"
# La bibliothèque vit ici, et nulle part ailleurs. À adapter au NAS
# (`/volume1/docker/mabibli` est la convention Synology).
volumes:
- ./donnees:/data
environment:
# ⚠️ CE N'EST PAS UNE AUTHENTIFICATION. Sans portail SSO, l'application n'a aucune
# identité à lire : sans cette valeur, la liste d'envies et les statuts de lecture
# sont refusés (le catalogue et les prêts, eux, sont communs et fonctionnent).
# L'alternative, si un proxy authentifiant est en place, est de lui faire injecter
# l'en-tête `YNH_USER` et de retirer ces trois lignes.
Identite__UtilisateurSimule: "prenom"
Identite__EmailSimule: "prenom@example.org"
Identite__NomCompletSimule: "Prénom Nom"