Un seul listener par hôte (profil default) sert tous les profils sous
/p/<profil>/... au lieu d'un gateway séparé par profil. Le pont Discord
existant (services/discord-bot) appelle maintenant
http://127.0.0.1:8642/p/discord-bridge/v1/chat/completions et expose une
surface de test locale (HERMES_BRIDGE_ASK_PORT). services/discord-hermes-bridge
(gateway dédié, 71 lignes) est retiré ; la clé est réutilisée depuis
secrets/discord-hermes-bridge.yml (clé renommée hermes_api_server_env).
- enableDiscordToken (option, même contrainte que enableMatrixToken : tower only)
- DISCORD_ALLOWED_USERS (@marc uniquement)
- plugin discord-auto-join : rejoint automatiquement le vocal quand un membre
autorisé y entre (register_platform_handler + join_voice_channel)
- Opus (libopus) : LD_LIBRARY_PATH posé sur l'unité systemd dès le démarrage
(dlopen lit sa valeur au lancement, pas via .env) ; binutils (ld/objdump)
pour le repli find_library, ffmpeg pour la conversion PCM 48kHz TTS/réception
- secret hermes_discord_env (secrets/hermes-discord.yml)
home-manager.useGlobalPkgs=true (test perf, évite d'évaluer nixpkgs 2x) est
revenu trop cher/instable en pratique. Retour à home-manager.users.marc
totalement désactivé côté système (profil standalone via `HM=marc just home`
uniquement). Conséquences :
- modules/home/gtk : catppuccin.autoEnable repasse à sa valeur par défaut
- modules/home/zen-browser : l'overlay nur revient dans son propre module
- profiles/common : commentaire nettoyé (n'a plus besoin d'expliquer le piège
useGlobalPkgs, qui ne s'applique plus)
- herdr : posé côté système (modules/nixos/herdr, symlink store ->
~/.config/herdr/config.toml) puisque le home-manager systeme ne construit
plus la conf de marc ; modules/home/herdr et son import dans
users/marc/home.nix restent (profil standalone), mais ne sont plus le
mécanisme actif sur tower.
/home/marc/media posait deux problèmes : /home/marc est 0700 (chaque service
devait recevoir un droit de traversée) et systemd-tmpfiles refuse de créer
quoi que ce soit sous /home/marc (« unsafe path transition », exit 73).
- services/media (nouveau) : dataset nixos/media -> /srv/media, groupe media
(setgid, dossiers 2775/2770), arborescence movies/tvshows/music/books/downloads
- disko.nix : dataset media à côté de marc/nix
- navidrome, jellyfin, kodi : bibliothèque -> /srv/media/{music,movies,tvshows}
- qbittorrent : téléchargements dans le même dataset que la bibliothèque
(permet aux *arr d'importer par lien dur au lieu de copier)
- radarr : dataDir corrigé (pointait sur celui de sonarr par copier-coller),
déplacé hors /home (impossible avec ProtectHome+tmpfiles du module)
- sonarr : dataDir dédié + migration depuis /home/marc/radarr (était figé
depuis le 28/07, port 8006 muet) ; réparé le 27/09/2026
- readarr : bibliothèque -> /srv/media/books
Bibliothèque sous /srv/media (dataset partagé, cf services/media). Clé API
dans secrets/audiobookshelf.yml, pas encore consommée par un service. Exposé
publiquement via Pangolin (audiobookshelf.marcpartensky.com).
Positions, soldes, NAV, transactions via Flex Web Service (HTTPS, sans session
IBKR, sans 2FA, sans gateway local). Le token Flex est indépendant du mot de
passe du compte et révocable depuis le portail : aucun pouvoir de passer un
ordre. Token + Query IDs encore des placeholders dans secrets/ibkr.yml.
jr2804/vaultwarden-mcp, binaire Rust téléchargé depuis GitHub releases (plus
simple qu'un crate2nix). Mot de passe master dans secrets/tower.yml
(vaultwarden_mcp_password). Non importé (services/default.nix) : input flake
manquant, cf FIXME.
- buildGoModule, transport stdio, GITEA_ACCESS_TOKEN_FILE (secret jamais en clair)
- v1.6.0 (pas la dernière) : v1.7.0 exige go >= 1.27, non fourni par nixpkgs 26.05
- secret PAT dans secrets/gitea-mcp.yml (sops)
Le token Codeberg (CODEBERG_ACCOUNTS) est désormais injecté via un .env généré
par sops.templates et sourcé par le wrapper, au lieu d'un placeholder en dur
dans l'option env= (visible dans le nix store).
LoadCredential (%{file:/run/credentials/stalwart.service/acme-secret}%) ne
fonctionnait pas de façon fiable ; le secret est maintenant écrit dans
/etc/stalwart/acme-secret (environment.etc) et référencé par ce chemin.
Gotify 3.1.0 (paquet unstable, absent de 26.05) avec SSO OIDC Zitadel en
client public (PKCE, pas de secret) et ressource Pangolin
gotify.marcpartensky.com en SSO. Le module n'est pas encore importé dans
services/default.nix (ligne ./gotify toujours commentée).
Le WebUI est l'interface principale, il prend donc le domaine court
hermes.marcpartensky.com (full-domain Pangolin + HERMES_WEBUI_ALLOWED_ORIGINS) ;
hermes-mobile.marcpartensky.com reste réservé à l'appli mobile.
hermes serve --isolated sur le profil pocket (127.0.0.1:9119) derrière un
nginx loopback qui réécrit le Host (le serveur refuse tout Host non loopback),
exposé par Pangolin sur hermes-mobile.marcpartensky.com sans SSO : l'appli
native ne peut pas faire le flux navigateur, seul le token de session du profil
protège l'accès. Les deux ports restent fermés au firewall (assertions).
Politique marc du 27/09/2026 : nvidia/nemotron-3-ultra-550b-a55b:free en
high par défaut, puis inkling:free, puis DeepSeek V4.1 Flash en dernier
recours. Nemotron sort de la chaîne de secours puisqu'il devient primaire.
Le WebUI n'écrit le titre d'une session qu'à la fin d'un run (jamais si le run
est interrompu) : la sidebar affiche le premier message brut pendant tout le run.
Le moteur Hermes (hermes_state) connaît déjà le bon titre quelques secondes après
le premier message.
default.nix patche donc le paquet amont pour y installer core_title_adoption.py
(watchdog qui recopie ce titre dans le sidecar du WebUI, state.db ouvert en
lecture seule, désactivable par HERMES_WEBUI_CORE_TITLE_ADOPTION=0) et
api_init.py. À retirer quand le comportement existera en amont dans l'input.
Le service exige le secret sops tailscale/auth_key, absent de
secrets/anywhere.yml : l'éval échouait. À réactiver avec le mesh (headscale est
utilisé en attendant).
Ce stateVersion sélectionne user/group/dataDir du module, pas la release système.
Vérifié le 27/09/2026 sur le VPS (fds ouverts du process + toml actif) : les
données vivantes sont dans /var/lib/stalwart/db ; celles de /var/lib/stalwart-mail
datent de juillet 2026. Ne pas redescendre sous 26.05.
Le Pangolin de ce VPS (schéma ancien) ignore silencieusement public-resources et
mode : la ressource noVNC n'était jamais créée et Traefik répondait 404 sur
vnc.marcpartensky.com. Vérifié le 26/09/2026.
L'option services.protonmail-mcp vient du input protonmail-mcp, importé par le
seul profile tower : l'import global dans services/default.nix cassait l'éval de
laptop/laptop-iso (option inexistante). L'import est commenté avec la raison, et
flake.nix ajoute ./services/protonmail-mcp aux modules de tower uniquement.
vms/dev.nix décrit une VM de dev (autologin marc, console série) construite par
l'input microvm ; ses runners sont exposés dans flake.nix pour être lancés
directement (`nix run .#dev`) et le Justfile ajoute `just vm` / `just vm-build`,
qui gardent l'état de la VM (overlay du store, socket qmp) dans
~/.local/state/microvms/<nom> (supprimer le dossier pour repartir de zéro).
Le runner passe par `path:` volontairement : ça marche même quand vms/<nom>.nix
n'est pas encore commité.
Gateway hermes-agent dédié (profil discord-bridge) sur 127.0.0.1:8643, API Server
OpenAI-compatible consommée par le cog hermes.py du discord-bot. Unité systemd écrite à
la main (le module NixOS ne gère pas d'instances) et même paquet que le bot Matrix,
donc mêmes groupes de deps (edge-tts, stt-whisper).
Clé d'API dans secrets/discord-hermes-bridge.yml, port jamais ouvert au firewall
(assertion). Côté discord-bot, HERMES_BRIDGE_URL et le nom de rôle viennent d'un
EnvironmentFile, seule la clé est secrète.
API GraphQL de Meetup (gql-ext) en stdio, sans authentification pour ce périmètre
(vérifié : eventSearch, groupSearch, recommendedEvents répondent sans token). Les
requêtes nominatives exigent un OAuth consumer de compte Pro : le jour où le token
arrive, il se pose dans secrets/meetup.yml et le serveur le lit via MEETUP_TOKEN_FILE,
sans changement de code.
Paquets et options NixOS/home-manager réelles (mcp-nixos, transport stdio, API
publiques, aucun secret) au lieu de les inventer.
NOTE : pas encore câblé dans services/default.nix, à importer pour l'activer.
Paquet communautaire github:nesquena/hermes-webui (input flake) en service, agent en
process sur le profil pocket, isolé du gateway Matrix.
Deux pièges vérifiés : sans HERMES_WEBUI_ISOLATED_PROFILE=1 le serveur retombe sur le
profil par défaut et écrit dans le state.db racine partagé avec le gateway, et il n'y
a aucune garde d'authentification sur une adresse loopback (le SSO Pangolin est la
seule protection, comme pour terminal.marcpartensky.com). Le PTY (/api/pty) et l'API
complète restent donc en loopback, jamais ouverts au firewall (assertions).
Instance gitea sur tower avec base postgres (rôle gitea en peer auth), compte admin
créé une seule fois par un oneshot idempotent avec le mot de passe de
secrets/gitea.yml (sops). Exposition gitea.marcpartensky.com via newt/Pangolin, et
assertion pour que le port HTTP local reste fermé.
Jellyfin sur tower, répertoires de médias créés par un oneshot, groupe users en
secondaire pour traverser /home/marc (piège classique NixOS). Exposition
jellyfin.marcpartensky.com via newt/Pangolin, port jamais ouvert au firewall.
secrets/hermes-matrix.yml n'a que tower comme destinataire : déclarer le secret là
où le module est importé (laptop, anywhere) ferait échouer l'activation (setupSecrets,
« 0 successful groups required »). Le fichier devient donc une option activée sur
tower seul, et le module est enveloppé dans un bloc config = { ... } pour pouvoir
déclarer `options`.
Le code produit par `herdr integration install hermes` est vendorisé dans
services/hermes/plugins/herdr-agent-state et branché déclarativement (extraPlugins,
symlink sous .hermes/plugins, plus plugins.enabled) : plus d'installation impérative.
Il rapporte l'ID de session du pane à herdr, qui peut alors relancer le pane dans sa
conversation (`hermes --resume <id>`).
LIMITE : le plugin sort si HERDR_ENV != 1, et le socket herdr est celui de marc
(~/.config/herdr/herdr.sock, 0600) : il n'agit que pour une session lancée dans un
pane herdr sous marc, inerte (sans effet de bord) sinon.
Groupe de deps « stt-whisper » (faster-whisper, CPU, gratuit) plus stt.enabled : le
gateway transcrit les vocaux entrants (Element/Matrix). language = "" et pas le
défaut "en", sinon Whisper décode du français comme de l'anglais (mesuré : fr
détecté à p=0.99 sur un vocal court). Modèle « base » (140 Mo, ~5 s pour 3 s d'audio).
numpy sort d'extraPythonPackages : onnxruntime, dépendance de stt-whisper, le
fournit déjà dans le venv scellé, et un doublon fait échouer le build du paquet
hermes (collision avec le venv).
Sans useGlobalPkgs, home-manager instancie un second nixpkgs (toute la distribution
réévaluée à chaque switch). Le partage change l'ordre de merge de
nixpkgs.config.permittedInsecurePackages, un attr « dernier gagne » que plusieurs
modules redéfinissent (matrix-whatsapp, pangolin, packages.nix) : on l'unionne
explicitement dans profiles/common. L'overlay nur de zen-browser passe au niveau
système, incompatible dans le home avec useGlobalPkgs. catppuccin.autoEnable=false
coupe le scan et l'activation auto de tous les programmes HM.
La clé ed25519 du Mac est déclarée pour l'utilisateur hermes. Déclaratif exprès :
/var/lib/hermes est en 2770 (écriture groupe, requis par le partage hermes/marc) et
sshd (StrictModes) ignore alors ~/.ssh/authorized_keys ; l'option écrit
/etc/ssh/authorized_keys.d/hermes en root 444, qui passe le contrôle et survit aux
rebuilds.
Les sessions hermes ouvertes dans un pane herdr tournent sous marc, pas sous le
compte de service : HERMES_HOME=/var/lib/hermes/.hermes (users/marc/home.nix) pour
qu'elles lisent et écrivent le même état (sessions, mémoire, lazy-python) que le
service hermes-agent.
Accès : groupe secondaire hermes pour marc (users.nix) et ACL tmpfiles sur
/var/lib/hermes (traversée) puis .hermes et .hermes/plugins (rwx + défauts ACL),
pour que les fichiers créés par marc restent lisibles côté service. C'est ce qui
rend le plugin herdr-agent-state utilisable : il parle au socket herdr de marc.
herdr n'a ni option NixOS ni module home-manager dans l'input épinglé : sa config
(sidebar qui affiche le modèle hermes de chaque pane) passe par xdg.configFile dans
modules/home/herdr, importé par users/marc/home.nix. force = true car le premier
switch remplace le symlink posé avant par systemd.tmpfiles, que check-link-targets
ne reconnaît pas comme un fichier home-manager-files (activation en status 1 sinon).
tower active home-manager.users.marc = users/marc/home.nix, le même profil que
`HM=marc just home` : herdr et le reste des dotfiles arrivent avec `just nixos`.
backupFileExtension = hm-backup pour la première bascule. Entre `just nixos` et
`just home`, c'est le dernier switch qui écrit les liens.