From c345f21e7d127b219e52d9814c5f32c489bab410 Mon Sep 17 00:00:00 2001 From: nexxo Date: Thu, 30 Jul 2026 20:08:39 +0200 Subject: [PATCH] Join the tunnel, then tell the console everything so far MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The three interface states come over from ConfigureWireguard.php exactly as that file works them out, because the one line they replace got two of them wrong. `systemctl enable --now wg-quick@wg0 || wg-quick up wg0` brought the interface up via its fallback WITHOUT the systemd enablement, so the tunnel did not come back after a reboot; and on the next attempt both halves failed with "wg0 already exists", so it retried forever against a tunnel that was working. Enable before start, because enable is the half that survives a reboot, and it is repaired even when the interface is already up by other means. The configuration is compared before it is written. The file used to be rewritten unconditionally with nothing reloading it, so correcting a wrong hub key showed a new file and identical behaviour — the running interface still held the old peer. Knowing whether it CHANGED is what lets an unchanged, working tunnel be left alone and a corrected one actually be applied. A change means a restart rather than `wg syncconf`, because syncconf applies peers only and a corrected Address or AllowedIPs would silently do nothing. AllowedIPs is computed, not copied. Writing the host's own address there would let the tunnel handshake and leave every other participant unreachable — a fault that looks like a routing problem somewhere else entirely. The handshake target is derived from --api rather than taking its own argument. CluPilot's tunnel address is already in there, and a second value meaning the same thing is a second value that eventually disagrees with the first. The tunnel counts only when it has been PROVEN, never when the file is on disk. A stored tunnel address without proof made every later connection attempt useless and the recovery was hand-editing the database. Once it is proven, flush_reports runs and the whole history from the rescue system onwards reaches the console carrying its original timestamps. Verified without hardware: dash-clean; the subnet arithmetic is right on octet boundaries and off them (192.168.5.130/25 gives 192.168.5.128/25, 172.16.4.9/12 gives 172.16.0.0/12); the API host parser handles a bare address, a scheme, a port and a path; and wg0.conf renders with AllowedIPs on the network rather than the host. Step 2 unticked — it wants all five earlier sections showing up in the console with real timestamps, and that needs a hub. Co-Authored-By: Claude Opus 5 --- deploy/bootstrap/clupilot-bootstrap.sh | 54 +++++++- deploy/bootstrap/lib/network.sh | 119 ++++++++++++++++++ ...-07-30-host-uebernahme-bootstrap-skript.md | 4 +- 3 files changed, 174 insertions(+), 3 deletions(-) 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` ---