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:
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user