Zwei Befunde, beide echt, und der erste ist ein Patzer:
detect_network_style wurde auf default_gateway() umgestellt,
bridge-run.sh blieb auf awk '{ print }'. Der Treiber reichte also
weiter den KARTENNAMEN als Gateway an build_bridge, und in der Strophe
stand 'gateway ens3' — der Fix half genau der Stelle nicht, fuer die er
gedacht war.
Meine Sandkiste konnte das nicht sehen: sie hatte immer ein via. Jetzt
ist sie parametrisiert, und ein Test faehrt bridge-run.sh mit einer
via-losen Standardroute durch und liest die geschriebene Strophe. Der
Beweis laeuft ueber den Produktivpfad, nicht ueber eine Einzelfunktion.
Zweitens: default_gateway suchte das erste via IRGENDWO in der Ausgabe,
detect_primary_interface das dev der ERSTEN Zeile. Bei zwei
Standardrouten baute das eine Bruecke ueber die Karte der einen mit dem
Gateway der anderen. Beide haengen jetzt an primary_default_route mit
head -1 — eine Route, eine Quelle.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Die Ruecknahme konnte Erfolg melden, ohne einen zu haben. Drei Wege
dorthin, alle behoben:
- Sie schaltete den entmachteten Netzverwalter nicht wieder ein. Die
Sicherung umfasst nur /etc/network/interfaces*; kam die Verbindung von
cloud-init, networkd oder NetworkManager, spielte die Ruecknahme eine
Datei zurueck, die die Maschine nie getragen hat, und liess den
Verwalter abgeschaltet. Genau der tote Host, den der Zeitgeber
verhindern soll. disown_network_manager hinterlaesst jetzt eine Notiz
(WAS entmachtet, WELCHE Strophen verdraengt), die das Ruecknahme-Skript
beim Feuern liest — aufgeschrieben statt eingebacken, weil der
Zeitgeber vor dem Entmachten gestellt wird.
- Sie setzte 'rolled-back' auch, wenn tar oder ifreload scheiterten. Das
urspruengliche network.sh hatte dafuer set -e; beim Umbau ist es
verlorengegangen. Jetzt bricht jeder Fehlschlag ab, bevor die Marke
entsteht — CluPilot pollt dann bis zur Frist statt 'ist zurueck' zu
glauben.
- Eine verdraengte interfaces.d-Strophe wurde nur umbenannt. Der Stern in
'source interfaces.d/*' fasst sie weiter; das versteckte die Kollision
vor dem Leser, statt sie zu loesen. Jetzt wandert sie aus dem
Verzeichnis heraus, und die Ruecknahme holt sie zurueck.
Dazu ein eigener Fund: das Gateway wurde mit awk '{print }' gelesen.
Bei 'default dev ens3 scope link' ist das der KARTENNAME, woraus
'gateway ens3' in der Strophe wuerde. default_gateway() liest jetzt
hinter dem via, und eine Standardroute ohne via wandert als eigene
up-Zeile mit, statt verlorenzugehen.
Zurueckgewiesen: der P2 zu extra_routes. 'ip route show dev X' laesst das
dev-Feld WEG (im Container nachgemessen), das angehaengte 'dev vmbr0' ist
also richtig.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Verdrahtet bridge.sh in der einen Reihenfolge, die stimmen muss:
sichern -> Zeitgeber -> uebernehmen -> umstellen -> nachsehen. Alles
davor stellt nur fest und veraendert nichts; ab 'sichern' gibt es einen
Weg zurueck, und erst ab dann darf ueberhaupt etwas angefasst werden.
Abgekoppelt ist hier Bedingung, nicht Optimierung: ifreload -a nimmt die
Leitung, ueber die der Befehl laeuft. PID als allererstes, damit ein
frueher Poll nicht 'running aber nicht lebendig' liest und einen gesunden
Lauf fuer tot erklaert.
Der Treiber bestellt den Zeitgeber NIE ab — das tut CluPilot nach dem
Wiederverbinden. Ein Test haelt das fest.
Dazu die Fremdverwalter-Erkennung in bridge.sh: cloud-init, networkd,
NetworkManager werden benannt und entmachtet, Unbekanntes fuehrt zum
Abbruch statt zu einem Versuch ins Blaue. Der Zeitgeber faengt diesen
Fall NICHT ab — zu seiner Zeit war alles in Ordnung, und die Bruecke
verschwaende erst beim naechsten Neustart, mit Kunden darauf.
Drei Tests fahren den Treiber in einer Sandkiste WIRKLICH durch, statt
nur seinen Text zu lesen: die ip-Attrappe antwortet vor dem ifreload
anders als danach. Sie belegen ok, failed-ohne-Bruecke und
failed-mit-schalem-Tunnel — und in allen drei Faellen, dass die
Rueckfahrkarte stehen bleibt.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Nur 'komme ich raus' zu pruefen ist notwendig und NICHT hinreichend.
Der Fehlerfall: ifreload bringt vmbr0 sauber hoch, die Maschine erreicht
das Internet, der Treiber waere zufrieden — aber wg0 kommt nicht zurueck.
Dann lebt der Host oeffentlich, CluPilot ist ausgesperrt, und wer hier
abbestellt, hat die Rueckfahrkarte weggeworfen.
wg0.conf enthaelt keine Geraetebindung; der Tunnel haengt an der
Quelladresse, die die Routing-Tabelle hergibt — und genau das ist die
Groesse, die der Umbau anfasst. Das macht den Fall nicht
unwahrscheinlicher, nur unauffaelliger: kein Fehler im Protokoll, nur
ein Handshake, der ausbleibt.
Ist der Handshake schal, wird wg-quick@wg0 EINMAL neu gestartet und
nochmal nachgesehen. Gefahrlos, weil ifreload die SSH-Sitzung ohnehin
schon mitgenommen hat — und es verwandelt einen haengenden Tunnel in
einen laufenden statt in eine Ruecknahme.
Kein ping: Hetzners Debian-Basis hat keins, PrepareBaseSystem
installiert es nicht, und network.sh:190 haette damit immer 'nicht
erreichbar' gesagt. Ein Test haelt bridge.sh ping-frei.
Nebenbei ein Fehler in den Tests selbst behoben: 'if gibtsnicht; then
… else echo NEIN; fi' ist in sh unwahr, also war jeder Test, der NEIN
erwartete, gruen SOLANGE die Funktion fehlte. verdictBody() meldet jetzt
FEHLT und trennt 'falsch' von 'gibt es nicht'.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Der Zeitgeber ist eine systemd-Einheit und kein 'sleep &': ein
Hintergrundlauf stirbt mit seiner Sitzung, und die Sitzung ist genau
das, was abreisst, wenn die Umstellung schiefgeht.
Zwei Dinge gegenueber der Fassung, die in network.sh stand:
- render_rollback_script ist vom Stellen getrennt, damit die Reihenfolge
darin ohne systemd pruefbar ist. Und die Reihenfolge ist der Punkt:
'failed' samt Grund wird geschrieben, BEVOR zurueckgespielt wird —
ein halb gegluecktes Zurueckspielen soll das Urteil trotzdem
hinterlassen. 'rolled-back' kommt zuletzt und ist das Signal, auf das
CluPilot wartet.
- Der Zeitgeber raeumt seine Unit-Dateien nach dem Feuern selbst weg.
Sonst sieht eine abgeschlossene Ruecknahme beim naechsten Hinsehen aus
wie eine ausstehende.
Der Treiber bestellt NICHT ab. Das tut CluPilot, nachdem es sich ueber
den Tunnel neu verbunden hat — ein Skript auf dem Host kann ueber seine
eigene Erreichbarkeit von aussen nur raten.
Geprueft: der Zeitgeber steht vor dem ersten veraendernden Aufruf.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
write_bridge_stanza ist von build_bridge getrennt: das Schreiben ist das,
was eine Maschine umbringt, und so ist es pruefbar, ohne dafuer ein Netz
neu laden zu muessen. Beide Pfade sind ueberschreibbar
(CLUPILOT_INTERFACES_FILE, CLUPILOT_IFRELOAD), dieselbe Technik wie
CLUPILOT_STORAGE_CFG beim Vorlagenbau.
Drei Dinge kann die Fassung mehr als die alte in network.sh:
- hwaddress festgenagelt. Eine Bruecke waehlt sonst die kleinste MAC
ihrer Ports; bei einem Port ist das dieselbe, aber 'ist dieselbe' und
'bleibt dieselbe' sind zweierlei.
- Zusatzrouten des Anbieters wandern mit. Ausgelassen bleiben die
Kernel-Route zum eigenen Subnetz und die Link-Route zum Gateway —
beide entstehen von selbst, und ein gescheitertes 'up' nimmt bei
ifreload die ganze Strophe mit.
- IPv6, aber nur statisch und global. SLAAC/DHCPv6 werden bewusst nicht
nachgebaut (forwarding=1 laesst den Kernel RAs ohne accept_ra=2
verwerfen) — dafuer gibt es eine Zeile ins Protokoll statt eines
stillen Verlusts.
Geprueft gegen echte sh: Hetzner /32 routed mit pointopoint, netcup
Subnetz ohne, DHCP ohne Adresse, statisches IPv6 mit fe80::1, SLAAC
faellt weg, Zusatzrouten kommen mit.
network.sh hat sein eigenes build_bridge abgegeben; ein Test haelt jetzt
jede der sieben Brueckenfunktionen auf genau einer Stelle im Repo fest.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Die Bruecken-Erkennung stand in network.sh, geschrieben fuer den
stillgelegten Rettungssystem-Weg, und borgte sich vier Dinge von
woanders: detect_primary_interface aus proxmox.sh, log, http_get und
CLUPILOT_PROBE_URL aus clupilot-bootstrap.sh.
Der Debian-Weg laedt die Bibliothek EINZELN auf den Host und faehrt sie
dort — geborgte Helfer waeren dann nicht da. Also allein lauffaehig, mit
log und http_get unter einem command-v-Schutz, damit der Bootstrap seine
eigenen behaelt.
Neu und aus dem laufenden Zustand abgeleitet:
- interface_is_physical — bridge_ports auf einem Bond oder einer
bestehenden Bridge ist falsch, und aus der Ferne nicht reparierbar.
- address_is_dynamic — der Kernel markiert eine geleaste Adresse, das
steht bei jedem Anbieter gleich da. Die Datei des Anbieters ist nur
noch das Zweitsignal.
Geprueft gegen eine echte sh mit aufgezeichneten ip-Ausgaben, plus die
Zusicherung, dass es detect_primary_interface im Repo genau einmal gibt.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>