# Stand am 31. Juli 2026 und was offen ist

Für die nächste Sitzung. Alles unten ist gemessen oder committet, nichts davon
ist Erinnerung.

## Wo es steht

`main` auf **v1.3.77**, alle Releases getaggt und gepusht, **2053 Tests grün**.
`feature/host-bootstrap` ist deckungsgleich mit `main`.

Ausgeliefert in dieser Sitzung: der **Betriebsmodus** (Test-/Livebetrieb mit je
eigenem Platz pro Zugangsdatum, seit v1.3.64 — er lag fertig und ohne Tag
herum), das **Bootstrap-Skript** für die Host-Übernahme, der **Adminbereich**
dafür, der **Datei-Hostname** `files.clupilot.com`, die Seite **Betrieb →
Hostnamen** mit Zertifikatsübersicht, und mehrere Korrekturen an der
Bereitschaftsseite.

## Offen 1 — Hetzner-DNS-Migration (dringend, blockiert Bestellungen)

Steht vollständig in **`docs/handoffs/2026-07-31-hetzner-dns-api-migration.md`**:
Endpunkte aus `cloud.spec.json` gezogen, RRSet-Modell, beide Fallen im Namen.

Kurz: die alte DNS-API ist abgeschaltet (301 auf die Weboberfläche). Der Token
des Betreibers ist bereits ein Cloud-Token und antwortet mit **200** an
`api.hetzner.cloud/v1/zones`. Es fehlt nur der Code — in `DnsTokenCheck` **und**
in `HttpHetznerDnsClient`. Solange das so steht, stirbt jede Bereitstellung in
`ConfigureDnsAndTls`, nachdem der Kunde bezahlt hat.

**Das zuerst.** Es ist das Einzige auf dieser Liste, das laufendes Geschäft
verhindert.

## Offen 2 — Die Bereitschaftsseite muss aus sich heraus behebbar sein

Der Betreiber hat den Punkt gemacht, und er ist richtig: eine Seite, die Dinge
bemängelt, die man nur über eine Shell beheben kann, verschiebt die Arbeit
lediglich. Drei Stellen tun heute genau das.

### a) Schlüsselpaar für Hosts erzeugen

`ssh.private_key` liegt im Tresor und muss von Hand hineingelegt werden — es
gibt **keinen Erzeuger**. Heute:

```bash
ssh-keygen -t ed25519 -N "" -C "clupilot" -f /tmp/clupilot-host
cat /tmp/clupilot-host    # privater Teil in die Konsole
shred -u /tmp/clupilot-host /tmp/clupilot-host.pub
```

Ein Knopf **„Schlüsselpaar erzeugen"** neben dem Feld wäre klein und klar
begrenzt. `App\Services\Wireguard\Keypair::generate()` macht dasselbe für
WireGuard längst in PHP, mit der Begründung im Kopfkommentar — dort steht auch,
warum nicht in eine Shell ausgelagert wird. Für SSH braucht es ein
Ed25519-Paar; `sodium_crypto_sign_keypair()` liefert es, das OpenSSH-Format ist
die einzige Arbeit daran.

Ein erzeugter privater Schlüssel darf **nicht** angezeigt werden — er geht
direkt in den Tresor, und angezeigt wird nur der öffentliche Teil.

### b) Stripe-Katalog aus der Konsole abgleichen

Der Befehl existiert und funktioniert:

```bash
docker compose exec -T -u www-data app php artisan stripe:sync-catalogue --dry-run
```

Er soll aus der Konsole laufen. **Nicht synchron in einem Livewire-Aufruf**: er
spricht mit Stripe, legt Produkte und Preise an und kann Sekunden bis Minuten
brauchen. Also als Auftrag in die Warteschlange, mit einem Ergebnis, das die
Seite anzeigt — dasselbe Muster wie `UpdateChannel`, nur ohne den Agenten, denn
hier braucht es kein root.

Der Trockenlauf gehört mit in die Oberfläche: erst zeigen, was entstünde, dann
ein zweiter Knopf. Der Befehl kann das schon, es fehlt nur der Weg dorthin.

**Achtung:** `SyncStripeCatalogue` verweigert den Dienst, wenn der gespeicherte
Katalog zu einem anderen Konto gehört (Betriebsmodus). Diese Fehlermeldung muss
die Konsole wörtlich zeigen — sie ist eine Warnung vor dem Leeren eines
Katalogs, an dem laufende Verträge hängen.

### c) Der Rest

Stripe-Schlüssel, Signaturschlüssel, Firmendaten, Mailtransport: alles Felder,
die **Beheben** schon richtig verlinkt. Da ist nichts zu bauen.

Beim Mailtransport reicht `mail.default` ungleich `log`/`array` — die
Arbeiterprozesse starten nach dem Speichern von selbst neu, weil sie ihre
Umgebung nur beim Start lesen.

## Offen 3 — Die Abnahme des Bootstrap-Skripts

**Von den zehn Aufgaben ist keine einzige auf echter Hardware gelaufen.** Der
Plan (`docs/superpowers/plans/2026-07-30-host-uebernahme-bootstrap-skript.md`)
hat 19 abgehakte und 12 offene Punkte, und alle zwölf sind Hardware-Abnahmen.

Zwei bestehen ausdrücklich darauf und sind sonst bloße Behauptungen:

- Die **Selbstrücknahme der Brücke** muss absichtlich ausgelöst werden — eine
  Brücke bauen, die die Maschine vom Netz nimmt, und zusehen, wie sie von selbst
  zurückkommt.
- Die **Vorlage** muss wirklich geklont, gestartet und mit `occ status` als
  `www-data` befragt werden.

Die Vorbedingungen stehen inzwischen: `files.clupilot.com` liefert das Archiv
aus (404 ohne gültigen Code, gemessen), der Adminbereich zeigt die Zeile, und
`--fqdn` wird mitgegeben. Was fehlt, ist eine dedizierte Maschine mit
`/dev/kvm` im Rettungssystem.

Was beim ersten Lauf stehenbleibt, gehört ins Runbook
(`docs/runbooks/host-bootstrap.md`) — dessen letzter Abschnitt sagt selbst, dass
er aus dem Skript geschrieben ist und nicht aus Vorfällen.

## Zwei lose Enden

- **Die zwei Phantom-Hosts** stehen weiter im Pool. In dieser Sitzung nicht
  angefasst.
- **`feature/betriebsmodus`** ist inhaltlich in `main` (über
  `feature/host-bootstrap`), der Zweig selbst kann weg.

## Was diese Sitzung über das Ausliefern gelernt hat

Steht auch in den Projektnotizen, aber hier, weil es zweimal Geld gekostet hat:

**Release-Schritte nie mit `&&` verketten.** Mergen, Version setzen, committen,
taggen, pushen — getrennte Aufrufe, mit `git log --oneline -1` dazwischen. In
einer Kette hat ein gescheiterter Merge trotzdem einen Tag am falschen Commit
erzeugt und gepusht. Betraf v1.3.66 und v1.3.69; beide gelöscht, beide Nummern
übersprungen.

**Vor jedem Release `pwd` UND `git branch --show-current`.** `cd` hält zwischen
Aufrufen nicht. v1.3.68 wurde im Worktree geschnitten und landete auf dem
Feature-Zweig statt auf `main`.

**Ein falscher Tag wird gelöscht und die Nummer übersprungen**, nie verschoben.

## Wiederkehrendes Muster in den Funden

Fast jeder Fehler dieser Sitzung war dieselbe Sorte: **eine Anzeige, die
zuversichtlicher war als ihre Grundlage.** Grünes „Erfüllt" über rotem „nicht in
Ordnung". „In diesem Konto liegt keine Zone", hergeleitet aus einer HTML-Seite.
Ein Kommentar, der behauptete, der Installer lege eine Datei an, die er nie
angelegt hat.

R19 nennt das beim Namen, und es lohnt sich, beim Lesen dieses Codes danach zu
suchen: **eine Zusage, die keine ist, hält den Nächsten vom Nachsehen ab.**
