Files
mabibli_ynh/conf/systemd.service
T
mathieuandClaude Opus 5 6d88ddb1d0 Restaure les vraies URL : on anonymise une copie, jamais l'original
Le manifeste de la 0.5.0, deja publiee, s'est mis a pointer une archive
introuvable — l'installation echouait sur ynh_setup_source, sans que le
symptome designe la cause.

Le remplacement global des URL avait reecrit amd64.url, c'est-a-dire
precisement le champ que la documentation ecrite le meme jour designait comme
« a ne jamais changer a la main », puisque publier.sh le regenere a chaque
publication.

⚠ L'erreur de conception est plus large : le placeholder avait sa place dans la
COPIE publique, pas dans le depot de travail, qui sert au deploiement reel et
doit rester operationnel. Anonymiser un depot qui sert encore, c'est le casser.

Ce qui reste, et qui vaut : depot_code commande toujours toutes les URL
derivees, et le garde-fou refuse toujours de publier avec une URL en
example.org — il ne se declenche simplement plus ici, puisqu'il sert a la copie
publique.

sha256 inchange : seule l'URL avait bouge, le manifeste retrouve exactement son
etat de publication.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-22 14:22:50 +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