156 lines
6.5 KiB
Bash
156 lines
6.5 KiB
Bash
#!/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 }')"
|
|
# Über den Helfer, nicht mit `awk '{ print $3 }'`: bei `default dev ens3 scope
|
|
# link` steht dort der KARTENNAME, und der landete als `gateway ens3` in der
|
|
# Strophe. Beides — Karte und Gateway — kommt aus derselben Route.
|
|
_gw="$(default_gateway)"
|
|
|
|
[ "$_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
|