diff --git a/deploy/bootstrap/clupilot-bootstrap.sh b/deploy/bootstrap/clupilot-bootstrap.sh index a27caa9..2b4d3ea 100755 --- a/deploy/bootstrap/clupilot-bootstrap.sh +++ b/deploy/bootstrap/clupilot-bootstrap.sh @@ -689,6 +689,52 @@ section_network_bridged() { return 0 } +# --------------------------------------------------------------------------- +# Abschnitt 6: wireguard_joined — ab hier ist CluPilot erreichbar +# --------------------------------------------------------------------------- +# +# Der erste Abschnitt, nach dem es einen Rückweg gibt. Deshalb endet er mit +# `flush_reports`: alles, was seit dem Rettungssystem liegengeblieben ist, geht +# jetzt raus — mit den Zeitstempeln von damals. +section_wireguard_joined() { + export DEBIAN_FRONTEND=noninteractive + if ! apt-get install -y wireguard >> "${CLUPILOT_WORK_DIR}/apt.log" 2>&1; then + CLUPILOT_SECTION_NOTE='wireguard ließ sich nicht installieren' + return 1 + fi + + mkdir -p /etc/wireguard + chmod 700 /etc/wireguard + + _desired="$(render_wireguard_config)" + _current="$(cat /etc/wireguard/wg0.conf 2>/dev/null || true)" + + # Vor dem Schreiben vergleichen. Die Datei wurde früher bedingungslos neu + # geschrieben, ohne dass irgendetwas sie neu einlas — wer einen falschen + # Hub-Schlüssel korrigierte, sah die neue Datei und kein geändertes + # Verhalten, weil die laufende Schnittstelle den alten Peer behielt. + _changed=0 + if [ "$_desired" != "$_current" ]; then + _changed=1 + umask 077 + printf '%s\n' "$_desired" > /etc/wireguard/wg0.conf + chmod 600 /etc/wireguard/wg0.conf + fi + + if ! bring_up_wireguard "$_changed"; then + CLUPILOT_SECTION_NOTE='wg0 kam nicht in den Zustand, den die Übernahme braucht' + return 1 + fi + + if ! wireguard_handshake_proven 10; then + CLUPILOT_SECTION_NOTE="kein Handshake mit $(api_host) über den Tunnel. Die Datei steht, der Tunnel nicht — und eine geschriebene Datei ist kein Tunnel." + return 1 + fi + + CLUPILOT_SECTION_NOTE="Handshake mit $(api_host) bewiesen, wg0 systemd-freigegeben" + return 0 +} + main() { parse_arguments "$@" @@ -734,7 +780,13 @@ main() { die 'Abbruch bei der Netzumstellung.' fi - # run_section wireguard_joined section_wireguard_joined # Task 6 + if ! run_section wireguard_joined section_wireguard_joined; then + die 'Abbruch: ohne Tunnel gibt es keinen Rückweg zu CluPilot.' + fi + + # Ab hier existiert ein Weg. Die ganze Vorgeschichte geht jetzt raus. + flush_reports + # run_section traefik_running section_traefik_running # Task 7 # run_section template_built section_template_built # Task 8 # run_section registered section_registered # Task 9 diff --git a/deploy/bootstrap/lib/network.sh b/deploy/bootstrap/lib/network.sh index 1dde840..f7de3d2 100644 --- a/deploy/bootstrap/lib/network.sh +++ b/deploy/bootstrap/lib/network.sh @@ -253,6 +253,125 @@ EOF fi } +# --------------------------------------------------------------------------- +# WireGuard +# --------------------------------------------------------------------------- + +# Netzadresse zu einer Adresse mit Präfix: 10.66.0.11/24 -> 10.66.0.0/24 +# +# Gerechnet statt geraten. `AllowedIPs` mit der Wirtsadresse statt der +# Netzadresse ließe den Tunnel handshaken und danach jeden anderen Teilnehmer +# unerreichbar — ein Fehler, der aussieht wie ein Routingproblem irgendwo sonst. +ipv4_network() { + _ip="${1%%/*}" + _prefix="${1##*/}" + + IFS=. read -r _a _b _c _d <> 24) & 255 )) $(( (_net >> 16) & 255 )) \ + $(( (_net >> 8) & 255 )) $(( _net & 255 )) "$_prefix" +} + +# Die Tunneladresse von CluPilot — dieselbe, die `--api` trägt. +# +# Sie ist auch das Ziel für den Handshake-Beweis: wenn CluPilot über den Tunnel +# antwortet, steht der Tunnel. Ein eigener Parameter dafür wäre ein zweiter Wert, +# der dasselbe meint und irgendwann auseinanderläuft. +api_host() { + printf '%s' "$CLUPILOT_API" | sed -e 's#^[a-z]*://##' -e 's#[:/].*##' +} + +render_wireguard_config() { + _subnet="$(ipv4_network "$CLUPILOT_WG_IP")" + + cat </dev/null 2>&1 && _present=1 + + # `is-enabled` wird nach seinem Rückgabewert gefragt, nicht nach seinem + # Wortlaut: eine nie freigegebene Template-Unit („disabled", 1) und eine, die + # es noch nicht gibt, werden damit gleich behandelt. + if ! systemctl is-enabled --quiet "$_unit" 2>/dev/null; then + if ! systemctl enable "$_unit" >/dev/null 2>&1; then + log "konnte ${_unit} nicht freigeben — wg0 überlebte keinen Neustart" + return 1 + fi + fi + + if [ "$_present" -eq 0 ]; then + systemctl start "$_unit" >/dev/null 2>&1 || { log 'wg0 ließ sich nicht starten'; return 1; } + return 0 + fi + + # Vorhanden und unverändert: die laufende Schnittstelle passt schon zur + # Datei. Hier neu zu starten ist das, was einen funktionierenden Tunnel + # kaputt aussehen ließ. + [ "$_config_changed" -eq 0 ] && return 0 + + # Vorhanden mit geänderter Konfiguration: Neustart, NICHT `wg syncconf`. + # syncconf wendet nur Peers an; eine korrigierte `Address` oder `AllowedIPs` + # bliebe still wirkungslos — genau die Sorte „die Datei hat sich geändert und + # nichts ist passiert". + systemctl restart "$_unit" >/dev/null 2>&1 || { log 'Neustart von wg0 mit der korrigierten Konfiguration fehlgeschlagen'; return 1; } + return 0 +} + +# Der Tunnel gilt erst mit BEWIESENEM Handshake, nicht mit geschriebener Datei. +# +# Eine gespeicherte Tunneladresse ohne Beweis machte jeden weiteren +# Verbindungsversuch unbrauchbar, und die Rettung war Datenbank-Handarbeit. +wireguard_handshake_proven() { + _target="$(api_host)" + [ -n "$_target" ] || return 1 + + _tries="${1:-10}" + while [ "$_tries" -gt 0 ]; do + if ping -c1 -W2 "$_target" >/dev/null 2>&1; then + return 0 + fi + _tries=$((_tries - 1)) + sleep 3 + done + + return 1 +} + # --------------------------------------------------------------------------- # Die Datacenter-Firewall # --------------------------------------------------------------------------- diff --git a/docs/superpowers/plans/2026-07-30-host-uebernahme-bootstrap-skript.md b/docs/superpowers/plans/2026-07-30-host-uebernahme-bootstrap-skript.md index 3b52649..f3afcc5 100644 --- a/docs/superpowers/plans/2026-07-30-host-uebernahme-bootstrap-skript.md +++ b/docs/superpowers/plans/2026-07-30-host-uebernahme-bootstrap-skript.md @@ -347,7 +347,7 @@ der Kunden-VMs wirkungslos, und `ConfigureProxmox` hielt das als eigenen Fund fe ### Task 6: `wireguard_joined` — ab hier ist CluPilot erreichbar -- [ ] **Step 1: Schreiben** +- [x] **Step 1: Schreiben** `wg0` aus den mitgegebenen Werten, **systemd-aktiviert** — lies `ConfigureWireguard.php` dazu: ein `||`-Rückfall verdeckte dort die fehlende @@ -363,7 +363,7 @@ Sobald der Handshake steht: `flush_reports` — alles bis hierher wird nachgerei - [ ] **Step 2: Auf echter Hardware prüfen.** In der Konsole müssen jetzt **alle fünf** bisherigen Abschnitte auftauchen, mit ihren echten Zeitstempeln. -- [ ] **Step 3: Committen.** `Join the tunnel, then tell the console everything so far` +- [x] **Step 3: Committen.** `Join the tunnel, then tell the console everything so far` ---