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

15 KiB
Raw Permalink Blame History

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-QueueAdvanceRunJob (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).
  • hostsinkl. datacenter, cluster (nullable), wg_ip, api_token_ref (encrypted), Kapazität total_gb / total_ram / cpu_weight / reserve_pct, status.
  • instancesvmid, quota_gb, disk_gb, subdomain, custom_domain, nc_admin_ref, route_written, cert_ok, status, FK host_id, customer_id.
  • provisioning_runspolymorph subject_type/subject_id, pipeline, current_step, status, attempt, next_attempt_at, started_at, error, context (json).
  • provisioning_step_eventsappend-only: run_id, step, attempt, outcome, message, external_ref, created_at.
  • run_resourcesrun_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_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 <= nowAdvanceRunJob 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 vorhandenProxmoxClient 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.