Files
mabibli_ynh/conf/systemd.service
T
mathieuandClaude Opus 5 7ab423783f Rendre le data_dir visible au service : ProtectHome=tmpfs + BindPaths
Le data_dir de YunoHost vit sous /home/yunohost.app/mabibli, or
ProtectHome=yes rend /home vide dans le namespace du service : le démarrage
mourait sur « SQLite Error 14: unable to open database file ».

ReadWritePaths ne perce pas ProtectHome — vérifié sur une unité de test :
`yes` + ReadWritePaths échoue, `tmpfs` + BindPaths réussit. La forme retenue
garde les répertoires personnels du serveur invisibles au service.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 19:26:48 +02:00

122 lines
5.2 KiB
Desktop File

[Unit]
Description=MaBibli — gestion de bibliothèque personnelle
Documentation=https://git.akbar.nohost.me/mathieu/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