Write down what to do when the bootstrap stops

One entry per section: how you notice, where to look, what it usually is. Above
all of them the rule the plan already states — a half installed machine is
reinstalled, not repaired — with the reason, which is not convenience. The
script is built so a second run on a freshly imaged machine always comes out the
same; a run on a machine somebody straightened out by hand is no longer that.
Repairing trades a repeatable result for a one-off, and the difference only
shows up on the next host.

rescue_checked is the stated exception, because nothing has been written yet. It
is the only section where fixing in place is right, and the runbook says so
rather than leaving the reader to infer it.

Two things that are easy to get wrong are answered up front. Whether the reboot
has happened is one `findmnt -no FSTYPE /` — tmpfs or overlay means still in the
rescue system, zfs means the first-boot hook already resumed. And when a host is
unreachable, the first question is whether the firewall had even run: it is
applied last, so if `registered` is not done, the cause is the network and not
the rules.

The installer can be watched while it runs. If QEMU is still up, forwarding 5900
over SSH shows what the ISO is doing, and an input mask there means the answer
file was not accepted and the installer has dropped into interactive mode where
it will sit until the hour expires. That failure otherwise arrives as a timeout
saying nothing.

The last heading is the honest one: everything here is written from the script
and the findings behind it, not from incidents. What actually stops on the first
real run belongs in this file afterwards, quoted as it appeared in the console.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
feature/host-bootstrap
nexxo 2026-07-30 20:22:32 +02:00
parent f45f290f00
commit 668ed67de4
2 changed files with 306 additions and 1 deletions

View File

@ -0,0 +1,305 @@
# Runbook: Wenn die Host-Übernahme stehenbleibt
Der Bootstrap läuft in neun Abschnitten. Bleibt einer davon aus, zeigt die
Konsole **welcher** — nicht „irgendetwas ging schief". Dieses Runbook sagt je
Abschnitt: woran man es merkt, wo man nachsieht, und was der übliche Grund ist.
---
## Die eine Regel, die über allen steht
**Eine halb installierte Maschine wird neu aufgesetzt, nicht nachgebessert.**
Nicht aus Bequemlichkeit. Der Bootstrap ist so gebaut, dass ein zweiter Lauf auf
einer frisch aufgesetzten Maschine immer gleich ausgeht — ein Lauf auf einer
Maschine, an der jemand von Hand etwas gerade gerückt hat, ist das nicht mehr.
Wer nachbessert, tauscht ein wiederholbares Ergebnis gegen ein einmaliges, und
merkt den Unterschied erst beim nächsten Host.
Der Weg zurück ist immer derselbe:
1. Im Adminbereich einen **neuen** Host anlegen. Der Einmal-Code ist verbraucht;
die Seite zeigt ihn nicht wieder her, und das ist Absicht.
2. Beim Anbieter das **Rettungssystem** starten.
3. Die neue Zeile einfügen.
Der alte Host-Eintrag wird im Adminbereich entfernt. Solange er steht, hält er
seine Tunneladresse belegt.
**Ausnahme:** Bleibt der Lauf in `rescue_checked` stehen, ist noch **nichts**
geschrieben worden. Dort ist Nachbessern richtig — es ist der einzige Abschnitt,
der die Platte nicht angefasst hat.
---
## Wo alles steht
| Was | Wo |
|---|---|
| Fortschritt, Zeile je Abschnitt | `/var/lib/clupilot/progress.jsonl` |
| Was schon gesendet wurde (Zeilenzahl) | `/var/lib/clupilot/progress.sent` |
| Laufprotokoll des Skripts | `/var/lib/clupilot/bootstrap.log` |
| Aufrufargumente (nur root) | `/var/lib/clupilot/arguments` |
| Arbeitsverzeichnis: ISO, Protokolle | `/var/tmp/clupilot-work/` |
| Installer-Protokoll | `/var/tmp/clupilot-work/qemu.log` |
| Antwortdatei des Installers | `/var/tmp/clupilot-work/answer.toml` |
| Traefik | `/var/log/traefik.log`, `/etc/traefik/traefik.yml` |
Die Fortschrittsdatei **ist** der Zustand. Ein Abschnitt, der dort `done`
gemeldet hat, läuft bei einem Wiederanlauf nicht erneut.
---
## Vor dem Neustart oder danach?
Das ist die erste Frage, und sie ist leicht zu beantworten:
```sh
findmnt -no FSTYPE /
```
`tmpfs`, `overlay` oder `ramfs` heißt: noch im Rettungssystem, der Neustart
steht bevor. `zfs` heißt: die Maschine läuft schon auf dem installierten System,
und der `first-boot`-Hook hat den Lauf wieder aufgenommen.
---
## Abschnitt für Abschnitt
### `rescue_checked` — die Eingangsprüfung
**Woran man es merkt:** Das Skript endet sofort mit einer Liste von Befunden.
In der Konsole steht nichts, weil es vor dem Tunnel keinen Weg dorthin gibt.
**Übliche Gründe:**
- *„Das Wurzelverzeichnis liegt auf /dev/… also auf einer echten Partition."*
Die Maschine läuft nicht im Rettungssystem. Beim Anbieter das Rettungssystem
einschalten und **neu starten** — das Einschalten allein genügt nicht.
- *„Die CPU meldet weder vmx noch svm."* Ein Cloud-Produkt ohne verschachtelte
Virtualisierung (Hetzner CPX/CX, teils netcup). Nicht abstellbar; es braucht
eine dedizierte Maschine.
- *„/dev/kvm gibt es nicht."* Virtualisierung im BIOS aus, oder das
Rettungssystem hat das Modul nicht geladen: `modprobe kvm_intel` bzw.
`modprobe kvm_amd`, dann die Zeile noch einmal.
- *„Es sind Partitionen echter Platten eingehängt."* Jemand hat nachgesehen.
`umount` und noch einmal.
- *„Die Uhr geht … Sekunden falsch."* Die Meldung enthält den fertigen Befehl
zum Stellen. Ohne ihn scheitert später jede TLS-Prüfung mit einer Meldung über
Zertifikate statt über die Uhrzeit.
**Hier ist Nachbessern richtig.** Es wurde noch nichts geschrieben.
---
### `debian_installed` — das Grundsystem
Heißt „Grundsystem geschrieben". Es entsteht **kein** Debian, das nachher jemand
umbaut: die offizielle Proxmox-ISO wird mit einer Antwortdatei versehen und
unter QEMU gegen die echten Platten gestartet.
**Woran man es merkt:** Der Lauf steht lange ohne Meldung; im Rettungssystem
läuft ein `qemu-system-x86_64`.
**Wo man nachsieht:** `/var/tmp/clupilot-work/qemu.log`, und die Antwortdatei
daneben. Läuft QEMU noch, kann man dem Installer zusehen — er hört auf
`127.0.0.1:5900`:
```bash
ssh -L 5900:127.0.0.1:5900 root@<öffentliche-ip>
```
Dann mit einem VNC-Betrachter auf `localhost:5900`. Sieht man dort eine
**Eingabemaske**, ist die Antwortdatei nicht angenommen worden — der Installer
ist in den interaktiven Modus gefallen und wartet, bis die Stunde abläuft.
**Übliche Gründe:**
- `answer.toml` abgelehnt. Steht in `/var/tmp/clupilot-work/assistant.log`. Das
Skript prüft sie mit `validate-answer`, bevor es die ISO backt, also ist das
selten — und wenn, dann liegt es an einem Wert aus der Umgebung (Adresse,
Gateway, MAC), den das Rettungssystem anders meldet als erwartet.
- Prüfsumme der ISO stimmt nicht. Abgebrochene Übertragung. Zeile noch einmal.
- Keine passende PVE-9-ISO gefunden. Die Liste unter
`https://enterprise.proxmox.com/iso/` war nicht erreichbar.
---
### `rebooted` — die einzige unumkehrbare Stelle
**Woran man es merkt:** Die Maschine antwortet nach dem Neustart nicht mehr,
oder sie antwortet, meldet aber nichts weiter.
**Übliche Gründe:**
- Die Maschine bootet noch vom Rettungssystem statt von der Platte. Beim
Anbieter das Rettungssystem **abschalten** und neu starten.
- Der `first-boot`-Hook lief nicht. Nachsehen mit
`journalctl -u proxmox-first-boot` und `ls /var/lib/clupilot/`. Fehlt das
Verzeichnis ganz, ist der Hook nicht in die ISO gekommen — dann neu aufsetzen.
**Kommt die Maschine gar nicht zurück:** Anbieterkonsole. Neu aufsetzen ist
schneller als jede Suche.
---
### `proxmox_installed` — abnehmen und Quellen richten
**Woran man es merkt:** Meldung mit `failed` und einem der folgenden Sätze.
**Übliche Gründe:**
- *„pveversion gibt es nicht"* — die ISO hat kein Proxmox installiert. Neu
aufsetzen.
- *„läuft auf …, das ist kein PVE-Kernel"* — die Maschine ist in den
Debian-Kernel gebootet. `uname -r` muss `-pve` enthalten.
- *„erwartet wäre PVE 9"* — Abbild und Unterbau passen nicht zusammen. Mit dem
richtigen Abbild neu aufsetzen; Paketquellen heilen das nicht.
- *„apt-get update schlägt fehl"* — siehe `/var/tmp/clupilot-work/apt.log`. Fast
immer eine Abonnement-Quelle, die wieder aufgetaucht ist:
`ls /etc/apt/sources.list.d/`.
---
### `network_bridged` — der gefährlichste Abschnitt
**Woran man es merkt:** Entweder eine `failed`-Meldung, oder die Maschine ist
für ein paar Minuten weg und kommt dann von selbst zurück.
**Kommt sie zurück, war das die Selbstrücknahme.** Das ist kein Fehler des
Zeitgebers, sondern sein Zweck: die Brücke hätte den Host vom Netz genommen.
Nachsehen mit `journalctl -t clupilot`.
**Übliche Gründe:**
- Falsche Anbieterform erkannt. Bei einer gerouteten Einzeladresse muss
`pointopoint` in `/etc/network/interfaces` stehen; fehlt sie, findet der
Kernel das Gateway nicht.
- *„die Datacenter-Firewall ließ sich nicht einschalten"* — ohne sie sind die
„nur 80/443"-Regeln **jeder** Kunden-VM wirkungslos. Prüfen mit
`pvesh get /cluster/firewall/options`.
**Kommt die Maschine nicht zurück:** Anbieterkonsole, dort
`tar xzf /var/lib/clupilot/interfaces.vor-der-bruecke.tar.gz -C /` und
`ifreload -a`. Danach neu aufsetzen.
---
### `wireguard_joined` — ab hier ist CluPilot erreichbar
**Woran man es merkt:** Bis hierher stand in der Konsole „wartet auf den
Tunnel". Bleibt es dabei, ist dieser Abschnitt der offene.
**Wo man nachsieht:** `wg show` auf dem Host. Steht dort ein Peer ohne
`latest handshake`, steht die Datei und der Tunnel nicht.
**Übliche Gründe:**
- Der Hub kennt den Peer nicht. Sollte nicht vorkommen — CluPilot legt ihn beim
Anlegen des Hosts an —, aber ein gelöschter Host-Eintrag nimmt ihn mit.
- Der Hub-Endpunkt ist von dieser Maschine aus nicht erreichbar. UDP auf dem
Port des Hubs prüfen.
- *„konnte wg-quick@wg0 nicht freigeben"* — der Tunnel überlebte den nächsten
Neustart nicht. Das ist ein harter Fehler, kein Schönheitsfehler.
---
### `traefik_running`
**Woran man es merkt:** `failed` mit einem der Sätze unten. Der Host ist
erreichbar, also lässt sich alles direkt nachsehen.
**Übliche Gründe:**
- *„antwortet nicht am Ping-Endpunkt"*`/var/log/traefik.log` und
`systemctl status traefik`.
- *„80 und 443 sind nicht belegt"* — Traefik läuft, ein Entrypoint fehlt.
`ss -ltnp | grep -E ':80|:443'`. Belegt sie etwas anderes, gehört das nicht auf
diese Maschine.
- Prüfsumme des Binaries stimmt nicht. Zeile noch einmal.
Dass Traefik hier **noch keine Routentabelle** holt, ist richtig so: den Token
dafür gibt es erst mit der Registrierung.
---
### `template_built` — die goldene Vorlage
**Woran man es merkt:** `failed`, meist mit einem Hinweis auf eine der drei
Fallen.
**Wo man nachsieht:** `/var/tmp/clupilot-work/virt-customize.log` und
`qm.log`.
**Übliche Gründe:**
- *„erfüllt Falle 2 nicht"* — das Cloud-Abbild hat LVM oder die Wurzel ist nicht
die letzte Partition. Dann stimmt etwas am Abbild nicht, das dort liegt.
- *„qemu-guest-agent ist NICHT im Abbild"*`virt-customize` hat Erfolg
gemeldet und apt hat das Paket still nicht installiert. Fast immer ein
Netzproblem im Abbild-Umbau.
- *„kein `user: www-data`"* — die Compose-Datei im Abbild ist nicht die aus
`assets/`. Ohne diese Zeile schlagen **alle** `occ`-Aufrufe fehl.
- *„meldet nicht template: 1"* — die VM existiert, ist aber keine Vorlage.
`qm template 9000` von Hand und dann **trotzdem neu aufsetzen**: wenn dieser
Schritt schiefging, ist unklar, was sonst noch.
---
### `registered` — die Übergabe
**Woran man es merkt:** Der Host bleibt in der Konsole auf `onboarding`.
**Übliche Gründe:**
- *„die Proxmox-Rolle ließ sich nicht angleichen"* — ohne `Sys.Modify` stirbt
später **jede** Kundenbereitstellung am Backup-Schritt. Prüfen mit
`pveum role list`.
- *„hat nicht geantwortet"* — CluPilot ist über den Tunnel nicht erreichbar,
obwohl der Handshake stand. Auf der CluPilot-Seite nachsehen, ob die
Tunneladresse in `TRUSTED_RANGES` steht.
- *„kam ohne host_token zurück"* — die Registrierung ist durchgelaufen, die
Antwort war unvollständig. Auf der CluPilot-Seite nachsehen.
- *„der Schlüsseltausch hat keinen Handshake ergeben"* — das Skript hat auf den
alten Schlüssel zurückgestellt, der Host ist also **noch erreichbar**. Der Hub
hat den neuen Peer möglicherweise schon aufgenommen und den alten entfernt;
dann ist der Host beim nächsten Versuch weg. Neu aufsetzen.
- *„die Routentabelle ist nicht abrufbar"* — der Token stimmt nicht oder
`/host/routes` antwortet nicht. Direkt prüfen:
```bash
curl -H "Authorization: Bearer $(cat /etc/traefik/host-token)" http://<tunnel-ip>/host/routes
```
---
## Der Host ist nicht mehr erreichbar
Erst die Frage: **war die Firewall schon dran?** Sie wird als **allerletztes**
angewendet. Steht in der Konsole `registered` noch nicht auf `done`, war sie es
nicht — dann liegt es am Netz, nicht an den Regeln.
War sie dran, gibt es von der Anbieterkonsole aus genau einen Griff:
```bash
/usr/local/sbin/clupilot-emergency-open-firewall.sh
```
Der Host nimmt danach auf jedem Port wieder an. **Es gibt bewusst keine
automatische Wiederöffnung** — kein Zeitgeber, kein „nach N Minuten ohne
Handshake wieder auf". Eine Firewall, die sich unter Störung selbst öffnet, ist
keine Firewall.
Zurück kommen die Regeln mit:
```bash
nft -f /etc/nftables.conf && systemctl enable --now nftables
```
---
## Was dieses Runbook nicht sagt
Alles, was noch nicht auf echter Hardware gelaufen ist. Die Abschnitte oben sind
aus dem Skript und aus den Fundstellen geschrieben, die ihm zugrunde liegen —
nicht aus Vorfällen. Was beim ersten Durchlauf tatsächlich stehenbleibt, gehört
hier ergänzt, mit dem Satz, der in der Konsole stand.

View File

@ -502,7 +502,7 @@ merkt, wo man nachsieht, was der übliche Grund ist. Und ausdrücklich: **eine h
installierte Maschine wird neu aufgesetzt, nicht nachgebessert.** Ein neuer Code
aus dem Adminbereich, Rettungssystem, noch einmal.
- [ ] Schreiben, committen. Nachricht: `Write down what to do when the bootstrap stops`
- [x] Schreiben, committen. Nachricht: `Write down what to do when the bootstrap stops`
---