#:schema https://raw.githubusercontent.com/YunoHost/apps/master/schemas/manifest.v2.schema.json packaging_format = 2 id = "mabibli" name = "MaBibli" description.fr = "Gestion de bibliothèque personnelle : catalogue, prêts et scan ISBN" description.en = "Personal library manager: catalogue, loans and ISBN scanning" version = "1.0.1~ynh1" maintainers = ["mathieu"] [upstream] license = "AGPL-3.0-or-later" code = "https://git.akbar.nohost.me/mathieu/mabibli" [integration] yunohost = ">= 12.1.38" helpers_version = "2.1" # ⚠️ Une architecture déclarée ici DOIT avoir son archive dans la release : YunoHost # choisit `.url` d'après la machine, et une URL manquante échoue chez celui qui # installe, dans `ynh_setup_source`. `build/publier.sh` compile les deux à chaque # publication, précisément pour qu'aucune des deux ne puisse être oubliée. # # armhf (32 bits) n'est PAS déclarée : .NET ne la prend plus en charge. architectures = ["amd64", "arm64"] # Une seule instance : la bibliothèque est commune au foyer (CLAUDE.md, « Portée des # données »). Deux instances voudraient dire deux collections étanches, ce qui est # exactement ce que le projet a écarté. multi_instance = false # L'application ne parle pas à LDAP : elle lit l'identité dans les en-têtes que # SSOwat injecte. C'est bien une intégration SSO, mais pas une intégration LDAP. ldap = false sso = true # ~170 Mo de publish self-contained (runtime .NET embarqué), plus la base et la marge # de décompression de l'archive. disk = "500M" # ⚠️ `ram.build` ne désigne AUCUNE compilation : rien n'est compilé sur le serveur, c'est # tout l'intérêt du self-contained. Il couvre le téléchargement et la décompression de # l'archive de 69 Mo. Ne pas aller chercher la valeur du côté d'un build .NET. ram.build = "50M" # Mesuré sur le serveur le 2026-08-22, service en marche : # cat /sys/fs/cgroup/system.slice/mabibli.service/memory.peak -> 208 793 600 o # soit 199,1 Mio de pic, contre 200M déclarés jusqu'ici : 0,4 % de marge, c'est-à-dire # aucune. Porté à 256M. # # ⚠️ `systemctl show -p MemoryPeak` ne renvoie RIEN sur ce serveur — la commande réussit # en silence et l'on croit lire un pic là où l'on ne lit rien. Passer par le cgroup. # # ⚠️ Le chiffre MAJORE le besoin : `memory.peak` compte le cache de fichiers, donc une # part des 69 Mo de binaire mappé, récupérable sous pression — et le GC de .NET se serre # sur une machine étroite. Mais YunoHost compare une déclaration statique à la RAM # disponible, sans faire cette nuance. L'asymétrie tranche : un refus d'installation est # visible et se contourne, un OOM survient plus tard, sous une opération lourde, sans que # rien ne désigne la cause. # # ⚠️ Le pic n'est PAS le lookup ISBN, contrairement à l'intuition — une notice. C'est # « Nouveautés » sur un auteur très réédité : jusqu'à dix pages de 100 notices SRU en # parallèle, plus le catalogue entier chargé pour le rapprochement. Ce cas-là n'a pas # encore été mesuré, et il croît avec la taille de la bibliothèque : à reprendre le jour # où le fonds aura beaucoup grossi. ram.runtime = "256M" [install] [install.domain] type = "domain" # Pas de question `path` : MaBibli s'installe sur un domaine entier. # # Ce n'est pas un oubli. Le client Blazor WebAssembly fige son `` # et les empreintes de `service-worker-assets.js` **à la compilation**. Servir # l'application sous un sous-chemin demanderait de réécrire `index.html` sur le # serveur, ce qui invaliderait les empreintes d'intégrité du service worker et # casserait le mode hors-ligne. Comme le paquet ne compile rien sur le serveur, # la seule option honnête est le domaine entier. # # Conséquence côté YunoHost : `resources.permissions.main.url` étant une chaîne, # le cœur classe l'app en « full_domain » et refusera un changement d'URL vers # un sous-chemin. # Pas de question `init_main_permission` : l'accès est fixé à `all_users` par la # ressource `permissions` ci-dessous, et l'installation ne demande donc rien. # # Vérifié dans le cœur YunoHost (`src/utils/resources.py`) : # # init_allowed = infos["allowed"] or self.get_setting(f"init_{perm}_permission") or [] # # `allowed` l'emporte sur la question ; garder la question l'aurait donc posée pour # rien, en laissant croire que la réponse comptait. # # ⚠️ Ce code ne s'exécute qu'à la CRÉATION de la permission. Une montée de version ne # réaligne rien : un accès élargi à la main le reste. [resources] [resources.sources.main] # Archives **déjà compilées** (publish self-contained), produites par # `build/publier.sh` et déposées en release sur le Gitea de l'utilisateur. # Le SDK .NET n'est jamais installé sur le serveur : c'est tout l'intérêt du # self-contained (CLAUDE.md, « Décisions techniques actées »). # # YunoHost retient la ligne correspondant à l'architecture de la machine. # # ⚠️ Ces quatre valeurs sont réécrites par `build/publier.sh`, jamais à la main. amd64.url = "https://git.akbar.nohost.me/mathieu/mabibli/releases/download/v1.0.1/mabibli-1.0.1-linux-x64.tar.gz" amd64.sha256 = "9978c559084db34007b4fca82ec4013b7d36d5652c798e471a1e918892b6e908" arm64.url = "https://git.akbar.nohost.me/mathieu/mabibli/releases/download/v1.0.1/mabibli-1.0.1-linux-arm64.tar.gz" arm64.sha256 = "d9056ae39bc48ba3396b1791b1ea37c1d30a9cf75336f6cb45b1a2a0491802a5" # L'archive dépose ses fichiers à la racine, sans dossier intermédiaire. in_subdir = false # Pas d'`autoupdate` : les releases sont déposées à la main sur un Gitea privé, # aucune stratégie amont connue de YunoHost ne s'y applique. [resources.system_user] [resources.install_dir] # Le service tourne sous `mabibli` mais ne doit **pas** pouvoir réécrire ses # propres binaires : propriétaire root, application en lecture seule. Cela va de # pair avec `ProtectSystem=strict` dans l'unité systemd. owner = "root:rwx" group = "__APP__:rx" [resources.data_dir] # La base SQLite vit ici, et **pas** à côté du binaire : c'est ce qui permet à une # mise à jour de remplacer intégralement `install_dir` sans toucher aux données. owner = "__APP__:rwx" group = "__APP__:rx" [resources.permissions] # Toute l'application derrière le SSO, `/api` compris : la bibliothèque est # familiale, rien n'a vocation à être public. `auth_header` vaut `true` par # défaut, c'est lui qui autorise SSOwat à injecter `YNH_USER` — sans quoi # l'application ne saurait jamais qui la consulte. main.url = "/" main.auth_header = true # ╔══════════════════════════════════════════════════════════════════════════════╗ # ║ ⚠️ MaBibli EST UNE APPLICATION PRIVÉE. NE JAMAIS L'OUVRIR AUX `visitors`. ║ # ╚══════════════════════════════════════════════════════════════════════════════╝ # # Ce n'est pas une préférence de confidentialité, c'est une propriété du code, et # elle vaut sur les DEUX moitiés de l'application : # # • L'application n'a AUCUNE authentification propre. Elle lit `YNH_USER`, un # en-tête que SSOwat injecte, et fait confiance à ce qu'il contient. Ouverte aux # visiteurs, il n'y a plus personne pour l'écraser : tout ce qui est PERSONNEL # (statuts de lecture, liste d'envies) devient lisible et modifiable par le # premier venu. C'est la même frontière que « le service n'écoute que sur # 127.0.0.1 » — SSOwat n'est pas un confort, c'est la seule porte. # # • Il n'existe AUCUNE notion d'écriture réservée. Toute personne autorisée peut # ajouter, modifier et SUPPRIMER n'importe quel livre du catalogue commun, ainsi # que noter un prêt ou son retour. C'est délibéré — un foyer, pas un service # public — et cela suppose que les personnes autorisées se connaissent. # # `all_users` est donc le plus large qui ait du sens : les comptes du serveur. # Restreindre davantage (un groupe dédié) est légitime ; élargir ne l'est pas. # # ⚠️ Volontairement PAS `protected = true` : l'administrateur du serveur reste # maître de ses permissions, et peut vouloir restreindre à un groupe. C'est # l'élargissement qui est un contresens, pas le réglage lui-même. main.allowed = "all_users" [resources.ports] # Port interne uniquement. `exposed` vaut `false` par défaut : le pare-feu reste # fermé, et l'unité systemd n'écoute que sur 127.0.0.1. [resources.apt] # `sqlite3` sert aux sauvegardes : la base est en mode WAL (vérifié), un simple # `cp` du fichier `.db` ne suffit pas. Voir `scripts/backup`. packages = "sqlite3"