# Proxmox-Provider ## Status: vollständig gebaut, der Meilenstein auf echter Hardware fehlt `SYNCOVA_IMPLEMENTATION_PLAN.md` §9 nennt für Phase 7 einen verpflichtenden End-to-End-Meilenstein: ```text Discover VM → Backup VM → Verify → Delete test VM → Restore VM → Boot VM → Validate VM ``` **Der Code für jeden dieser Schritte ist vorhanden und läuft durch — gegen einen Nachbau der API.** Ein Gast lässt sich entdecken, sichern, prüfen und bitgenau zurückschreiben; ein Test belegt den vollständigen Rundlauf. Was **fehlt**, ist die Ausführung auf einem echten Verbund, und damit vor allem der Beweis der vorletzten Stufe: **dass die wiederhergestellte Maschine startet**. Für die Entwicklung stand kein Proxmox-VE-Verbund zur Verfügung; der gesamte Code entstand auf macOS. Was ein Nachbau leisten kann und was nicht: | Geprüft | Ungeprüft | | --- | --- | | Deutung der Antwortformate (`data`-Hülle, UPID, Konfigurationssyntax) | Ob echte Proxmox-Versionen genau diese Formate liefern | | Asynchrone Aufgabenverfolgung über UPIDs | Verhalten bei Migration während einer laufenden Aufgabe | | Fehlerklassifizierung, Wiederholung, Zertifikatsbindung | Zeitverhalten unter Last, Rechtefehler echter Tokenrollen | | Plattenerkennung inkl. `backup=0` und CD-ROM-Abgrenzung | Speicherarten, die wir nicht kennen (Ceph-RBD, ZFS, Gluster) | | Schutzmaßnahmen der Wiederherstellung | **Ob eine wiederhergestellte VM tatsächlich bootet** | Die letzte Zeile ist die entscheidende. Kein Test in diesem Repository belegt, dass eine wiederhergestellte Maschine startet — und genau das ist der Kern des Produktversprechens („Ein Backup gilt erst als vertrauenswürdig, wenn Wiederherstellbarkeit nachgewiesen wurde"). **Der Proxmox-Provider ist bis zu diesem Nachweis nicht produktionsreif.** ### Der Ablauf auf dem Produktivsystem Vier Schritte, in dieser Reihenfolge. Der erste ist rein lesend und verändert nichts. ```bash # 1. Lesend prüfen, ob Anmeldung, Zertifikat und Rechte stimmen export SYNCOVA_PROXMOX_TOKEN_SECRET="" syncova-proxmox discover --url https://pve.example:8006 \ --token 'syncova@pve!backup' --fingerprint --disks # 2. Den Verbund in der Control Plane einrichten curl -X POST https:///api/v1/proxmox/clusters \ -H "Authorization: Bearer " -H 'Content-Type: application/json' -d '{ "name": "pve-labor", "api_endpoint": "https://pve.example:8006", "api_token_id": "syncova@pve!backup", "api_token_secret": "", "tls_fingerprint": "", "backup_storage_id": "local", "archive_transport": "local", "archive_mount_roots": {"local": "/var/lib/vz"} }' # 3. Bestand aufnehmen und einen Auftrag mit der Gastquelle anlegen curl -X POST https:///api/v1/proxmox/clusters//discover -H "Authorization: Bearer " # 4. Nach der Sicherung: Test-VM löschen, wiederherstellen — und **starten** syncova-proxmox restore-guest --cluster --repository /pfad/zum/repository \ --backup --target-guest qemu/900 --node pve-01 ``` Der vierte Schritt schreibt bewusst **neben** das Original (`--target-guest`): Ein Wiederherstellungstest, der die geprüfte Maschine überschreibt, vernichtet genau das, was er prüfen soll. Gestartet wird nie unaufgefordert — `--start` ist der ausdrückliche Weg. Erst wenn die so entstandene Maschine bootet und ihre Daten stimmen, ist Phase 7 abgeschlossen. ## Die Lücke der Proxmox-API **Proxmox VE kann eine Sicherung anstoßen, aber die entstandene Datei nicht herausgeben.** Es gibt keinen REST-Endpunkt, der den Inhalt eines Datenträgers oder eines Sicherungsarchivs ausliefert. Das ist kein Übersehen unsererseits — die API ist so gebaut. Damit bleiben drei Wege, und alle brauchen Zugriff jenseits der REST-API: 1. Syncova läuft auf dem Proxmox-Knoten — dann genügt ein Dateizugriff. 2. Das Sicherungsziel ist eine Freigabe (NFS, CIFS), die beide Seiten sehen. 3. Zugriff über SSH auf den Knoten. Statt einen dieser Wege festzuschreiben, steht im Code eine Naht: `ArchiveTransport`. Wer sie nicht füllt, bekommt `ErrArchiveTransportMissing` — einen klaren Fehler und kein halbes Backup. **Beide Wege sind umgesetzt** und werden über `archive_transport` des Verbunds gewählt: | Weg | Wann | Was er braucht | | --- | --- | --- | | `local` | Syncova läuft auf dem Knoten oder der Sicherungsspeicher ist eingehängt | Zuordnung Proxmox-Speicherkennung → lokaler Pfad | | `ssh` | alles andere | Anmeldekonto, privater Schlüssel, **Fingerabdruck des Wirtsschlüssels je Knoten** | Die Zuordnung ist ausdrücklich und wird nicht geraten: Ein Speicher heißt auf dem Knoten anders als auf dem Server, der ihn einhängt, und ein geratener Pfad führte entweder ins Leere oder — schlimmer — auf fremde Daten. **Beim SSH-Weg wird ein Knoten ohne hinterlegten Fingerabdruck abgelehnt.** Einen Schalter „Wirtsschlüssel egal" gibt es nicht: Ein Transport, der jeden Schlüssel annimmt, macht aus einem Zwischenangriff eine Einladung — der Angreifer lieferte dann das Archiv, das Syncova für ein Backup hält. Der Fingerabdruck wird als Base64 in der Schreibweise von `ssh-keygen -l` verglichen, mit oder ohne das Präfix `SHA256:`. Das ist ausdrücklich **nicht** dieselbe Normalisierung wie bei TLS-Zertifikaten: Jene macht Kleinbuchstaben, was bei einem Hexwert richtig und bei Base64 falsch ist. ### Die Gegenrichtung: ohne sie gäbe es keine Wiederherstellung Die Proxmox-API nimmt zum Zurückspielen ausschließlich eine **Volumenkennung** entgegen — also eine Datei, die auf einem Speicher des Knotens bereits liegt. Einen Endpunkt zum Hochladen gibt es nicht. `ArchiveWriter` ist deshalb eine **eigene** Schnittstelle, nicht eine Erweiterung von `ArchiveTransport`: Ein Zugriffsweg kann lesend eingerichtet sein, ohne schreiben zu dürfen — eine schreibgeschützt eingehängte Freigabe etwa. Wer nur sichert, braucht das Schreibrecht nicht. Geschrieben wird unter einem Zwischennamen und erst beim Schließen umbenannt (dasselbe Vorgehen wie beim Ablegen eines Blocks in Phase 2). Bricht die Übertragung ab, liegt kein Archiv da, das Proxmox für vollständig hält. ## Was ausdrücklich nicht unterstützt wird `ReadChangedBlocks` meldet `ErrNotSupported`. Die Schmutzbitmap von QEMU ist ausschließlich über das Sicherungsprotokoll des Proxmox Backup Servers oder über QMP am Knoten zugänglich — beides ist kein REST-Endpunkt. Eine leere Bereichsliste zurückzugeben wäre der bequeme Weg und der schlimmste: die Sicherung hielte jede Platte für unverändert und schriebe ein leeres Backup, das aussähe wie ein gelungenes. Der Aufrufer weicht bei `ErrNotSupported` auf das vollständige Lesen aus — langsamer, aber richtig. ### Die Folge: „inkrementell" heißt bei einem Gast etwas anderes Bei einer Dateisystemquelle ist der Gewinn einer Zusatzsicherung **Zeit** (Phase 6): Unveränderte Dateien werden nicht einmal geöffnet. Bei einem Proxmox-Gast ist es genau umgekehrt. vzdump liest jedes Mal die ganze Maschine — es gibt keinen Weg, ihm zu sagen, welche Blöcke sich geändert haben. Gespart wird ausschließlich **Platz**, und zwar durch die inhaltsabhängige Blockfindung und Deduplizierung der Backup Engine: Zwei Sicherungen derselben Maschine unterscheiden sich nur in den tatsächlich geänderten Bereichen, und die Deduplizierung findet sie auch dann wieder, wenn sich das Archiv an einer Stelle verschoben hat. Wer das verwechselt, erwartet nach dem ersten Lauf kurze Sicherungsfenster und plant seinen Nachtbetrieb falsch. ## Anmeldung **API-Token statt Benutzername und Passwort.** Ein Ticket läuft nach zwei Stunden ab und verlangt zusätzlich einen CSRF-Wert für ändernde Aufrufe. Ein API-Token ist dauerhaft gültig, einzeln widerrufbar und mit eigenen Rechten versehbar — genau das, was ein Dienstkonto braucht. ```bash pveum user add syncova@pve pveum aclmod / --user syncova@pve --role PVEAuditor # nur Erfassung pveum user token add syncova@pve backup --privsep 0 ``` Für Sicherung und Wiederherstellung reicht `PVEAuditor` nicht; dort werden `VM.Backup`, `VM.Snapshot`, `Datastore.AllocateSpace` und für den Restore `VM.Allocate` gebraucht. Das Geheimnis kommt aus `SYNCOVA_PROXMOX_TOKEN_SECRET`, niemals als Aufrufparameter: Parameter stehen in der Prozessliste und in der Shell-Historie. ## Selbstsignierte Zertifikate Proxmox liefert ab Werk ein selbstsigniertes Zertifikat. Die übliche Antwort darauf ist `InsecureSkipVerify` — und damit jeder Schutz gegen einen Mittelsmann aufgegeben, auf einer Verbindung, über die ein dauerhaft gültiges Token geht. Die Alternative ist `--fingerprint`: Die Verbindung wird an genau dieses Zertifikat gebunden. Das ist **strenger** als eine gewöhnliche Prüfung gegen eine Zertifizierungsstelle, denn es akzeptiert nur ein einziges Zertifikat statt jedes von irgendeiner Stelle unterschriebene. Der Fingerabdruck steht in der Weboberfläche unter *Certificates* und darf in der dortigen Schreibweise (Großbuchstaben, Doppelpunkte) übernommen werden. `--insecure` bleibt möglich, protokolliert aber bei jedem Verbindungsaufbau eine Warnung — wer die Prüfung abschaltet, soll es in den Protokollen wiederfinden. ## Alles ist asynchron Nahezu jeder ändernde Aufruf in Proxmox antwortet sofort mit einer Aufgabenkennung (UPID), während die Arbeit im Hintergrund läuft. **Wer die Antwort für das Ergebnis hält, meldet einen Snapshot als angelegt, bevor er existiert** — und sichert im schlimmsten Fall gegen ein Abbild, das gerade erst entsteht. Drei Feinheiten, die dabei leicht untergehen: - **Der Statusendpunkt ist knotenbezogen.** Eine Aufgabe auf `pve-02` lässt sich nicht über `pve-01` abfragen. Der Knoten steckt in der UPID; deshalb wird sie zerlegt. - **Fehler stehen im Exit-Status, nicht im HTTP-Status.** Ein erfolgreich abgefragter Status kann eine gescheiterte Aufgabe beschreiben. `OK (with warnings)` gilt als Erfolg, wird aber ausgewiesen. - **Der Exit-Status ist meist ein Satz; die Ursache steht im Aufgabenprotokoll.** Es wird bei Fehlschlag mit abgerufen und der Fehlermeldung angehängt — das erspart den Gang in die Proxmox-Oberfläche. `WaitForTask` ohne Zeitgrenze ist der richtige Standard: Ein `vzdump` über mehrere Terabyte läuft Stunden. Eine willkürliche Grenze bräche die Sicherung genau bei den großen Maschinen ab, für die man sie am nötigsten braucht. Die Zeitgrenze gehört in den Auftrag. Für Snapshots gilt das Gegenteil — sie sind Sekundensache, und zehn Minuten bedeuten, dass der Gastdienst auf eine Anwendung wartet, die sich nicht ruhigstellen lässt. Ein Abbruch über den Kontext beendet nur das Warten, nicht die Aufgabe: Proxmox führt sie zu Ende. Eine halb abgebrochene Wiederherstellung wäre schlimmer als eine zu Ende geführte. ## Plattenerkennung: die drei Fallen Die Proxmox-Konfiguration ist eine Textdatei mit Zeilen wie `scsi0: local-lvm:vm-100-disk-0,size=32G,backup=0`. Drei Dinge müssen daraus richtig gelesen werden: 1. **`backup=0` nimmt eine Platte von der Sicherung aus.** Das ist die wichtigste Angabe überhaupt. Wer sie übergeht, hält eine unvollständige Maschine für vollständig. Sie erscheint als `ExcludedFromBackup` und im `discover --disks` mit ausdrücklicher Warnung. 2. **Ein eingelegtes ISO-Abbild ist keine Platte.** `ide2: local:iso/debian.iso,media=cdrom` hängt an einem Plattenanschluss, trägt aber ein Installationsmedium. Es mitzusichern lüde es ins Backup; es beim Wiederherstellen zu erwarten liesse die Maschine scheitern, wenn es fehlt. 3. **`unused0` ist eine abgehängte, aber vorhandene Platte.** Sie enthält Daten und gehört ins Backup. Proxmox bindet sie nur nicht ein. Dazu Kleinigkeiten mit großer Wirkung: `efidisk0` ist 1 MiB groß — ohne sie startet eine UEFI-Maschine nicht. Größen kommen auch gebrochen vor (`1.5T`). Und Proxmox lässt `bios` weg, wenn SeaBIOS gilt; das Feld leer zu lassen wäre missverständlich, denn eine mit UEFI wiederhergestellte Maschine startet nicht. ## Konsistenzstufe: nie beschönigt Die erreichte Stufe wird **vor** dem Anlegen des Abbilds ermittelt und ins Ergebnis geschrieben: | Lage | Stufe | | --- | --- | | Gast angehalten | anwendungskonsistent — er schreibt nicht mehr | | Läuft, Gastdienst vorhanden, `QuiesceGuest` | anwendungskonsistent | | Läuft, `QuiesceGuest` verlangt, **kein Gastdienst** | **crash consistent** + Warnung | | Läuft, Arbeitsspeicher mitgesichert | dateisystemkonsistent | | Läuft, sonst | crash consistent | Die dritte Zeile ist der Punkt: Wer anwendungskonsistent verlangt und keinen Gastdienst hat, bekommt die niedrigere Stufe ins Manifest geschrieben und eine Warnung ins Protokoll. Ein Backup, das mehr verspricht als es hält, ist gefährlicher als eines, das seine Grenzen kennt. ## Wiederherstellung: die Schutzmaßnahmen Die drei Zielarten unterscheiden sich in wenigen Feldern, aber sehr im Risiko. - **Eine laufende Maschine wird nie überschrieben** (`ErrGuestRunning`). Geschähe es, verlöre man beides: den alten Zustand und den laufenden Betrieb. Proxmox lehnt das ohnehin ab; die Prüfung hier liefert die verständliche Begründung statt einer Meldung aus dem Aufgabenprotokoll. - **Ein vorhandener Gast wird nie stillschweigend überschrieben**, auch wenn er steht — es braucht ausdrückliche Zustimmung (`ErrTargetGuestExists`). - **Der Gast startet nie unaufgefordert.** Eine wiederhergestellte Maschine, die sich mit derselben Adresse ins Netz meldet wie das noch laufende Original, richtet mehr Schaden an als der Ausfall. - **Als neuer Gast** wird oberhalb der Ursprungskennung gesucht, damit die Kopie in der Übersicht neben dem Original steht. Es folgt eine Pflichtwarnung vor doppelten MAC- und IP-Adressen. - **Ein nicht erreichbarer Zielknoten wird sofort abgelehnt** — das erspart einen Abbruch nach Stunden. Die Konfiguration wird **nach** dem Zurückspielen gesetzt (vorher existiert der Gast nicht). Dabei gilt: Netzkarten, BIOS, Prozessorzahl und alles Übrige kommen zurück — **die Plattenzuordnung nicht**. Sie verwiese auf Datenträger am alten Ort, und die Maschine startete nicht. Der Anwender erfährt das als Warnung, statt es zu erraten. Die MAC-Adresse muss erhalten bleiben: Lizenzbindungen und DHCP-Reservierungen hängen daran. Eine wiederhergestellte VM mit neuer MAC ist für das Netz eine andere Maschine. ## Warum die Rohkonfiguration wortgetreu mitgesichert wird `GuestMetadata.RawConfiguration` enthält die Proxmox-Konfiguration unverändert. Eine von uns umgedeutete Fassung verlöre genau die Felder, die wir heute noch nicht kennen — und Proxmox ergänzt mit jeder Version welche. Die gedeuteten Felder (Netzkarten, Startreihenfolge, Firmware) stehen zusätzlich daneben, nicht anstelle. ## Was im Backup eines Gasts liegt Ein gesicherter Gast besteht im Manifest aus **zwei** Objekten: | Pfad | Inhalt | | --- | --- | | `guest/disk-image.vma` | das vzdump-Archiv, unverändert durch Chunking, Kompression und Verschlüsselung | | `guest/configuration.json` | Gastkonfiguration, Plattenliste samt Ausnahmen, Verbund, Zeitpunkt | Der Pfad des Abbilds ist **fest** und nicht aus dem Dateinamen des vzdump-Archivs abgeleitet: Der trägt einen Zeitstempel, und ein Manifestpfad, der sich bei jedem Lauf ändert, machte jede Deduplizierung über Backupgrenzen hinweg unmöglich — die Engine vergleicht Objekte über ihren Pfad. Die Konfiguration liegt daneben und nicht nur in den Attributen des Manifests: Sie muss eine Wiederherstellung überstehen, die ohne Control Plane auskommt. Ohne sie ließe sich die Maschine zwar mit ihren Daten, aber nicht in ihrer Gestalt zurückholen — falsche Netzkarte, anderes BIOS, fehlende serielle Schnittstelle. Eine bootfähige, aber unbrauchbare VM ist kein Restore. **Sie wird vor dem Archiv gelesen.** Danach hieße: nach einem womöglich stundenlangen vzdump — und scheiterte sie dann, wäre der ganze Lauf umsonst gewesen. ### Eine ausgenommene Platte macht den Lauf zum Teilfehler `backup=0` an einer Platte bedeutet: Proxmox sichert sie nicht. Die Wiederherstellung liefert dann eine unvollständige Maschine. Das wird als übergangenes Objekt ausgewiesen und der Lauf damit als `PARTIAL FAILURE` geführt — nicht, weil etwas schiefging, sondern weil niemand die Maschine für vollständig halten soll. ## Endpunkte | Endpunkt | Recht | Zweck | | --- | --- | --- | | `GET /proxmox/clusters` | `providers.read` | Eingerichtete Verbünde | | `POST /proxmox/clusters` | `providers.write` | Verbund einrichten (auditiert) | | `GET /proxmox/clusters/{id}` | `providers.read` | Einzelner Verbund | | `POST /proxmox/clusters/{id}/test` | `providers.write` | Verbindung prüfen | | `POST /proxmox/clusters/{id}/discover` | `providers.write` | Bestand aufnehmen | | `DELETE /proxmox/clusters/{id}` | `providers.write` | Verbund entfernen (auditiert) | | `GET /proxmox/clusters/{id}/hosts` | `providers.read` | Knoten des Verbunds | | `GET /proxmox/clusters/{id}/vms` | `providers.read` | Gäste des Verbunds | | `GET /proxmox/vms/{id}` | `providers.read` | Einzelner Gast | | `GET /virtual-machines` | `providers.read` | Bestand über alle Verbünde | **Prüfung und Bestandsaufnahme hängen am Schreibrecht, obwohl sie nur lesen.** Sie bauen eine Verbindung nach außen auf und erzeugen Last auf dem Verbund; wer nur zusehen darf, soll das nicht auslösen können. **Ein Verbund, auf den noch Sicherungsquellen verweisen, wird nicht gelöscht** (409). Die betroffenen Aufträge verlören sonst ihre Grundlage — stillschweigend, bis zum nächsten Lauf. ## Der Bestand ist eine Momentaufnahme `POST /proxmox/clusters/{id}/discover` schreibt Knoten und Gäste in die Control Plane. Ein Gast, der bei einer Aufnahme fehlt, wird **nicht gelöscht**, sondern mit `missing_since` vermerkt: Er könnte abgeschaltet, verschoben oder tatsächlich entfernt worden sein — und eine gelöschte Zeile nähme die Zuordnung zu vorhandenen Backups mit. Wer sie braucht, braucht sie genau dann, wenn die Maschine weg ist. Ein zweiter Lauf verschiebt den Zeitpunkt nicht; sonst stünde bei einem seit Wochen fehlenden Gast immer „seit gerade eben". Scheitert die Plattenabfrage eines einzelnen Gasts, wird er trotzdem aufgenommen — mit einer Warnung im Ergebnis. Eine Null bei den Platten sähe aus wie eine Maschine ohne Datenträger. ## Zugangsdaten API-Token und SSH-Schlüssel liegen **verschlüsselt** in `proxmox_clusters` (`crypto.SecretStore`, derselbe Schlüsselsatz wie für MFA-Geheimnisse und Repository-Datenschlüssel). Die Token**kennung** steht daneben im Klartext — sie ist kein Geheimnis, steht in jeder Proxmox-Oberfläche und wird zur Fehlersuche gebraucht. `LoadCredentials` ist ein eigener Aufruf und nicht Teil von `GetCluster`: Wer einen Verbund nur anzeigt, soll die Geheimnisse gar nicht erst im Speicher haben. In keiner API-Antwort erscheinen sie. Zwei Regeln stehen als CHECK in der Datenbank, nicht nur im Code: Ein `ssh`-Verbund ohne Anmeldekonto oder Schlüssel und ein `local`-Verbund ohne Speicherzuordnung werden abgelehnt. Im Code müsste jede schreibende Stelle sie einhalten — eine vergisst es. ## Offene Punkte - **Der E2E-Nachweis auf echter Hardware (siehe oben).** Ohne ihn ist der Provider nicht freigegeben. Das ist der einzige verbleibende Punkt, der die Phase offen hält. - **Kein Changed Block Tracking** — siehe oben; kommt nur mit PBS-Protokoll oder QMP. Die Folge steht dort: „inkrementell" spart bei einem Gast Platz, keine Zeit. - **LXC ist erfasst und wird über denselben Weg gesichert wie QEMU**, aber nie gegen einen echten Container geprüft. `vzdump` behandelt Container anders; ob die Wiederherstellung eines LXC über `/nodes/{n}/lxc` durchläuft, ist offen. - **Keine eigene Oberfläche.** Verbünde werden über die API eingerichtet, nicht über die Weboberfläche. Die Seite „Virtuelle Maschinen" steht seit Phase 12 als benannte Lücke. - **Kein Wiederherstellungstest als eigener Auftrag** (PROMPT.md: kein Backup-Feature ohne Recovery-Test). Die Prüfmaschine aus Phase 10 kennt Gäste nicht; `syncova-proxmox restore-guest --target-guest` ist der Weg von Hand. - **Der SSH-Weg ist ungeprüft.** Er übersetzt und seine Fingerabdruckprüfung ist getestet, aber es hat nie eine echte SSH-Verbindung zu einem Proxmox-Knoten gegeben. Der `local`-Weg dagegen läuft in den Tests durch. - **Kein Vergleich der Archivgröße.** Meldet vzdump Erfolg und liefert ein verkürztes Archiv, fiele das erst bei der Wiederherstellung auf. Proxmox nennt die erwartete Größe in der Bestandsliste des Speichers — sie zu vergleichen wäre der nächste sinnvolle Schritt.