# SSH/scp Task-Polling statt NFS-Freigabe (Sicherheits-Umbau 2026-08-03) ## Entscheidung NFS-Freigabe ist OBSOLET. Alle Sub-Agenten erhalten Tasks über **SSH/scp (Key-Auth, verschlüsselt)**. Grund: NFS (sec=sys) überträgt plaintext → Man-in-the-Middle-fähig. SSH = Ende-zu-Ende verschlüsselt, nur eigene Tasks sichtbar (gefiltert nach `*.C.D.*`), auditierbar. `/srv/work/tasks/` bleibt als **zentrale Ablage auf dem Master** (offen/in_arbeit/fertig/bugfix/archiv), nur die Zugriffsart änderte sich: kein Mount, sondern pull/push per scp. ## Warum NFS hier nicht überall geht SwitchHermes = Ubuntu 24.04 auf Nintendo-Switch (Tegra-Kernel `4.9.140-l4t`): - `modprobe nfs` → `FATAL: Module nfsv4 not found` - NFS-Client + FUSE/sshfs fehlen (kein Kernelmodul, wenig Speicher → kein Kernel-Ausbau gewollt) → SSH/scp ist die einzige portable Methode für ARM-Switch-Geräte. ## Architecture des SSH-Poller-Daemons - Jeder Agent hat **eigenen ed25519-Key** (auf Master: `/root/.ssh/_mesh`), Public-Key in `authorized_keys`. - Poller-Script `mesh-agent-poller.sh [master] [keyfile]`: 1. `scp` pull eigene Tasks `offen/...*.json` nach `/tmp/mesh-tasks/` 2. Atomarer Lock **per SSH auf dem Master**: `mv offen/... in_arbeit/...` (absolute Pfade!), `&& echo OK || echo LOST` 3. Bearbeitung via `hermes chat -q "" --quiet --max-turns 3` 4. Ergebnis als Base64 ins JSON-Feld `result` schreiben (SSH-python), dann `mv in_arbeit/ → fertig/`, chmod 0666 - **Daemon**: systemd-user-service (`~/.config/systemd/user/mesh-poller.service`, `Restart=always`, Loop alle 10s). NICHT nohup/Cron-@reboot als Loop — der stirbt beim SSH-Session-Ende. - Service-Start für User-Services: `export XDG_RUNTIME_DIR=/run/user/$(id -u); systemctl --user ...` ## Wichtigste Pitfalls (alle live getroffen) 1. **`SSH_CMD` MUSS den Host enthalten**: `SSH_CMD="ssh -i $KEY ... root@172.24.196.119"`. Vergessen → `hostname contains invalid characters` + leerer Lock-Output = endlose SKIPs, obwohl der manuelle `mv`-Test auf dem Master klappt. 2. **Relative Pfade im remote-mv vermeiden** — `../../../in_arbeit` geht 3 Ebenen hoch statt 1. Nutze absolute Pfade. 3. **Datei-Rechte (Hauptgrund, warum Polling anfangs nie lief):** vom Master (root) erstellte/durch `mv` bewegte Task-Dateien behalten root-Owner → Sub-Agent (uid 1000) kann nicht reinschreiben, selbst bei 0777-Dir. Fix: **`chmod 0666`** nach Task-Erstellung (`mesh-task-create.sh`) UND nach jedem `mv` im Poller (`in_arbeit` UND `fertig`). Sonst `PermissionError: [Errno 13]`. 4. **fail2ban bannt Mesh-IPs** bei scp-Fehlschlägen → `Connection refused` auf Port 22, obwohl Ping klappt und sshd auf `0.0.0.0:22` lauscht. Fix: `ignoreip = ` in `[sshd]`-Sektion von `/etc/fail2ban/jail.local`. Verifizieren: `fail2ban-client status sshd` → IP aus `Banned IP list` nehmen. 5. **Doppelte Daemon-Prozesse** (ein nohup-Rest + ein systemd-Rest) stören sich gegenseitig beim Lock. Nach Umbau: `systemctl --user stop`, `pkill -9 -f mesh-poller-daemon`, PID-Datei löschen, sauber `start`. Am Ende MUSS `pgrep -f mesh-poller-daemon` genau EINEN Prozess zeigen. 6. **"No such file or directory" beim remote-scp-Glob** kann heißen, dass der Task bereits in `fertig/` liegt (ein früherer Lauf hat ihn gezogen). Erst `ls` auf Master prüfen, bevor man den Glob verdächtigt. ## NFS-Server deaktivieren (nach Migration) - `systemctl stop/disable nfs-kernel-server rpcbind` - `ufw delete allow from to any port 2049/111/20048` für jede Agenten-IP - Offene Mesh-Ports danach NUR: 22 (SSH), 8644 (Webhook), 11434 (Ollama) — alle gerichtet auf die 4 bekannten ZT-IPs. - `/etc/exports` bleibt als Datei liegen, aber inaktiv. ## NFS-Export-Härtung (falls man doch NFS nutzen muss) Export auf die 4 bekannten IPs statt `/16`, zur defense-in-depth: UFW-Regel (nur bekannte IPs) UND Export (bekannte IPs) konsistent: `/srv/work/tasks ip1(rw,sync,no_subtree_check,no_root_squash) ip2(...) ...` `exportfs -ra` reload separat ausführen, NICHT als gateway-gebundener Befehl (wird gekillt).