#!/bin/sh # shellcheck shell=sh # # Treiber für den Brückenbau, wenn er aus der CluPilot-Pipeline kommt. # # `App\Provisioning\Steps\Host\EnsureNetworkBridge` lädt diese Datei zusammen mit # `bridge.sh` auf den Host und startet sie abgekoppelt. Die eigentliche Arbeit # macht `bridge.sh` — hier steht nur, in welcher Reihenfolge, und wie der # Fortschritt zurückgemeldet wird. # # --------------------------------------------------------------------------- # Warum abgekoppelt # --------------------------------------------------------------------------- # # Nicht wegen der Dauer — der Bau ist in Sekunden durch. Sondern weil die # SSH-Verbindung MITTEN im Befehl stirbt: `ifreload -a` nimmt die Leitung, über # die der Befehl läuft. Ein synchroner Aufruf hätte keinen Rückgabewert, sondern # eine Leiche. # # --------------------------------------------------------------------------- # Die Reihenfolge, und warum sie nicht verhandelbar ist # --------------------------------------------------------------------------- # # sichern → Zeitgeber → übernehmen → umstellen → nachsehen. # # Der Zeitgeber steht VOR jeder Änderung. Er ist der einzige Grund, warum dieses # Skript die Netzkonfiguration überhaupt anfassen darf: kommt der Host nicht # zurück, spielt er den alten Zustand ein, und der Schritt landet in einer # Wiederholung statt auf einem toten Server. # # Abbestellt wird er hier NICHT. Das tut CluPilot, nachdem es sich über den # Tunnel neu verbunden und nachgesehen hat. Dieses Skript kann über seine eigene # Erreichbarkeit von außen nur raten. # # --------------------------------------------------------------------------- # Rückmeldung # --------------------------------------------------------------------------- # # state running | ok | failed # pid die PID dieses Skripts — und, weil es per `setsid` gestartet # wird, zugleich die seiner PROZESSGRUPPE # phase die laufende Phase, für die Fortschrittszeile in der Konsole # note der Grund im Fehlerfall # rolled-back vom Zeitgeber angelegt, wenn er zurückgespielt hat # bridge.log alles, samt der geschriebenen Strophe # # `state` allein ist keine Aussage über den Lauf: stirbt das Skript, bleibt dort # für immer `running` stehen, weil niemand mehr da ist, der es ändert. Deshalb # fragt der Schritt zusätzlich `kill -0` gegen `pid`. set -u CLUPILOT_WORK_DIR="${CLUPILOT_WORK_DIR:-/var/lib/clupilot/bridge}" # Als ALLERERSTES, vor jedem Einlesen und jeder Prüfung: der Startbefehl wartet # darauf, dass hier etwas steht, und bis dahin gilt der Lauf als noch nicht # angelaufen. `$$` und nicht `$!` auf der anderen Seite, weil `setsid` dazwischen # liegt — und weil `setsid` daraus eine eigene Sitzung macht, ist diese Zahl # zugleich die Prozessgruppe, an die ein Abbruch geschickt wird. echo $$ > "${CLUPILOT_WORK_DIR}/pid" # CLUPILOT_WG_HUB_PUBKEY und alles andere Veränderliche schreibt der Schritt. # shellcheck source=/dev/null . "${CLUPILOT_WORK_DIR}/env" # shellcheck source=lib/bridge.sh . "${CLUPILOT_WORK_DIR}/bridge.sh" phase() { printf '%s' "$1" > "${CLUPILOT_WORK_DIR}/phase" log "--- $1" } fail() { printf '%s' "$1" > "${CLUPILOT_WORK_DIR}/note" printf 'failed' > "${CLUPILOT_WORK_DIR}/state" log "ABBRUCH: $1" exit 1 } # Ein Abbruch, den keine Zeile hier abgefangen hat (kein Speicher, ein Signal), # darf nicht als `running` liegen bleiben und den Schritt gegen eine Leiche # pollen lassen. Der Schritt erkennt das zwar auch an `kill -0`, aber ein Grund # ist besser als eine Vermutung. on_exit() { _code=$? if [ "$_code" -ne 0 ] && [ "$(cat "${CLUPILOT_WORK_DIR}/state" 2>/dev/null)" = 'running' ]; then printf 'Der Brueckenbau brach unerwartet ab (Rueckgabewert %s); siehe bridge.log' "$_code" \ > "${CLUPILOT_WORK_DIR}/note" printf 'failed' > "${CLUPILOT_WORK_DIR}/state" fi } trap on_exit EXIT # --------------------------------------------------------------------------- # Feststellen — noch wird nichts verändert # --------------------------------------------------------------------------- cd "$CLUPILOT_WORK_DIR" || exit 1 phase 'Zustand feststellen' _iface="$(detect_primary_interface)" [ -n "$_iface" ] \ || fail 'Keine Karte traegt die Standardroute — auf dieser Maschine ist nicht abzuleiten, worueber eine Bruecke gehen soll.' interface_is_physical "$_iface" \ || fail "Die Karte mit der Standardroute (${_iface}) ist keine physische Karte, sondern eine Bruecke, ein Bond oder ein VLAN. bridge_ports darauf waere falsch." _style="$(detect_network_style "$_iface")" _cidr="$("$CLUPILOT_IP" -4 -o addr show dev "$_iface" scope global 2>/dev/null | awk '{ print $4; exit }')" _gw="$("$CLUPILOT_IP" -4 route show default 2>/dev/null | awk '{ print $3; exit }')" [ "$_style" = 'dhcp' ] || [ -n "$_cidr" ] \ || fail "Keine globale IPv4-Adresse auf ${_iface} — nichts, was auf eine Bruecke ziehen koennte." log "Karte ${_iface}, Form ${_style}, Adresse ${_cidr:-per DHCP}, Gateway ${_gw:-keins}" # Unbekanntes wird nicht ins Blaue gebaut. Ein Verwalter, den dieses Skript nicht # entmachten kann, nimmt die Bruecke beim naechsten Neustart wieder weg — und das # faellt erst auf, wenn Kunden darauf liegen. _foreign="$(foreign_network_manager)" case "$_foreign" in ''|cloud-init|networkd|network-manager) ;; *) fail "Unbekannter Netzverwalter (${_foreign}) — hier wird nicht ins Blaue gebaut." ;; esac [ -n "$_foreign" ] && log "Fremdverwalter erkannt: ${_foreign}" # --------------------------------------------------------------------------- # Ab hier wird verändert # --------------------------------------------------------------------------- phase 'sichern' backup_network_config \ || fail 'Die bestehende Netzkonfiguration liess sich nicht sichern — ohne Rueckfahrkarte wird hier nichts umgestellt.' phase 'Zeitgeber stellen' schedule_network_rollback "${CLUPILOT_ROLLBACK_MINUTES:-5}" phase 'uebernehmen' disown_network_manager "$_foreign" "$_iface" phase 'umstellen' build_bridge "$_iface" "$_style" "$_cidr" "$_gw" phase 'nachsehen' bridge_proven \ || fail 'Die Bruecke steht, aber der Weg nach draussen oder der Tunnel fehlt. Der Zeitgeber spielt den alten Zustand zurueck.' phase 'wartet auf CluPilot' printf 'ok' > "${CLUPILOT_WORK_DIR}/state" log 'Bruecke steht und traegt; der Zeitgeber laeuft weiter, bis CluPilot ihn abbestellt.' exit 0