CluPilotCloud/docs/handoffs/2026-07-25-clupilot-engine-...

211 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# Handoff — CluPilot Provisionierungs-Engine v1.0
**Erstellt:** 2026-07-25
**Für:** frische Claude-Code-Session
**Art:** Feature-Bau-Handoff (TDD)
**Zuerst lesen:** `docs/handoffs/2026-07-25-clupilot-state-handoff.md` (Projekt-Zustand, Workflow, Regeln, Gotchas). Dann dieses Dokument.
**Design-Spec:** die ausführliche Engine-Design-Spec liegt beim **Nutzer** (nicht im Repo). **Vor Baubeginn anfordern** — sie ist bei Detail-Konflikten der Vertrag; dieses Dokument ist der Bau-Fahrplan.
---
## 1. Mission
Aus einer **bezahlten Bestellung** wird vollautomatisch eine **laufende, gesicherte, überwachte Nextcloud-Instanz** — mit persistentem Status, Retry, Timeout, sofort gespeicherten externen IDs und Acceptance-Gate. Ausführungsmodell: **DB-State-Machine + Tick-Orchestrator** (kein monolithischer Job).
## 2. Arbeitsweise (verbindlich)
- **TDD.** Das Superpowers-`test-driven-development`-Skill ist auf der VM installiert (ab dieser Session verfügbar) — nutzen. Jede Step-Klasse + der Orchestrator-Kern test-first. **`ProxmoxClient` in Step-Tests mocken.**
- **Tests laufen gegen sqlite `:memory:`** (siehe State-Handoff §5/§6 — Test-Isolation ist bereits korrekt eingerichtet; nicht kaputt machen).
- **Regeln R1R17** gelten (State-Handoff §9). R15 (Codex-Review clean) vor „fertig". Bei Spec-Konflikt: Design-Spec → `CLAUDE.md`/Regeln → Nachfrage.
- **Kleine, fokussierte Klassen** — eine Step-Klasse = eine Verantwortung.
---
## 3. Topologie-Entscheidung (verbindlich) ⭐
Zwei **getrennte** Ebenen — nicht verwechseln:
### 3.1 Software-Modell = **Standalone-Flotte** (Option 1)
CluPilot ist die **zentrale Konsole** und spricht **jeden Host einzeln per Proxmox-API-Token** an; die Flotte lebt in **CluPilots eigener DB**. **Kein corosync im Steuerungs-Layer.** „Cluster" ist zunächst nur CluPilots **logische Sicht**.
> Konsequenz für die Engine: Ein Host wird **immer** über seine eigene `wg_ip` + Token adressiert. Ob dieser Host physisch allein steht oder Teil eines Proxmox-Clusters ist, ist der Engine **egal**. Damit läuft v1.0 **heute mit einem einzigen Host** und bleibt zukunftsoffen.
### 3.2 Wachstumsziel Infrastruktur = **Cluster pro Rechenzentrum** (Option 3)
Sobald pro Standort ≥23 Hosts stehen und **Wartung ohne Downtime** (Live-Migration) bzw. **HA** gewünscht ist: die Hosts **innerhalb eines RZ** zu einem echten **corosync-Cluster** verbinden (Falkenstein-Cluster, Helsinki-Cluster). **Niemals** ein Cluster über WAN (fsn↔hel) — corosync braucht niedrige Latenz.
Fußangeln fürs spätere Clustern (jetzt nur im Datenmodell vorsehen, nicht bauen):
- **Quorum:** 2 Nodes sind fragil → 3 Nodes oder QDevice.
- **Storage:** Live-Migration braucht shared/replizierten Storage (Start: ZFS+Replikation; groß: Ceph).
- Für reine Wartungs-Umzüge reicht auch offline `qm remote-migrate` zwischen Standalone-Nodes (Proxmox 8) — Cluster erst für **Live**-Migration/HA nötig.
### 3.3 Was daraus fürs Datenmodell folgt (jetzt umsetzen)
- `hosts` bekommt **`datacenter`** (z. B. `fsn`, `hel`) und ein **nullable `cluster`** (Cluster-Name/-ID; v1.0 überall `null` = standalone). Beides jetzt anlegen, damit späteres Clustern **additiv** ist.
- **Placement v1.0 = einfach:** erster **aktiver** Host **im Ziel-Rechenzentrum** mit genug **freier Zusage** (`total_gb reserve Σ instances.quota_gb`). Cluster/Live-Migration/HA/Rebalancing sind **v1.1+**.
- Migration/HA werden **nicht** in v1.0 gebaut, aber das Modell verbaut sie nicht.
---
## 4. Scope
**IN v1.0 (Happy Path + Robustheit):**
1. Datenmodell (Migrations + Models).
2. Orchestrator-Kern: Run-State-Machine, Step-Kontrakt (Advance/Retry/Fail), Tick-Scheduler, `AdvanceRunJob`, Per-Run-Lock, Timeout, append-only Event-Log.
3. `ProxmoxClient`-Service (**NEU**).
4. Die **15 Kunden-Pipeline-Schritte** als idempotente Step-Klassen, inkl. Backup- + Monitoring-Registrierung + Acceptance.
5. Bestell-Eingang: Stripe-Webhook → `orders` mit **Idempotency-Key** → Run-Anlage.
6. Live-Fortschritt: die bereits existierenden Views an echte Daten binden (s. §9).
**NICHT in v1.0 (= v1.1, bewusst weglassen):**
- **Host-Pipeline** / Panel-Host-Onboarding / Hetzner-Robot-Client.
- **Live-Migration, HA, Cluster-Bildung, Rebalancing, Reconciliation-Loop** (Waisen-Abgleich).
- Admin-Skip/Resume-Cleanup, Abort-Cleanup-Pipeline (simples Retry + Pause reicht).
- Kompletter Kündigungs-/Löschprozess.
- Multi-Host-Placement über simple Zusage-Rechnung hinaus.
---
## 5. Voraussetzungen (vor echtem End-to-End-Lauf)
1. **Versioniertes Nextcloud-Blueprint-Template in Proxmox** (einmal manuell bauen → Template; muss **qemu-guest-agent + docker** enthalten). `CloneVirtualMachine` klont daraus; Template-VMID in Config/DB hinterlegen. Fehlt es → Step failt (`Fail`, nicht Retry).
2. **Dev-Proxmox-Host erreichbar** über WG-Tunnel (`hosts.wg_ip:8006`) mit `automation@pve`-Token (Minimal-Rolle). Ohne echten Host läuft nur die **gemockte** Test-Suite.
3. **MariaDB-Entscheidung** betrifft den **Nextcloud-Stack im Blueprint** (Compose im Gast), **nicht** die Panel-DB — nicht verwechseln. Die Engine schreibt nur Config, baut die DB nicht selbst.
4. Panel läuft (ist gebaut, s. State-Handoff).
5. **Stripe** nur Test-Modus in Dev, keine echten Zahlungen.
---
## 6. Bestandscode: Reuse / Neubau / Kollision
**Reuse:**
- `phpseclib3` (SSH/SFTP) — für host-nahe Kommandos falls nötig. Gast-interne occ/Compose-Kommandos laufen aber über **qemu-guest-agent (`guestExec`)**, nicht SSH-Warten.
- `Illuminate\Support\Facades\Crypt` — Muster für verschlüsselte Secrets. Proxmox-Token/Keys ebenso verschlüsselt ablegen (`hosts.api_token_ref`).
- **Laravel Reverb** (konfiguriert) → Live-Fortschritt pushen.
- **`scheduler`-Service** (`schedule:work`) → **Tick** hier registrieren (`routes/console.php` Schedule, jede Minute).
- **`redis`-Queue** → `AdvanceRunJob` (eigene Queue `provisioning` denkbar).
- Livewire-Konventionen + das bestehende **Design-System/Komponenten** (State-Handoff §4) für Admin-Ansichten.
**Neubau (existiert NICHT):**
- **`App\Services\ProxmoxClient`** — Proxmox VE REST (`/api2/json`), Token-Auth `PVEAPIToken=automation@pve!<id>=<secret>`, Basis-URL aus `hosts.wg_ip`. Methoden v1.0: `cloneVm, setCloudInit, resizeDisk, startVm, guestAgentPing, guestExec, getTaskStatus` (UPID-Polling), `applyFirewall`.
> ⚠️ **`HetznerClient` existiert nicht hier** (frischer Build). Falls du irgendwo Hetzner-Cloud-Code als Vorlage suchst: Proxmox-API ist **komplett anders** — nicht vermischen.
- Orchestrator-Kern (Runner, Step-Interface, Result-Objekte, Tick, Lock).
- Engine-Tabellen + Models.
**Kollisionen bewusst trennen:** Es gibt hier **keine** Alt-Domäne (`Server`/`Cluster`/`ProvisioningLog`/`ProvisionGaleraNode`) — der frühere „Server-Manager" wurde **nicht** mitgebaut. Die Engine ist ein sauberer Neuaufbau. Die neue `provisioning_step_events`-Tabelle ist **run-scoped & reich** (nicht mit irgendeinem simplen Log verwechseln).
---
## 7. Datenmodell (Migrations + Models) — Kern zuerst
Neu anlegen (Reihenfolge: runs/events/resources zuerst, dann Fach-Tabellen):
- `customers`, `orders` (`stripe_event_id` **unique** = Idempotency; `status`, `plan`, `amount`).
- `hosts`**inkl. `datacenter`, `cluster` (nullable)**, `wg_ip`, `api_token_ref` (encrypted), Kapazität `total_gb / total_ram / cpu_weight / reserve_pct`, `status`.
- `instances``vmid`, `quota_gb`, `disk_gb`, `subdomain`, `custom_domain`, `nc_admin_ref`, `route_written`, `cert_ok`, `status`, FK `host_id`, `customer_id`.
- `provisioning_runs`**polymorph** `subject_type`/`subject_id`, `pipeline`, `current_step`, `status`, `attempt`, `next_attempt_at`, `started_at`, `error`, `context` (json).
- `provisioning_step_events`**append-only:** `run_id, step, attempt, outcome, message, external_ref, created_at`.
- `run_resources``run_id, host_id, kind, external_id` (Brotkrumen für Idempotenz + spätere Reconciliation).
- `dns_records`, `backups`, `monitoring_targets`, `onboarding_tasks`.
**Kapazität = berechnet** (SUM `instances.quota_gb` je Host), kein separater Ledger. **Placement-Query** liest `total_gb reserve zugesagt`, **gefiltert auf `datacenter` und `status='active'`** (Cluster ignoriert die Query in v1.0).
**R11:** in URLs adressierbare Records (customers/instances/runs) per **UUID**, nicht Integer-PK.
---
## 8. Orchestrator-Kern (Herzstück, zuerst + test-first)
**Run-Status:** `pending → running ⇄ waiting → completed`, plus `paused` (Admin), `failed` (Retries erschöpft/fatal).
**Step-Interface (Vorschlag):**
```php
interface ProvisioningStep {
public function execute(ProvisioningRun $run): StepResult; // Advance | Retry | Fail
}
// StepResult::advance() | ::retry(int $afterSeconds, string $reason) | ::fail(string $reason)
```
**Runner-Pflichten je Ausführung:**
- **Vor Arbeit: Idempotenz-Check** (externe ID / DB-State „schon getan?").
- **Externe IDs sofort** in `run_resources`, **BEVOR** weitergemacht wird (vmid, UPID, hetzner_record_id, backup_job_id).
- Ergebnis in `provisioning_step_events` (append-only) + `provisioning_runs` fortschreiben.
- **Timeout** je Schritt (`started_at + max_duration`); Polling-Schritte mit Gesamt-Deadline.
- **Retry** mit Backoff, `attempt++`, `max_attempts` → sonst `Fail`.
- **Fatal vs. retrybar** entscheidet der Schritt je Fehler (Proxmox-5xx/Timeout = Retry; „Template not found"/ungültige Order = Fail).
**Advancement:**
- **Tick** (Scheduler, jede Minute): Runs `status IN (running,waiting) AND next_attempt_at <= now``AdvanceRunJob` dispatchen.
- **Sofort-Dispatch** bei Run-Anlage + nach erfolgreichem Schritt (nicht auf Tick warten).
- **Per-Run-Lock** (`Cache::lock("run:{id}")`) → nie zwei Worker auf demselben Run.
---
## 9. Die 15 Kunden-Schritte
Namespace `App\Provisioning\Steps\Customer\`. Reihenfolge:
`ValidateOrder → ReserveResources → CloneVirtualMachine → ConfigureCloudInit → StartVirtualMachine → WaitForGuestAgent → ConfigureNetwork → DeployApplicationStack → ConfigureNextcloud → CreateCustomerAdmin → ConfigureDnsAndTls → RegisterBackup → RegisterMonitoring → RunAcceptanceChecks → CompleteProvisioning`
Per-Schritt-Verhalten (Idempotenz-Check, **[E]** externe ID sofort, **[P]** Polling, **[F]** Fail) steht in der **Design-Spec Sektion 3** — dort nachschlagen, nicht neu erfinden. Harte Regeln:
- **Kundenzugang ERST nach `RunAcceptanceChecks` (14).** `CompleteProvisioning` (15) schaltet frei.
- Backup (12) + Monitoring (13) sind **Teil** der Bereitstellung, nicht danach.
- Zahlung ok trotz Fehler → Run `failed` + **manueller** Retry, **kein** Auto-Refund.
- `CreateCustomerAdmin`: Initial-Credential sicher an Kunde, **nie plaintext** persistieren.
- **`ReserveResources`** nutzt das Placement aus §7 (Host im Ziel-`datacenter`).
- occ/Compose im Gast via **guest-agent** (`guestExec`), nicht SSH-Warten.
- **DNS/TLS:** Hetzner-DNS-API legt `kundeXY.clupilot.com` an **[E]** `hetzner_record_id`; Cert via **HTTP-01** (kein DNS-01); Traefik-YAML in File-Provider-Verzeichnis schreiben, dann Cert-Ausstellung pollen **[P]** (Deadline großzügig — DNS-Propagation + Port 80 erreichbar).
---
## 10. Bestell-Eingang (Stripe)
- Webhook-Endpoint (Test-Modus). **Idempotency-Key = `stripe_event_id` unique** → verhindert Doppel-Provisionierung bei Webhook-Retries.
- Bezahlt → `orders`-Row → `provisioning_runs`-Eintrag (`pipeline=customer`, Subject = Order/Customer) anlegen + **sofort dispatchen**.
## 11. Live-Fortschritt (an bestehende UI binden)
- **Admin `/admin/provisioning`** ist aktuell eine **Fixture-View** (`app/Livewire/Admin/Provisioning.php`) → an echte `provisioning_runs` + `provisioning_step_events` binden, live via **Reverb** bei jedem `step_event`. Der `x-ui.progress-stepper` existiert bereits.
- **Kundendashboard**: der Bereitstellungs-Stepper auf `/dashboard` (aktuell Fixtures) → an den Run des Kunden binden; Zugang zur Cloud erst nach Abschluss.
- Panel = reine **View** auf die Tabellen (kein State im Frontend). Broadcast-Kanäle **privat** (authentifiziert), pro-Kunde scopen.
---
## 12. Build-Reihenfolge (jede Phase grün testen)
1. Migrations + Models (runs, step_events, run_resources, instances, hosts[+datacenter/cluster], customers, orders).
2. Orchestrator-Kern test-first (StepResult, Interface, Runner Advance/Retry/Fail/Timeout/Lock, `AdvanceRunJob`, Tick) mit 23 Fake-Steps.
3. `ProxmoxClient` + Fake/Mock-Doppel.
4. Steps 12 (ValidateOrder, ReserveResources — reine DB/Placement, früh testbar, inkl. datacenter-Filter).
5. Steps 37 (VM-Lebenszyklus) gegen gemockten ProxmoxClient; externe-ID-Persistenz + Idempotenz je Step.
6. Steps 810 (Stack/Nextcloud/Admin) via guestExec-Mock.
7. Step 11 (DNS/TLS) — Hetzner-DNS-Client + Traefik-YAML-Writer + Cert-Polling.
8. Steps 1213 (Backup/Monitoring registrieren).
9. Steps 1415 (Acceptance + Complete).
10. Stripe-Webhook + Idempotenz + Run-Anlage.
11. Live-Fortschritt an Admin+Kundendashboard binden (Reverb).
12. End-to-End gegen Dev-Host + Template (falls verfügbar), sonst voll gemockt grün.
## 13. Definition of Done (v1.0)
- [ ] Bezahlte Test-Bestellung erzeugt einen Run, der ohne manuelles Zutun bis `completed` läuft (gegen Dev-Host+Template) ODER voll gemockt grün.
- [ ] Jeder Schritt idempotent: künstlicher Abbruch + erneuter Tick → **keine** Doppel-Ressourcen (verifiziert über `run_resources`).
- [ ] Retry mit Backoff + Timeout + Per-Run-Lock getestet.
- [ ] Externe IDs (vmid/UPID/dns/backup) werden **vor** Weitergabe persistiert (Test simuliert Crash danach).
- [ ] Kundenzugang erst nach bestandener Acceptance.
- [ ] Stripe-Doppel-Webhook → nur EINE Provisionierung.
- [ ] Placement respektiert `datacenter` + freie Zusage; `cluster`-Feld existiert (nullable, ungenutzt in v1.0).
- [ ] Admin- + Kunden-Fortschritt live (Reverb).
- [ ] Pest grün; R12 (Admin/Kunden-Views 0 Konsolenfehler); R15 Codex clean; keine Secrets im Repo.
## 14. Watch-Items
1. **Kein Proxmox-Code vorhanden**`ProxmoxClient` inkl. Auth/UPID-Polling ist echter Neubau. Früh isoliert testen.
2. **Guest-Agent statt SSH** — Nextcloud-VM hat evtl. keinen SSH-Zugang; occ/Compose via `guestExec`. Blueprint muss guest-agent + docker enthalten.
3. **Traefik File-Provider-Pfad** muss beschreibbar sein (wo läuft Traefik, wie schreibt die Engine die YAML? — konkreten Schreibweg festlegen, ggf. via Host/guest).
4. **HTTP-01 braucht Port 80** auf Traefik für die neue Subdomain, bevor das Cert kommt → DNS-Propagation abwarten (Polling-Deadline großzügig).
5. **Secrets** (Proxmox-Token, Hetzner-DNS-Token, Backup-Key-Refs) verschlüsselt (`Crypt`)/aus `.env`, nie in Git.
6. **Topologie:** `cluster` bleibt v1.0 `null`. Nicht verleiten lassen, Cluster/Migration/HA einzubauen — das ist v1.1 und würde v1.0 aufblähen.
7. Gotchas aus dem State-Handoff §6 beachten (`@js()`/Test-Isolation/CSRF/Locale/Vite-Reload).
## 15. Danach (v1.1, NICHT jetzt)
Host-Pipeline (Panel-Onboarding + Hetzner-Robot-Client) · **Cluster-pro-RZ-Bildung + Live-Migration/HA** (Topologie-Wachstumsziel) · Reconciliation-Loop · Admin-Skip/Resume-Cleanup · Kündigungs-/Löschprozess. Die Architektur (polymorpher Run, `run_resources`, Step-Kontrakt, `datacenter`/`cluster` im Host) ist so gebaut, dass das **rein additiv** ist.