# Runbook: Wenn der Tunnel weg ist Der WireGuard-Tunnel trägt alles: die Konsole, jeden Host, jede Provisionierung, das Terminal. Fällt er aus, sieht es aus, als sei das ganze Produkt kaputt — obwohl meistens nur eine Sitzung nicht wieder aufgebaut wurde. Dieses Runbook beantwortet zwei Fragen. Erstens: **womit komme ich noch hinein, wenn der Tunnel nicht steht?** Zweitens: **wie bringe ich ihn zurück?** --- ## Die eine Regel **Kein Weg zurück darf durch den Tunnel führen, der gerade kaputt ist.** Alles unten ist danach sortiert. Wer als Rettungsweg etwas einplant, das selbst WireGuard braucht, hat keinen Rettungsweg eingeplant. --- ## Die vier Wege hinein, in dieser Reihenfolge ### 1. SSH auf den CluPilot-Server Der Hebel für fast alles. Er hängt an nichts, was hier beschrieben wird — der Server ist über seine öffentliche Adresse erreichbar, unabhängig vom Tunnel. ```bash ssh @ cd /opt/clupilot ``` Von hier aus laufen alle Befehle weiter unten. ### 2. Die Konsole des Anbieters (Hetzner: KVM oder Rettungssystem) Für einen **Host**, nicht für den CluPilot-Server. Immer verfügbar, hängt an keinem Dienst von uns. Der Weg, wenn ein Host weder über den Tunnel noch über SSH antwortet. ### 3. Das Notfallskript auf dem Host Liegt auf jedem übernommenen Host: ``` /usr/local/sbin/clupilot-emergency-open-firewall.sh ``` `SecureHostFirewall` lässt Port 22 nur aus dem WireGuard-Netz zu. Steht der Tunnel nicht, ist SSH aus dem Internet **zu** — das ist Absicht und genau der Grund, warum es dieses Skript gibt. Aufgerufen wird es über die Konsole des Anbieters (Weg 2), nicht per SSH; per SSH käme man ja gerade nicht hin. Danach ist der Host wieder aus dem Internet erreichbar. **Nach der Reparatur den Schritt `SecureHostFirewall` erneut laufen lassen**, sonst bleibt die Maschine offen. ### 4. Eine WireGuard-Konfiguration, die offline liegt In der Zugangsliste der Konsole gibt es zu jedem Mitarbeiter-Zugang einen Download. **Eine davon gehört außerhalb der Konsole aufbewahrt** — Passwort-Safe, zweites Gerät, ausgedruckt. Sonst braucht man die Konsole, um an den Zugang zu kommen, der einen zur Konsole bringt. --- ## Zuerst: ein Befehl, der das meiste allein macht ```bash cd /opt/clupilot && sudo -u clupilot bash deploy/rescue-tunnel.sh ``` Er stellt der Reihe nach fest, wo es klemmt, startet und zieht hoch, was fehlt, räumt die gemerkten Ströme weg — und sagt am Ende, was noch von Hand bleibt. Er fasst den Tunnel-Container **nicht** neu an; genau das wäre die Ursache und nicht die Lösung. Was er tut, steht unten einzeln, falls man es von Hand nachvollziehen will. --- ## Von Hand feststellen, wo es klemmt Vom CluPilot-Server aus, in dieser Reihenfolge. Jede Zeile schließt eine Möglichkeit aus. ```bash cd /opt/clupilot # 1. Steht wg0 überhaupt, und wie viele Zugänge kennt es? sudo -u clupilot docker compose exec -T vpn-hub wg show # 2. Läuft der Container, dem der Namensraum mit wg0 gehört? sudo -u clupilot docker compose ps vpn-hub # 3. Was sagt er beim Starten? sudo -u clupilot docker compose logs --tail=40 vpn-hub ``` **Kein Interface, kein `wg0`** → weiter bei „Der Tunnel kommt nicht hoch". **Interface da, Zugänge da, aber `Letzter Kontakt: nie` oder uralt, und die Zahlen stehen bei ↑ etwas / ↓ 0 B** → weiter bei „Der Tunnel steht, aber niemand kommt an". **Ein einzelner Zugang fehlt, die anderen stehen** → das Problem liegt auf dessen Gegenseite, nicht hier. Bei einem Host: Weg 2 und `systemctl status wg-quick@wg0` auf der Maschine. --- ## Der Tunnel steht, aber niemand kommt an Das häufigste Bild, und das mit der unauffälligsten Ursache. Der Kernel merkt sich laufende UDP-Ströme samt Ziel. Wird der Tunnel-Container **neu gebaut** (nicht neu gestartet — neu gebaut), bekommt er eine neue Adresse im Compose-Netz, und die Weiterleitung für den WireGuard-Port wird neu geschrieben. Die gemerkten Einträge zeigen aber weiter auf den alten Container. Und sie verfallen nicht: WireGuard schickt alle 25 Sekunden ein Lebenszeichen und hält den kaputten Eintrag damit am Leben. Der Zustand heilt also **nie** von allein. Daran erkennt man ihn: * Ein Telefon kommt nach Aus- und Einschalten sofort zurück — ein neuer Quellport bekommt einen frischen Eintrag. * Ein **Host** kommt gar nicht zurück — er schickt stur vom selben Port. * Auf der Zugangsseite: ↑ wächst weiter, ↓ bleibt bei 0 B. Der Griff, auf dem **Wirt**, nicht im Container: ```bash sudo conntrack -D -p udp --dport 51820 ``` Fehlt das Werkzeug: `sudo apt-get install -y conntrack`. Danach dreißig Sekunden warten — jeder Zugang meldet sich von selbst wieder, alle 25 Sekunden läuft sein Keepalive. > **Seit v1.4.9 kann das gar nicht mehr passieren.** Der Hub hat eine feste > Adresse im Compose-Netz (`172.18.0.240`, über `CLUPILOT_VPN_HUB_IP` > änderbar). Nach einem Neubau entsteht damit exakt dieselbe Weiterleitung, und > die gemerkten Ströme bleiben gültig. Was unten steht, gilt für Server, die > älter sind — und als Rückfahrkarte, falls jemand das Subnetz ändert. > > **Seit v1.4.6 macht das Update das Aufräumen ohnehin selbst.** Es merkt sich die Container-ID > vor und nach `docker compose up -d`; hat sie sich geändert, räumt es die > Ströme weg. Kann es das nicht (es läuft als Dienstbenutzer, der Griff braucht > Root), steht der Befehl als Warnung im Protokoll. Siehe „Einmal einrichten" > unten. --- ## Der Tunnel kommt nicht hoch `wg show` zeigt gar kein Interface. ```bash cd /opt/clupilot # Von Hand hochziehen — sagt auch gleich, woran es scheitert. sudo -u clupilot docker compose exec -T vpn-hub wg-quick up wg0 # Steht die Konfiguration überhaupt noch im Datenträger? sudo -u clupilot docker compose exec -T vpn-hub ls -l /etc/wireguard/ ``` Die Zugänge stehen **in `wg0.conf`** und überleben einen Neustart. Fehlt die Datei, ist der `wireguard`-Datenträger das Problem, nicht WireGuard. Läuft der Container gar nicht: ```bash sudo -u clupilot docker compose up -d vpn-hub sudo -u clupilot docker compose logs --tail=40 vpn-hub ``` Prozess 1 ist `/usr/local/bin/vpn-hub`. Das Skript zieht wg0 hoch und tut danach nichts mehr — genau das ist die Aufgabe. Steht dort etwas anderes, läuft der Server auf einem Stand vor v1.4.8, und der Tunnel hängt noch am Provisionierungs-Arbeiter. --- ## Ein Host kommt nicht zurück, alle anderen schon Dann liegt es auf der Maschine. Über die Konsole des Anbieters (Weg 2): ```bash systemctl status wg-quick@wg0 wg show systemctl restart wg-quick@wg0 ``` `ConfigureWireguard` schreibt dem Host `Endpoint = ` und `PersistentKeepalive = 25` — er meldet sich also von sich aus, alle 25 Sekunden. Tut er das nicht, läuft wg0 dort nicht. Kommt man über die Konsole nicht weit genug, weil SSH zu ist: erst Weg 3, dann weiter. --- ## Einmal einrichten, damit der Plan später trägt Vier Dinge, die man vorher macht und nicht, wenn es brennt. 1. **Eine WireGuard-Konfiguration offline ablegen.** Siehe Weg 4. 2. **Zugang zur Anbieterkonsole prüfen** — einmal einloggen, bevor man es braucht. Ein vergessenes Passwort ist im Ernstfall ein zweiter Ausfall. 3. **Dem Dienstbenutzer den conntrack-Griff erlauben**, damit das Update ihn selbst ausführen kann. In `/etc/sudoers.d/clupilot-conntrack`: ``` clupilot ALL=(root) NOPASSWD: /usr/sbin/conntrack -D -p udp --dport 51820 ``` Genau dieser eine Aufruf, nicht `conntrack` allgemein. 4. **`conntrack` installieren**, falls nicht vorhanden: `sudo apt-get install -y conntrack`. --- ## Was inzwischen NICHT mehr passieren sollte Fünf Ursachen sind abgestellt. Wer sie trotzdem sieht, hat einen Server auf altem Stand. * **Bis v1.4.3** hing wg0 am Leben des Warteschlangen-Arbeiters: Prozess 1 des Containers war `php artisan queue:work`. Ein Zeitüberlauf, ein fataler Fehler oder ein Speicherlimit beendete den Arbeiter, damit den Container, damit den Tunnel. Das las sich von außen als „das VPN ist unzuverlässig". Seit v1.4.4 läuft der Arbeiter in einer Schleife neben dem Tunnel. * **v1.4.2** gab dem Tunnel-Container einen Netz-Alias. Ein Alias ist Teil der Netzkonfiguration, und Compose baut einen Container neu, sobald die sich ändert — mit allem, was oben unter „gemerkte Ströme" steht. Seit v1.4.4 ist der Alias weg; nginx spricht die Terminal-Brücke unter dem Dienstnamen des Nachbarn an. * **Bis v1.4.5** startete jedes Update `queue-provisioning` neu und riss damit jede Sitzung ab, auch wenn sich nur PHP-Code geändert hatte. Seit v1.4.6 bekommt der Arbeiter ein Signal und wird von seiner Schleife neu gestartet, während der Container steht. Dasselbe gilt seither für den Knopf „Dienste neu starten" in der Konsole — der lief über denselben Befehl. * **Bis v1.4.7** wohnte wg0 im Container des Provisionierungs-Arbeiters, also im Abbild `clupilot-app`. Das wird bei fast jeder Freigabe neu gebaut, und ein neu gebauter Container bekommt eine neue Adresse — mit allem, was oben unter „gemerkte Ströme" steht. Seit **v1.4.8** gehört der Namensraum einem eigenen Container `vpn-hub` mit eigenem, selten wechselndem Abbild. Nachgemessen: App-Abbild neu gebaut, Arbeiter und Brücke neu erzeugt — der Hub blieb derselbe Container, wg0 und beide Zugänge unangetastet. * **Bis v1.4.8** bekam der Hub bei jedem Neubau die nächste freie Adresse im Compose-Netz. Genau darauf zeigen die gemerkten UDP-Ströme — eine neue Adresse machte sie alle ungültig. Seit **v1.4.9** hat er eine feste. Nachgemessen: `172.18.0.240` vor und nach `up -d --force-recreate vpn-hub`. Erzwungen durch `tests/Feature/DeploymentRunsAsTheAppUserTest.php` — der Container darf nicht in der Neustart-Liste stehen, und der conntrack-Griff muss im Skript bleiben.