15 KiB
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.ProxmoxClientin Step-Tests mocken. - Tests laufen gegen sqlite
:memory:(siehe State-Handoff §5/§6 — Test-Isolation ist bereits korrekt eingerichtet; nicht kaputt machen). - Regeln R1–R17 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 ≥2–3 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-migratezwischen Standalone-Nodes (Proxmox 8) — Cluster erst für Live-Migration/HA nötig.
3.3 Was daraus fürs Datenmodell folgt (jetzt umsetzen)
hostsbekommtdatacenter(z. B.fsn,hel) und ein nullablecluster(Cluster-Name/-ID; v1.0 überallnull= 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):
- Datenmodell (Migrations + Models).
- Orchestrator-Kern: Run-State-Machine, Step-Kontrakt (Advance/Retry/Fail), Tick-Scheduler,
AdvanceRunJob, Per-Run-Lock, Timeout, append-only Event-Log. ProxmoxClient-Service (NEU).- Die 15 Kunden-Pipeline-Schritte als idempotente Step-Klassen, inkl. Backup- + Monitoring-Registrierung + Acceptance.
- Bestell-Eingang: Stripe-Webhook →
ordersmit Idempotency-Key → Run-Anlage. - 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)
- Versioniertes Nextcloud-Blueprint-Template in Proxmox (einmal manuell bauen → Template; muss qemu-guest-agent + docker enthalten).
CloneVirtualMachineklont daraus; Template-VMID in Config/DB hinterlegen. Fehlt es → Step failt (Fail, nicht Retry). - Dev-Proxmox-Host erreichbar über WG-Tunnel (
hosts.wg_ip:8006) mitautomation@pve-Token (Minimal-Rolle). Ohne echten Host läuft nur die gemockte Test-Suite. - 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.
- Panel läuft (ist gebaut, s. State-Handoff).
- 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.phpSchedule, jede Minute).redis-Queue →AdvanceRunJob(eigene Queueprovisioningdenkbar).- 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-AuthPVEAPIToken=automation@pve!<id>=<secret>, Basis-URL aushosts.wg_ip. Methoden v1.0:cloneVm, setCloudInit, resizeDisk, startVm, guestAgentPing, guestExec, getTaskStatus(UPID-Polling),applyFirewall.⚠️
HetznerClientexistiert 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_idunique = Idempotency;status,plan,amount).hosts— inkl.datacenter,cluster(nullable),wg_ip,api_token_ref(encrypted), Kapazitättotal_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, FKhost_id,customer_id.provisioning_runs— polymorphsubject_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):
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_runsfortschreiben. - Timeout je Schritt (
started_at + max_duration); Polling-Schritte mit Gesamt-Deadline. - Retry mit Backoff,
attempt++,max_attempts→ sonstFail. - 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→AdvanceRunJobdispatchen. - 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.ReserveResourcesnutzt 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.coman [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_idunique → 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/provisioningist aktuell eine Fixture-View (app/Livewire/Admin/Provisioning.php) → an echteprovisioning_runs+provisioning_step_eventsbinden, live via Reverb bei jedemstep_event. Derx-ui.progress-stepperexistiert 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)
- Migrations + Models (runs, step_events, run_resources, instances, hosts[+datacenter/cluster], customers, orders).
- Orchestrator-Kern test-first (StepResult, Interface, Runner Advance/Retry/Fail/Timeout/Lock,
AdvanceRunJob, Tick) mit 2–3 Fake-Steps. ProxmoxClient+ Fake/Mock-Doppel.- Steps 1–2 (ValidateOrder, ReserveResources — reine DB/Placement, früh testbar, inkl. datacenter-Filter).
- Steps 3–7 (VM-Lebenszyklus) gegen gemockten ProxmoxClient; externe-ID-Persistenz + Idempotenz je Step.
- Steps 8–10 (Stack/Nextcloud/Admin) via guestExec-Mock.
- Step 11 (DNS/TLS) — Hetzner-DNS-Client + Traefik-YAML-Writer + Cert-Polling.
- Steps 12–13 (Backup/Monitoring registrieren).
- Steps 14–15 (Acceptance + Complete).
- Stripe-Webhook + Idempotenz + Run-Anlage.
- Live-Fortschritt an Admin+Kundendashboard binden (Reverb).
- 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
completedlä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
- Kein Proxmox-Code vorhanden →
ProxmoxClientinkl. Auth/UPID-Polling ist echter Neubau. Früh isoliert testen. - Guest-Agent statt SSH — Nextcloud-VM hat evtl. keinen SSH-Zugang; occ/Compose via
guestExec. Blueprint muss guest-agent + docker enthalten. - Traefik File-Provider-Pfad muss beschreibbar sein (wo läuft Traefik, wie schreibt die Engine die YAML? — konkreten Schreibweg festlegen, ggf. via Host/guest).
- HTTP-01 braucht Port 80 auf Traefik für die neue Subdomain, bevor das Cert kommt → DNS-Propagation abwarten (Polling-Deadline großzügig).
- Secrets (Proxmox-Token, Hetzner-DNS-Token, Backup-Key-Refs) verschlüsselt (
Crypt)/aus.env, nie in Git. - Topologie:
clusterbleibt v1.0null. Nicht verleiten lassen, Cluster/Migration/HA einzubauen — das ist v1.1 und würde v1.0 aufblähen. - 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.