Files
bf-infra/docs/gotchas.md
Christoph Emsenhuber 52a469185b docs: CLAUDE.md ausgedünnt (61→28 KB) + Diagnose-Fallen nach gotchas.md
Strukturelle Hygiene (Handoff 2026-09-22): Pflichtlektüre auf 3 Pointer,
Kritische Gotchas auf Kern-Cluster komprimiert, ausführliche Diagnose-Blöcke
(SSDP-Relay, DNS-Hang, networking.service, NTP, HA-http, Nautobot-healthcheck …)
nach docs/gotchas.md verschoben. Tote ALLNET/Omada-Einträge entfernt,
"korrigiert"-Notizen bereinigt (nur die korrigierte Angabe behalten).

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-09-22 13:05:56 +02:00

10 KiB
Raw Blame History

Gotchas — ausführliche Diagnose-Anleitungen (Detail-Doku)

Diese Datei trägt die ausführlichen Diagnose-Blöcke, die in CLAUDE.md nur noch als 1-Zeilen-Merkregel mit Zeiger stehen. Volltext hier, damit der Kern-Index schlank bleibt. Alle Blöcke sind aktiv (querschnittliche Fallen), keine Historie.


SSDP-Relay für die macOS-Sonos-App

LXC 108 mcast-relay auf pve, zweibeinig 10.40.10.21 (VLAN10) + 10.40.40.21 (VLAN40), multicast-relay mit --noMDNS (mDNS macht der Core). Unit im Repo: mcast-relay/multicast-relay.service. Zwei Beine sind zwingend — SSDP ist link-lokal mit TTL 1, ein Relay muss Mitglied beider Broadcast-Domänen sein; Routing hilft nicht.

  • 🔴 --ssdpUnicastAddr gehört in ein zweibeiniges Relay NICHT — die Flagge hat den Hinweg 7 Wochen lang blockiert (drin 2026-08-18, ausgebaut 2026-08-25). Sie schreibt in multicast-relay.py Z. 630 die Quelle jedes SSDP-Pakets an 239.255.255.250:1900 auf <addr>:1901 um (auch NOTIFY); beim Senden greift dann Z. 691 if srcAddr == ssdpUnicastAddr and not onNetwork(srcAddr, tx['addr'], tx['netmask']): continue — 10.40.10.21 liegt nicht im VLAN40-Netz, also verwarf der Relay die M-SEARCH selbst, bevor sie die Player erreichte. Der Rückweg gehört stattdessen geroutet: forward TP5: MEDIA->MAIN ist offen, die Player antworten unicast an den echten Sucher. (Upstream nennt den Rückweg selbst # FIXME: record more than one? — es gibt nur einen globalen Merkslot.)
  • 🔎 Symptom-Merkregel: App sucht hunderte Male, sieht NOTIFYs, baut aber nie eine Verbindung auf = Relay, nicht Netz. Diagnose in einer Zeile — Richtungsbilanz im Journal: grep -c "via eth1" (VLAN10→VLAN40) gegen grep -c "via eth0". 0 zu n heißt: der Hinweg fehlt.
  • ⚠️ Aus dem Relay heraus lässt sich der Relay nicht testen (Loop-Schutz ignoriert eigene Quelladressen) — am echten Client testen oder journalctl -u multicast-relay mitlesen. Und am Client nicht auf Port 1900 messen: Sonos-App und Spotify halten dort je ein SO_REUSEPORT-Socket, die Unicast-Antwort bekommt nur eines davon. Richtig ist eine Probe mit ephemerem Quellport (bind((ifaddr, 0))), genau wie ein echter Client sucht.
  • 🔴 Zweite, unabhängige Ursache desselben Falls (08-25): die Sonos-App suchte gar nicht. Nach der Relay-Reparatur lag weiter nichts an — der Mitschnitt auf der VLAN40-Leitung (pct exec 108 -- tcpdump -i eth1 -n -s0 -A "udp and host <client>") zeigte: jedes SSDP-Paket des MacBooks kam von Chrome (ST: urn:dial-multiscreen-org:…dial:1, USER-AGENT: Chromium), kein einziges von Sonos. Die App hatte Sockets offen (*:1900, *:6969, TCP *:3400) und redete mit der Cloud, legte aber nichts auf die Leitung. Ursache war das Local-Network-Gate von macOS: es stand auf „an" und griff trotzdem nicht. Fix = Schalter unter Datenschutz & Sicherheit → Lokales Netzwerk aus, App beenden, wieder an, App starten (hart: sudo tccutil reset LocalNetwork com.sonos.macController2). ⇒ Merkregel: „die App sucht hunderte Male" ist erst dann ein Befund, wenn der Mitschnitt sagt, WER sucht. Im Handoff vom Vormittag waren die gezählten 291 Suchen Chrome + eigene Proben; die Zahl hat drei Stunden in die falsche Richtung gezeigt.
  • 🖥️ Das MacBook ist dual-homed in VLAN10: en5 = 10.40.10.183 (USB-Dock, Default-Route) und en0 = 10.40.10.82 (WLAN). Die früher als „zweiter Mac" gelesene .183 ist dasselbe Gerät — beim Messen immer beide Beine prüfen.

Konstanter 10-Sekunden-Hänger = DNS-Timeout, kein Lastproblem

(Fund 2026-09-01, TP16.3-Randbefund.) gitea hing bei ~10 % der Anfragen exakt 10,1 s — quer über Web-UI, /api/healthz und den Git-Pfad, also kein Rendering. Ursache: MariaDB (LXC 302) hatte skip-name-resolve auskommentiert und machte bei jeder neuen Verbindung einen Reverse-Lookup, während ihr /etc/resolv.conf noch auf den toten 10.40.41.254 zeigte (glibc: 5 s × 2 Versuche = 10 s). Pool-Verbindungen waren schnell ⇒ es wirkte sporadisch.

🔑 Drei Merkregeln daraus:

  • (a) immer gegen eine zweite Sache messen, die denselben Weg nimmt — vw/hass über dieselbe Strecke hatten 0 von 30 Hängern, das hat CF, Tunnel, Firewall und Caddy in einem Schritt entlastet.
  • (b) wer an einer DB hängt, sagt die Verbindungstabelle (ss -tn | grep 3306), nicht die Vermutung — hier hingen Vaultwarden, Home Assistant und gitea an derselben MariaDB, und die Annahme „Vaultwarden nutzt SQLite" war schlicht falsch.
  • (c) ein toter Resolver ist selten allein — der Sweep über alle LXCs fand fünf Container mit 10.40.41.254. Sweep: for c in $(pct list|awk 'NR>1{print $1}'); do pct exec $c -- grep ^nameserver /etc/resolv.conf; done

networking.service failed bei funktionierendem Netz = tote auto-Stanzas

(Ursache gefunden + behoben 2026-09-01.) In vier LXCs standen in /etc/network/interfaces noch Einträge aus dem alten VLAN1-Schema (eth0 → 10.40.41.x) und aus früheren Versuchen (veth16, veth24, Tippfehler eth01) — auf Interfaces, die es im Container gar nicht gibt. ifup -a scheitert daran, das echte veth20 kommt trotzdem hoch. Fix: nur die Stanzas entfernen, deren Interface in ip -br link fehlt (lo und die echten behalten), Backup daneben.

🔴 Abnahme ist ein echter Reboot, nicht systemctl is-active — der Servicestatus sagt nichts darüber, ob beim nächsten Start etwas hochkommt.


networking.service failed im LXC, ohne dass das Netz fehlt

networking.service kann in einem LXC dauerhaft failed sein, ohne dass das Netz fehlt — und dann startet alles mit After=network-online.target nach einem Reboot nicht (Fund 2026-09-01: 101, 102, 106, 107; influxdb2 kam nach dem Neustart nicht hoch, Unit enabled, kein Startversuch im Journal). Fällt nur auf, wenn man neu startet — die Container liefen 13+ Tage.

Vor jedem geplanten LXC-Neustart systemctl --failed lesen und danach die Ports gegen vorher vergleichen, sonst verliert man still einen Dienst.


Messwerkzeug, das die falsche Frage beantwortet

(zweimal in einer Nacht, 2026-09-01.)

  • (a) nc -z lieferte falsch-negative „Port zu" für neun Dienste gleichzeitig — inklusive des SSH-Ports, über den die Session gerade arbeitete; curl bekam von demselben Host HTTP 200 in 24 ms. Bei „alles gleichzeitig kaputt" zuerst das eigene Werkzeug verdächtigen, dann das Netz: ein Python-Socket oder curl entscheidet in Sekunden.
  • (b) Ein Firewall-Zähler taugt nicht als Wegbeweis, wenn der Client die Verbindung offenhält (cloudflared): er zählt nur NEUE Verbindungen und steht still, obwohl alles läuft — richtig ist die Verbindungstabelle (/ip firewall connection).

VLAN20-Docker-Hosts: Host-DNS nach dem Umzug prüfen (2026-07-10, TP14.2a)

NAS + pve-LXC zeigten /etc/resolv.conf noch auf totes .41.254 → docker pull ghcr scheitert (EAI_AGAIN, aber Egress ok!). Fix = .20.1: LXC pct set 400 --nameserver 10.40.20.1; Synology-Falle: DSM-„Allgemein"-DNS ≠ was dockerd liest — Interface zieht DNS per DHCP → Fix an der Netzwerkschnittstelle „DNS manuell". Laufende Container frieren den 127.0.0.11-Upstream zum Erstellzeitpunkt ein (Recreate nötig). Merkregel: docker-pull-hängt + dns.lookup EAI_AGAIN aber TCP zu ghcr-IP:443 ok = toter Resolver, nicht Egress.


Zeitquelle ist der Core (NTP)

/system ntp server, seit 2026-08-18, ausgeliefert per DHCP-Option 42 (ntp-server= in jedem der 7 Netze) und drift-überwacht (ComplianceRule system-ntp). Vorher lief nirgends eine Synchronisation: die anderen drei Geräte zeigten auf 10.40.99.1, wo kein Server lief, und der Core selbst hatte gar keinen NTP-Client — ap2 war dadurch 17 Stunden versetzt.

🔴 Zwei Fallen dabei:

  • (a) ein NTP-Server mit use-local-clock=no und unsynchronisiertem Client liefert Stratum 16, das jeder Client verwirft — Server einschalten allein reicht nicht.
  • (b) wird der Server eingeschaltet, bevor sein Client synchron ist, hält er diesen Zustand fest → enabled=no/=yes nach der Synchronisation.

Merkregel: bei widersprüchlichen Zeitstempeln zwischen Geräten zuerst /system ntp client get status lesen, und zum Eingrenzen einen Client testweise auf eine öffentliche Quelle zeigen lassen.


Home Assistant: der http:-Block in configuration.yaml ist WIRKUNGSLOS

(Fund 2026-08-26, TP31.) HA hat die http-Config nach .storage/http migriert ("yaml_migration_done": true); seither wird die YAML kommentarlos ignoriert — selbst ein ungueltiger Schluessel darin erzeugt keinen Config-Fehler.

🔴 Die Logzeile fuehrt aktiv in die Irre: Received X-Forwarded-For header from an untrusted proxy <ip> faellt laut Quelltext nur bei aktivem use_x_forwarded_for — man haelt die Option fuer wirksam und die Liste fuer falsch und editiert die falsche Datei. Richtig ist der WS-Zweischritt http/config/configure (setzt pending, startet HA neu) → binnen 5 Minuten http/config/promote, sonst Auto-Revert auf stable.

Merkregel zur Eingrenzung: ein Testskript in <config>/python_scripts/ + python_script.reload — taucht der Service auf, liest HA das Verzeichnis, und die Frage ist nicht mehr welche Datei, sondern warum ein gelesener Block nicht wirkt.


Nautobot auf der DS923 braucht einen abgeschalteten Image-Healthcheck

Der eingebackene Check ruft alle 10 s nautobot-server health_check = einen vollen Django-Start; unter Last dauert der >2 min, die Aufrufe stapeln sich unbegrenzt → Speicher alle → fork() mit ENOMEM → DSM räumt den ganzen Stack ab (am 2026-08-17 zweimal passiert, Load bis 73 bei 4 Kernen). Im Stack stehen deshalb healthcheck: disable: true auf worker+scheduler, --concurrency=2 und mem_limit. cpus: geht auf Synology nicht (Kernel ohne CFS-Quota, der Stack-Deploy schlägt sonst fehl).