Files
mesh-configs/skills/autonomous-ai-agents/mesh-file-task-queue/references/ssh-scp-task-polling-2026-08-03.md
T

4.0 KiB

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/<agent>_mesh), Public-Key in authorized_keys.
  • Poller-Script mesh-agent-poller.sh <meine-IP> <meine-ID> [master] [keyfile]:
    1. scp pull eigene Tasks offen/<JahrMonatTag>.<C>.<D>.*.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 "<PROMPT>" --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 = <alle ZT-IPs> 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 <ip> 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).