Paquet YunoHost de MaBibli 1.0.0

Installe l'archive self-contained publiée en release du dépôt du code,
amd64 ou arm64 selon la machine.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Mathieu Limonier
2026-08-22 22:36:27 +02:00
co-authored by Claude Opus 5
commit 1373532345
16 changed files with 2058 additions and 0 deletions
+49
View File
@@ -0,0 +1,49 @@
location / {
# Le service n'écoute que sur la boucle locale (cf. conf/systemd.service).
# nginx est le seul chemin d'accès, et donc le seul endroit où l'identité
# est établie.
proxy_pass http://127.0.0.1:__PORT__;
# ---------------------------------------------------------------------------
# Intégration SSOwat — c'est cette ligne qui rend `YNH_USER` fiable
# ---------------------------------------------------------------------------
# `proxy_params_with_auth` (fourni par YunoHost) transmet à l'application les
# en-têtes d'identité, là où `proxy_params_no_auth` les vide explicitement.
#
# Deux jeux d'en-têtes coexistent, et ce n'est pas une redondance :
# - SSOwat (Lua, `access.lua`) injecte `YNH_USER`, `YNH_USER_EMAIL` et
# `YNH_USER_FULLNAME` dans la requête ; c'est ce que MaBibli lit.
# - `proxy_params_with_auth` ajoute par-dessus `Ynh-User`, `Ynh-User-Email`,
# `Ynh-User-Fullname`, `REMOTE_USER` et `X-Forwarded-User`, recopiés depuis
# les précédents pour les applications qui attendent la forme à tirets.
#
# Anti-usurpation : avant toute injection, SSOwat efface *tous* les en-têtes
# entrants dont le nom commence par `ynh_` ou `ynh-`, sans distinction de casse.
# Un client ne peut donc pas fournir son propre `YNH_USER` — à condition que la
# requête passe bien par nginx, d'où la contrainte d'écoute sur 127.0.0.1.
include proxy_params_with_auth;
# Le client Blazor télécharge des assemblies .wasm de plusieurs mégaoctets, et le
# premier chargement en enchaîne beaucoup. Des tampons plus larges évitent que
# nginx ne les fasse transiter par des fichiers temporaires.
proxy_buffers 16 32k;
proxy_buffer_size 32k;
# Le lookup ISBN interroge la BnF puis OpenLibrary en cascade, avec un délai de
# 10 s par source côté application : laisser à nginx une marge au-dessus.
proxy_read_timeout 60s;
# Aucun envoi de fichier : les ebooks sont des fiches, pas des fichiers
# (CLAUDE.md, « Ebooks : fiches uniquement »). Une limite basse suffit.
client_max_body_size 1m;
# `application/wasm` n'est pas dans la liste gzip par défaut, et les assemblies
# Blazor se compressent très bien.
gzip on;
gzip_min_length 1024;
gzip_proxied any;
gzip_types application/wasm application/octet-stream application/json
application/javascript text/css text/plain
application/manifest+json;
}
+121
View File
@@ -0,0 +1,121 @@
[Unit]
Description=MaBibli — gestion de bibliothèque personnelle
Documentation=https://forge.example.org/mabibli
After=network.target
[Service]
Type=simple
User=__APP__
Group=__APP__
# WorkingDirectory n'est PAS décoratif ici. ASP.NET Core déduit son *content root*
# du répertoire courant, et c'est sous ce content root qu'il cherche `wwwroot/`,
# c'est-à-dire tout le client Blazor WebAssembly. Vérifié en local : lancé depuis un
# autre répertoire, le service démarre, répond 200 sur `/`… et journalise
# « The WebRootPath was not found », l'application n'étant jamais servie.
WorkingDirectory=__INSTALL_DIR__
ExecStart=__INSTALL_DIR__/MaBibli.Api
# ---------------------------------------------------------------------------
# CONTRAINTE DURE : écoute sur 127.0.0.1 uniquement
# ---------------------------------------------------------------------------
# L'application fait confiance à l'en-tête `YNH_USER` pour savoir qui la consulte.
# Cet en-tête n'est digne de confiance que parce que SSOwat l'écrase à chaque
# requête traversant nginx. Un service qui écouterait sur 0.0.0.0 serait joignable
# en direct sur le port interne, sans passer par nginx : n'importe qui sur le réseau
# pourrait alors forger `YNH_USER: mathieu` et se faire passer pour un membre du
# foyer. L'adresse ci-dessous est donc une frontière de sécurité, pas un réglage.
#
# Ne jamais remplacer par `http://*:__PORT__`, `http://0.0.0.0:__PORT__`
# ni `http://+:__PORT__`.
Environment=ASPNETCORE_URLS=http://127.0.0.1:__PORT__
Environment=ASPNETCORE_ENVIRONMENT=Production
# La base vit dans le répertoire de données, pas à côté du binaire : c'est ce qui
# permet à `upgrade` de remplacer tout `install_dir` sans perdre la bibliothèque.
# `ConnectionStrings__MaBibli` surcharge la valeur d'`appsettings.json`
# (`Data Source=mabibli.db`, relative au content root).
#
# ⚠️ LES GUILLEMETS SONT OBLIGATOIRES — ne pas les retirer.
# `Environment=` découpe sur les espaces. Sans guillemets, cette ligne ne définit pas
# une variable mais DEUX : `ConnectionStrings__MaBibli=Data` et
# `Source=__DATA_DIR__/mabibli.db`. L'application reçoit alors la chaîne de connexion
# `Data` et meurt au démarrage sur :
# ArgumentException: Format of the initialization string does not conform to
# specification starting at index 0.
# Constaté à la première installation réelle (2026-08-18), et reproduit isolément sur
# une unité de test : sans guillemets `[Data]`, avec `[Data Source=/tmp/x.db]`.
Environment="ConnectionStrings__MaBibli=Data Source=__DATA_DIR__/mabibli.db"
# Pas de socket de diagnostic .NET, pas de télémétrie sortante.
Environment=DOTNET_EnableDiagnostics=0
Environment=DOTNET_CLI_TELEMETRY_OPTOUT=1
# Certains chemins .NET cherchent un HOME ; le pointer vers le répertoire de données
# évite toute tentative d'écriture dans un home inexistant.
Environment=HOME=__DATA_DIR__
Restart=on-failure
RestartSec=5
TimeoutStopSec=20
StandardOutput=journal
StandardError=journal
SyslogIdentifier=__APP__
# ---------------------------------------------------------------------------
# Durcissement
# ---------------------------------------------------------------------------
NoNewPrivileges=yes
PrivateTmp=yes
PrivateDevices=yes
DevicePolicy=closed
# ⚠️ `yes` ne convient PAS ici, et c'est contre-intuitif.
# Le `data_dir` de YunoHost vit sous `/home/yunohost.app/__APP__`. `ProtectHome=yes`
# rend `/home` inaccessible et vide dans le namespace du service : la base devient
# introuvable, et le démarrage meurt sur « SQLite Error 14: unable to open database
# file ». `ReadWritePaths=` ne suffit pas à percer cette protection — mesuré sur une
# unité de test : `yes` + `ReadWritePaths` échoue, `tmpfs` + `BindPaths` réussit.
#
# `tmpfs` conserve l'essentiel du bénéfice : les répertoires personnels du serveur
# restent invisibles au service, seul son propre répertoire de données est remonté.
ProtectHome=tmpfs
BindPaths=__DATA_DIR__
ProtectHostname=yes
ProtectClock=yes
ProtectControlGroups=yes
ProtectKernelModules=yes
ProtectKernelTunables=yes
ProtectKernelLogs=yes
ProtectProc=invisible
RestrictNamespaces=yes
RestrictRealtime=yes
RestrictSUIDSGID=yes
LockPersonality=yes
RemoveIPC=yes
SystemCallArchitectures=native
# Tout le système en lecture seule, sauf le répertoire de données. `install_dir`
# appartient à root : le service ne peut donc pas réécrire ses propres binaires.
ProtectSystem=strict
ReadWritePaths=__DATA_DIR__
# AF_INET/AF_INET6 restent nécessaires : le service écoute en TCP local *et* appelle
# les API BnF et OpenLibrary en sortie. Pour cette raison, pas de `IPAddressDeny=any`.
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
# Aucune capacité : le port alloué par YunoHost est > 1024, donc même
# CAP_NET_BIND_SERVICE est inutile.
CapabilityBoundingSet=
AmbientCapabilities=
# Liste reprise de radarr_ynh, autre application .NET packagée sans Docker, donc
# éprouvée sur ce runtime.
SystemCallFilter=~@clock @debug @module @mount @obsolete @reboot @setuid @swap
# ⚠️ Ne PAS ajouter MemoryDenyWriteExecute=yes : le JIT de .NET a besoin de pages
# exécutables inscriptibles, le service ne démarrerait pas.
[Install]
WantedBy=multi-user.target