L'emulateur etait le maillon manquant : la capture tournait deja en service, mais la cible
avd ne vivait que si une session interactive la lancait.
- android-emulator : AVD headless avec -tcpdump vers pcap/avd/. JAVA_HOME force au JDK 17
qu'androidenv utilise lui-meme, parce que le runner appelle la version non encapsulee du
SDK, celle qui reclame JAVA_HOME et sans laquelle l'AVD n'est jamais cree.
- android-avd-install : timer qui installe au boot les APK deposes dans apks/.
- aucune unite bloquante : ni wantedBy sur une oneshot d'installation, ni ExecStartPost qui
attend le boot Android. Une unite laissee en cours de demarrage bloque toute activation
nixos-rebuild, qui sort en « unit already loaded ».
- le dossier apks/ est cree par le service, une regle tmpfiles ne suffisait pas.
Verifie sur tower : switch code 0, qemu actif sous le service, emu.pcap ecrit et croissant
sous l'utilisateur de capture, co.hinge.app installe dans l'AVD du service.
Une cible = une instance Android, donc une interface et ses propres fichiers : c'est la
seule isolation franche du trafic par appli, et elle ne demande aucun root.
- waydroid : capture sur le bridge waydroid0.
- avd : alimentee par le -tcpdump de l'emulateur, qui sort par la pile reseau de l'hote.
- rotation de 10 min, 72 fichiers conserves (12 h), digest d'hotes par cible.
Pas de root : CAP_NET_RAW et CAP_NET_ADMIN en capacites ambiantes, donc NoNewPrivileges
reste a false (systemd refuse la combinaison). Utilisateur statique et non DynamicUser,
plusieurs unites partageant le meme arbre d'etat. marc et hermes sont ajoutes au groupe
pour lire les captures sans root.
Verifie sur tower : tcpdump a l'ecoute sur waydroid0, deux jeux de pcaps distincts, digests
ecrits par les timers (avd : connectivitycheck.gstatic.com ; waydroid : play.googleapis.com).
Les lignes du sélecteur de modèle SANS suffixe [1m] (claude-opus-5-5, claude-opus-5, claude-sonnet-5) passent par le provider natif anthropic, pas par claude-subscription-directsdk. Sans le paquet python anthropic dans le venv scellé, une session ouverte sur une de ces lignes meurt en agent_init_failed : "The 'anthropic' package is required for the Anthropic provider" (lazy installs désactivés par le packaging nix).
Auth du provider natif : aucune clé API à poser, agent/anthropic_credentials.py prend l'OAuth de l'abonnement dans ~/.claude/.credentials.json (refresh et réécriture gérés par Hermes), ou un grant Hermes via hermes auth add anthropic --type oauth.
Les lignes AVEC [1m] (provider claude-subscription-directsdk, CLI claude) ne changent pas. Supprime aussi la ligne commentée devenue doublon : extraDependencyGroups n'est défini qu'une fois.
MCP stdio lancé par hermes-agent sur tower ; parle au Mac en ssh (alias
maclan puis mac, clé hermes). Tools : status (winnie-doctor + révisions),
sync, doctor_fix, xcode_fix (3 opérations root via la règle sudo
NOPASSWD xcode-select/xcodebuild/xcrun), runtime_install et les builds
(arrière-plan, suivi par log_tail) : ios_init, ios_build_sim,
macos_build.
Vérifié : pycodestyle/pyflakes, build de la dérivation, sonde stdio
(tools/list + appels réels sur le Mac), éval du toplevel avant commit.
L'ancien câblage ne pouvait pas marcher : client_id littéral "gitea"
(impossible, Zitadel génère les client_id) et oneshot qui échouait à chaque
boot sur --enabled (flag inexistant en Gitea 1.27.x), avalé par `|| true`.
- App Zitadel "Gitea" (projet marcpartensky) : appId 396570385799184684,
clientId 396570385799250220, type Web, redirect
https://git.marcpartensky.com/user/oauth2/zitadel/callback
- oneshot gitea-oidc-init : add-or-update (id de login_source via psql),
secret lu depuis sops à chaque exécution, idempotent même en dérive
- [openid] d'app.ini : OpenID 2.0 legacy, désactivé (sans rapport avec OIDC,
échouait en "rel=openid2.provider not found" avec une URL Zitadel)
- [oauth2_client] : ENABLE_AUTO_REGISTRATION, USERNAME=email,
ACCOUNT_LINKING=login (page de liaison unique pour le compte local marc)
- ressource Pangolin : auth.sso-enabled=false (auth Gitea seule, une seule
couche, clients git/API à token non bloqués par un redirect SSO)
- secrets/gitea-oidc.yml : vrai oidc_client_secret (chiffré, clé tower)
Le premier bootstrap avait laisse un layout Garage a zero noeud (quorum de 0, inapplicable) : purge unique de meta/data avant le demarrage du service, winnie-bucket reprovisionne bucket, cle et s3.env. L'ingest passe de Type=oneshot a Type=exec pour que RuntimeMaxSec s'applique, et S3_ENV_FILE force le redemarrage du backend quand Garage est provisionne apres son premier demarrage.
postgresql.service est Type=notify : sans after et requires, le oneshot qui pose le mot de passe de la base partait en parallele et mourait sur « connection to server on socket /run/postgresql/.s.PGSQL.5432 failed » (boots du 22/10 20:29 et du 23/10 09:37).
enabled=false, send=false et offer_version=2 ecrits dans config.yaml : la question de partage des stats ne revient plus a chaque session (le defaut ne compte pas comme reponse) et rien n'est collecte.
Deux agents separes au niveau OS (utilisateurs anna et yves, HERMES_HOME dedie, tmpfiles + ExecStartPre qui recree le squelette du home en mode managed), outils dangereux desactives et /api/pty comme /api/files bloques par nginx. Exposes en hermes-anna et hermes-yves.marcpartensky.com ; identifiants hors store nix sous /var/lib/hermes/guests.
services/hermes-pocket devient services/hermes-mobile : le backend de l'appli F-Droid sert le profil default (meme historique, memoire, skills et modele que le gateway Matrix et le dashboard) au lieu du profil isole pocket, en gardant la porte d'auth native (mobile-serve.env). Le nom de ressource Pangolin reste hermes-pocket : le renommer recreerait une ressource sur un domaine deja pris.
tower fait routeur pour son LAN : ip_forward et NAT wg1 -> wlan0 (sortie avec l'IP LAN 192.168.1.44), et le pair tower cote VPS declare desormais 192.168.1.0/24 dans ses allowedIPs. Le Steam Deck et le reste du LAN deviennent joignables depuis rack sans dependre d'un forward de la box.
Timer de 5 minutes qui remet 100.89.128.1/24 et la route du 100.89.128.0/24 sur wg0, et reinsere l'exception iptables INPUT pour 100.89.128.0/24 : tailscaled jette cette source recue sur une interface autre que tailscale0, ce qui avait coupe le tunnel Pangolin 6h40 le 23/10. Complement de gerbil-wg0-ensure.nix, qui ne couvre que le demarrage de gerbil.
Le controleur headscale du VPS passe en mode durci : flotte DERP de Tailscale retiree (derp.urls vide), DERP embarque sur le VPS (region 900, verify_clients) et STUN en UDP 3478 ; route Traefik headscale.marcpartensky.com -> 127.0.0.1:8090. Cote clients, services/tailscale remplace l'ancien secret tailscale/auth_key (absent) par headscale/preauth_key, coupe l'envoi de logs vers Tailscale Inc (disableUpstreamLogging) et est importe sur tower, le deck et anywhere.
Paquets yggdrasil + tor et deux launchd.daemons (tor SOCKS 127.0.0.1:9050,
yggdrasil sur /etc/yggdrasil.conf) pour faire entrer le Mac dans le mesh.
/etc/yggdrasil.conf (mot de passe partagé) reste hors du store, à créer
une fois à la main ; le torrc ne contient aucun secret.
Module services/ygg-mesh : Yggdrasil en réseau privé (mot de passe partagé,
clés publiques épinglées), liens Tor via une instance tor dédiée (onion
service par machine) et lien direct multicast prioritaire sur le LAN.
Chaque machine garde une adresse IPv6 stable et un nom <host>.ygg.
Secret dans secrets/ygg-mesh.yml (règle .sops.yaml dédiée) ; config rendue
à chaud au démarrage (ExecStartPre + credential systemd), le mot de passe
ne touche jamais le store. Identités et clés dans la table `nodes`.
stalwart-mcp (Rust) parle JMAP au Stalwart du VPS, patche pour l'URL
API et l'envoi depuis l'identite marc. Mot de passe dedie dans
secrets/stalwart-mcp.yml ; importe par tower seulement, pour eviter
le conflit de cle sops avec services/stalwart sur anywhere.
Le mineur ne tourne que 01:00-09:00, plus en semaine 09:00-18:00
quand un evenement du calendrier sg est actif. Le wallet vient de
secrets/monero.yml via LoadCredential (plus dans la ligne de
commande) ; meme planning sur le Deck.
Le forward de box 77.207.176.170:42070 est mort : tower devient pair
client du hub wg1 avec persistentKeepalive. L'alias ssh tower pointe
sur 10.100.0.3 et la cle root du VPS est autorisee sur tower. Cle
privee dans secrets/wireguard.yml (recipient tower).
Renomme stalwart_mail_pw2/pw3, spam_pw, noreply_pw, admin_pw, bob_pw
et acme_secret en stalwart_*_password / stalwart_acme_secret, un
fichier sops par usage.
66.108.44.154 passe en allowed-ip : l'auto-ban persiste en base et
bloquait le domicile sur 25/465/993.
meetup@marcpartensky.com partage la boite de marc.
Les blobs common.yml et anywhere.yml sont re-chiffres dans le meme
commit (renommage des cles Stalwart, rotation, postgres_crt/key).
secrets/zitadel-smtp.yml porte le mot de passe du compte d'envoi pour
le provisioning SMTP par API (v2 ignore la config runtime). Le PAT
admin est materialise en 0400 pour hermes et rotate.
dashboard.public_url engage auth_required : la racine ne sert plus le
token de session en clair derriere Pangolin. Le flux client passe par
/auth/password-login puis un ws-ticket a usage unique. nginx annonce
https et la sonde devient /api/status.
App Zitadel publique en PKCE (client_id 395733475048620332), claim
preferred_username limite a marc@marcpartensky.com, redirect explicite
car nginx reecrit le Host. SSO Pangolin coupe pour eviter le double
login.
DeepSeek v4.1 Flash devient le modele par defaut ; chaine de secours
DeepSeek puis Nemotron 3 Ultra gratuit ; le salon de Zoe (Matrix) y
passe aussi.
Beeper est declare dans les mcpServers avec le token lu depuis
l'environnement (plus de token en dur dans config.yaml) et l'URL LAN
pour etre cliquable depuis Hermes Desktop. Le timeout du MCP gitea
est complete.
anki-mcp : Anki sans bureau et addon AnkiMCP, HTTP MCP sur 3141,
connecte a la collection du serveur de sync.
mcp-lan-access republie sur l'adresse LAN de tower les endpoints
loopback (3141, 8710, 6060, 23373), firewall limite au Mac ; l'addon
Anki autorise ce Host (anti DNS-rebinding).
arr-mcp ne publie plus 6060 sur 0.0.0.0. Le module mcp-lan-access
est importe par services/default.nix avec anki-mcp, wise-mcp et
bank-mcp.
- gerbil-wg0-ensure.nix : supprime wg0 sans adresse avant le demarrage de gerbil, sinon plus aucun trafic ne passe dans le tunnel newt.
- vaultwarden-mcp commente : son secret vit dans secrets/tower.yml, illisible sur anywhere, ce qui cassait setupSecrets a chaque activation.
- zitadel/steps retire : le secret etait chiffre en type:int, refuse par sops-install-secrets ; l'instance est deja initialisee.
- secret oidc_env (secrets/winnie.yml, chiffre pour tower) materialise par le oneshot winnie-oidc-secret dans /var/lib/winnie/oidc.env, requis par winnie-backend (EnvironmentFile non optionnel)
- PKCE : pas de client_secret cote backend
- .sops.yaml : creation_rule pour secrets/winnie.yml
Ressource proxy-resources.sonarr -> 127.0.0.1:8006, healthcheck /ping, meme schema que radarr (proxy-resources + protocol, l'ancien schema que comprend le Pangolin du VPS). SSO actif et auth de l'appli coupee : sans SSO l'UI complete et l'API v3 seraient ouvertes a Internet.
- urlbase = "localhost" (recopie de l'exemple du module nixpkgs) faisait rediriger en 307 TOUT chemin hors base, /api/v3/... compris : les clients API locaux recevaient la page de login. Radarr, Sonarr et Readarr sont servis a la racine du domaine
- settings.auth.method = "External" : l'auth de l'appli ne filtre rien derriere le tunnel newt (les requetes distantes arrivent de 127.0.0.1, donc vues comme locales) et la porte reste le SSO Pangolin. Ni Radarr 6.x ni Sonarr 4.x n'ont d'OIDC natif (arrive dans Sonarr 5)
Politique du 13/10/2026 : le shell n'attend plus de validation avant d'executer. « off » est un bypass teste AVANT les branches cron / single_query / unattended, donc il coupe aussi les refus par defaut en contexte headless. Restent actifs malgre off : la blocklist hardline code-shippee et les regles approvals.deny (vides ici).
Le module melangeait l'acces (utilisateur hermes, cle SSH, sudo nixos-rebuild, python3 systeme pour le probe SSH de Desktop, git safe.directory) et le gateway lui-meme : couper le chat sur un hote ou il est inerte retirait aussi le moyen de s'y connecter et d'y deployer. config passe en lib.mkMerge : le bloc acces s'applique toujours, le gateway (postgres, MCP, claude-code unfree, service hermes-agent) seulement si enableAgent.
Module services.llm-steamdeck, desactive par defaut (enable a passer a true dans le profil deck, cf. README) : llama-server en Vulkan sur RADV, GGUF telecharge depuis Hugging Face au premier demarrage, web UI, limites memoire pour les 16 Go unifies. Importe par services/default.nix (donc inerte sur laptop/tower) et par profiles/deck.
- services/cage-waydroid : waydroid dans un cage HEADLESS, expose en VNC standard (wayvnc 5902, auth DES = le seul mode que macOS Screen Sharing negocie), plus un pont noVNC sur 127.0.0.1:6082
- root volontaire : waydroid-net.sh cree le bridge waydroid0 (CAP_NET_ADMIN) et la session waydroid est un SINGLETON global, c'est ce service qui la detient
- tower : virtualisation.waydroid.package = waydroid-nftables, le noyau n'a que nf_tables (le waydroid-net.sh standard sort en « iptables (legacy): can't initialize iptables table nat »)
- newt : ressource android.marcpartensky.com -> 127.0.0.1:6082 derriere le SSO Pangolin
- modules/nixos/kasmvnc-chrome-cdp.nix : lance un Chromium dans le desktop KasmVNC avec --remote-debugging-port=9222, profil persistant /home/marc/chrome-cdp-profile
- Le MCP Playwright s'y attache (--cdp-endpoint http://127.0.0.1:9222) : les actions d'automatisation se font dans CETTE fenetre, donc visibles en direct sur le bureau distant (8443 en LAN, kasm.marcpartensky.com)
- CDP reste sur 127.0.0.1 : un port CDP joignable donne le controle total du navigateur par quiconque l'atteint, donc jamais au LAN ni via Pangolin
- newt : healthcheck HTTP sur le vhost local 8288 (KasmVNC repond 200 sur "/" avec disableBasicAuth) ; sans lui la cible sortait du load-balancer
- winnie-frontend : build Vite (app/dist) servi par static-web-server sur 8888, aligne avec la ressource Pangolin winnie-frontend (un desaccord = 502, port en cache cote newt)
- newt : nouvelle ressource dioxus-frontend (dioxus.marcpartensky.com)
La chaine de secours hermes passait de inkling:free a nemotron (meme modele, pas de basculement vers un modele different). openrouter-mcp ecrit aussi le default_model nemotron dans sa config.toml.
Trois binaires dans le profil systeme, patron de services/amazon-mcp :
- hinge-auto-login chromium visible (telephone + SMS), session persistee dans /var/lib/hermes/hinge-auto/user-data
- hinge-auto-like boucle de likes headless sur Discover (HINGE_MAX_LIKES defaut 40, detection de fin de flux)
- hinge-auto-calibrate dump des boutons pour affiner les selecteurs
Profil partage hermes <-> marc via ACL tmpfiles. Garde-fous : limite ~8 likes/jour gratuite Hinge, arret sur marqueur de fin.
- Variables sans underscore (GOTIFY_OIDC_CLIENTID, ...) : les formes snake_case sont ignorees en silence par Gotify 3.x
- LISTENADDR 127.0.0.1 (exposition via newt/Pangolin uniquement), SSO Pangolin desactive : les clients natifs (appli Android, gotify-cli, scripts /message) ne suivent pas de redirection navigateur
- OIDC_LINK_BY_USERNAME + AUTOREGISTER : rattache l'identite Zitadel au compte local renomme
- after/wants newt + StartLimitIntervalSec=0 : le discovery OIDC 503 au boot si le tunnel n'est pas monte