Enterprise-Backup-, Recovery-, Verification-, Security- und Monitoring-Plattform fuer Proxmox VE, Windows, Linux und Dateisysteme. Der Leitsatz, der fast jede Entscheidung erklaert: Ein Backup gilt erst als vertrauenswuerdig, wenn Integritaet geprueft und Wiederherstellbarkeit nachgewiesen wurde. Deshalb steigt ein Wiederherstellungspunkt erst nach einem tatsaechlich durchgefuehrten Restore-Test auf "recoverable", und Unbekanntes geht in keine Bewertung als "gut" ein. Umfang (Phasen 0-23): - Repository Engine: inhaltsadressierte Bloecke, atomares Commit-Protokoll, Katalogaufbau allein aus den Manifesten — ohne Datenbank - Backup Engine: inhaltsabhaengiges Chunking, Deduplizierung trotz Verschluesselung, zstd, AES-256-GCM, Streaming mit Gegendruck - Agenten fuer Windows und Linux mit Auftragsabholung (Pull-Modell) - Proxmox-Provider mit beiden Zugriffswegen auf die Sicherungsarchive - Scheduler, Recovery Engine mit Pruefpunkt, Verification, Unveraenderlichkeit - Weboberflaeche, Kennzahlen, Meldungen, Berichte, Security Center, Ransomware-Heuristik (meldet, handelt nie) - Disaster Recovery, Haertung, Leistungsmessung, Chaos Testing - Eingefrorene Vertraege fuer API, Migrationen, Backup-Format und Repository - Auslieferungspaket fuer linux/amd64, linux/arm64 und windows/amd64 Nicht enthalten und als solches gekennzeichnet: Kapazitaetsprognose, Backup Copy, Changed Block Tracking bei Proxmox, erweiterte Attribute und ACLs. Gebaut, aber nie auf echter Hardware gefahren: der Windows-Dienst, die systemd-Einheit und der verpflichtende Proxmox-Meilenstein — ob eine wiederhergestellte VM startet, ist ungeprueft. Einzelheiten in CHANGELOG.md und docs/release-candidate.md. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
405 lines
16 KiB
Go
405 lines
16 KiB
Go
// Package providers beschreibt die herstellerneutrale Schnittstelle zu
|
|
// Virtualisierungsplattformen.
|
|
//
|
|
// Der Zuschnitt folgt SYNCOVA_ARCHITECTURE.md §5: Proxmox ist in V1 der einzige
|
|
// Provider, VMware und Hyper-V müssen sich später ergänzen lassen, ohne die
|
|
// Backup Engine anzufassen. Deshalb enthält dieses Paket ausschließlich
|
|
// allgemeine Begriffe — kein Feld heißt „vmid", keine Konstante nennt eine
|
|
// Proxmox-Speicherart.
|
|
//
|
|
// Die Abhängigkeitsrichtung ist einseitig: Provider dürfen von der Engine
|
|
// gelesen werden, die Engine niemals vom Provider.
|
|
package providers
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"io"
|
|
"time"
|
|
)
|
|
|
|
// GuestType benennt die Art eines virtuellen Gasts.
|
|
type GuestType string
|
|
|
|
const (
|
|
// GuestTypeVirtualMachine ist eine vollwertige virtuelle Maschine.
|
|
GuestTypeVirtualMachine GuestType = "virtual_machine"
|
|
// GuestTypeContainer ist ein Betriebssystemcontainer.
|
|
//
|
|
// Proxmox kennt neben QEMU auch LXC. Der Unterschied ist für die Sicherung
|
|
// wesentlich: ein Container hat kein BIOS und keine virtuellen Platten im
|
|
// selben Sinn.
|
|
GuestTypeContainer GuestType = "container"
|
|
)
|
|
|
|
// PowerState ist der Betriebszustand eines Gasts.
|
|
type PowerState string
|
|
|
|
const (
|
|
// PowerStateRunning bezeichnet einen laufenden Gast.
|
|
PowerStateRunning PowerState = "running"
|
|
// PowerStateStopped bezeichnet einen angehaltenen Gast.
|
|
PowerStateStopped PowerState = "stopped"
|
|
// PowerStatePaused bezeichnet einen pausierten Gast.
|
|
PowerStatePaused PowerState = "paused"
|
|
// PowerStateUnknown bezeichnet einen nicht ermittelbaren Zustand.
|
|
//
|
|
// Er wird ausdrücklich geführt statt „stopped" anzunehmen: eine falsch als
|
|
// angehalten geltende VM würde ohne Rückfrage überschrieben.
|
|
PowerStateUnknown PowerState = "unknown"
|
|
)
|
|
|
|
// ConsistencyLevel beschreibt, wie konsistent die gesicherten Daten sind.
|
|
//
|
|
// Die Stufe gehört ins Manifest, weil sie darüber entscheidet, was eine
|
|
// Wiederherstellung wert ist. Ein Backup ohne diese Angabe verspricht mehr, als
|
|
// es halten kann.
|
|
type ConsistencyLevel string
|
|
|
|
const (
|
|
// ConsistencyCrashConsistent entspricht dem Zustand nach einem Stromausfall.
|
|
//
|
|
// Dateisysteme erholen sich davon meist; Datenbanken nicht immer.
|
|
ConsistencyCrashConsistent ConsistencyLevel = "crash_consistent"
|
|
// ConsistencyFilesystemConsistent bedeutet geleerte Dateisystempuffer.
|
|
ConsistencyFilesystemConsistent ConsistencyLevel = "filesystem_consistent"
|
|
// ConsistencyApplicationConsistent bedeutet zusätzlich ruhiggestellte Anwendungen.
|
|
ConsistencyApplicationConsistent ConsistencyLevel = "application_consistent"
|
|
)
|
|
|
|
// Cluster ist ein Verbund von Wirten.
|
|
type Cluster struct {
|
|
// Identifier ist die stabile Kennung des Verbunds.
|
|
Identifier string `json:"identifier"`
|
|
// Name ist die sprechende Bezeichnung.
|
|
Name string `json:"name"`
|
|
// Version ist die Version der Verwaltungssoftware.
|
|
Version string `json:"version,omitempty"`
|
|
// HostCount ist die Zahl der Wirte im Verbund.
|
|
HostCount int `json:"host_count"`
|
|
// Quorate meldet, ob der Verbund beschlussfähig ist.
|
|
//
|
|
// Ein Verbund ohne Quorum nimmt keine ändernden Aufrufe an. Ohne diese
|
|
// Angabe liefe eine Sicherung in eine Reihe unverständlicher Fehler.
|
|
Quorate bool `json:"quorate"`
|
|
}
|
|
|
|
// Host ist ein einzelner Virtualisierungswirt.
|
|
type Host struct {
|
|
// Identifier ist die stabile Kennung des Wirts.
|
|
Identifier string `json:"identifier"`
|
|
// Name ist der Knotenname.
|
|
Name string `json:"name"`
|
|
// ClusterID benennt den Verbund, dem der Wirt angehört.
|
|
ClusterID string `json:"cluster_id,omitempty"`
|
|
// Online meldet die Erreichbarkeit.
|
|
Online bool `json:"online"`
|
|
// CPUCount ist die Zahl der logischen Prozessoren.
|
|
CPUCount int `json:"cpu_count,omitempty"`
|
|
// MemoryBytes ist der Gesamtarbeitsspeicher.
|
|
MemoryBytes int64 `json:"memory_bytes,omitempty"`
|
|
// Version ist die Version der Wirtsoftware.
|
|
Version string `json:"version,omitempty"`
|
|
}
|
|
|
|
// Guest ist eine virtuelle Maschine oder ein Container.
|
|
type Guest struct {
|
|
// Identifier ist die plattformweite Kennung.
|
|
Identifier string `json:"identifier"`
|
|
// Name ist die sprechende Bezeichnung.
|
|
Name string `json:"name"`
|
|
// GuestType ist die Art des Gasts.
|
|
GuestType GuestType `json:"guest_type"`
|
|
// HostID benennt den Wirt, auf dem der Gast liegt.
|
|
HostID string `json:"host_id"`
|
|
// PowerState ist der Betriebszustand.
|
|
PowerState PowerState `json:"power_state"`
|
|
// CPUCount ist die Zahl zugewiesener Prozessoren.
|
|
CPUCount int `json:"cpu_count,omitempty"`
|
|
// MemoryBytes ist der zugewiesene Arbeitsspeicher.
|
|
MemoryBytes int64 `json:"memory_bytes,omitempty"`
|
|
// OperatingSystem ist das gemeldete Betriebssystem, sofern bekannt.
|
|
OperatingSystem string `json:"operating_system,omitempty"`
|
|
// Tags sind plattformseitig vergebene Etiketten.
|
|
//
|
|
// Sie erlauben es, Sicherungsaufträge nach Etikett statt nach Kennung zu
|
|
// bilden — eine neue VM wird damit ohne Konfigurationsänderung erfasst.
|
|
Tags []string `json:"tags,omitempty"`
|
|
// Protected meldet einen plattformseitigen Löschschutz.
|
|
Protected bool `json:"protected,omitempty"`
|
|
}
|
|
|
|
// Disk ist eine virtuelle Platte eines Gasts.
|
|
type Disk struct {
|
|
// Identifier ist die Kennung innerhalb des Gasts, etwa der Gerätename.
|
|
Identifier string `json:"identifier"`
|
|
// StorageID benennt den Speicher, auf dem die Platte liegt.
|
|
StorageID string `json:"storage_id"`
|
|
// Volume ist der plattformseitige Bezeichner des Datenträgers.
|
|
Volume string `json:"volume"`
|
|
// SizeBytes ist die eingerichtete Größe.
|
|
SizeBytes int64 `json:"size_bytes"`
|
|
// Format ist das Abbildformat, etwa qcow2 oder raw.
|
|
Format string `json:"format,omitempty"`
|
|
// ExcludedFromBackup meldet eine von der Sicherung ausgenommene Platte.
|
|
//
|
|
// Das ist keine Nebensache: Eine Wiederherstellung liefert dann eine
|
|
// unvollständige Maschine. Wer es nicht weiß, hält sie für vollständig.
|
|
ExcludedFromBackup bool `json:"excluded_from_backup"`
|
|
// ReadOnly meldet einen schreibgeschützten Datenträger, etwa ein Abbild.
|
|
ReadOnly bool `json:"read_only,omitempty"`
|
|
}
|
|
|
|
// GuestMetadata sind die Konfigurationsdaten eines Gasts.
|
|
//
|
|
// Ohne sie liesse sich eine Maschine zwar mit ihren Daten, aber nicht in ihrer
|
|
// Gestalt wiederherstellen — falsche Netzkarte, fehlende serielle Schnittstelle,
|
|
// anderes BIOS. Eine bootfähige, aber unbrauchbare VM ist kein Restore.
|
|
type GuestMetadata struct {
|
|
// GuestID ist die Kennung des Gasts.
|
|
GuestID string `json:"guest_id"`
|
|
// RawConfiguration ist die unveränderte Konfiguration der Plattform.
|
|
//
|
|
// Sie wird wortgetreu mitgesichert. Eine von uns umgedeutete Fassung
|
|
// verlöre genau die Felder, die wir heute noch nicht kennen.
|
|
RawConfiguration map[string]string `json:"raw_configuration"`
|
|
// FirmwareType benennt BIOS oder UEFI.
|
|
FirmwareType string `json:"firmware_type,omitempty"`
|
|
// NetworkInterfaces sind die Netzwerkkarten in ihrer Reihenfolge.
|
|
NetworkInterfaces []NetworkInterface `json:"network_interfaces,omitempty"`
|
|
// BootOrder ist die Startreihenfolge der Geräte.
|
|
BootOrder []string `json:"boot_order,omitempty"`
|
|
// GuestAgentEnabled meldet einen eingerichteten Gastdienst.
|
|
//
|
|
// Ohne ihn ist keine anwendungskonsistente Sicherung möglich.
|
|
GuestAgentEnabled bool `json:"guest_agent_enabled"`
|
|
}
|
|
|
|
// NetworkInterface ist eine virtuelle Netzwerkkarte.
|
|
type NetworkInterface struct {
|
|
// Identifier ist der Gerätename.
|
|
Identifier string `json:"identifier"`
|
|
// MACAddress ist die Hardwareadresse.
|
|
//
|
|
// Sie muss erhalten bleiben: Lizenzbindungen und DHCP-Reservierungen hängen
|
|
// daran. Eine wiederhergestellte VM mit neuer MAC ist für das Netz eine
|
|
// andere Maschine.
|
|
MACAddress string `json:"mac_address,omitempty"`
|
|
// Bridge ist die Netzbrücke des Wirts.
|
|
Bridge string `json:"bridge,omitempty"`
|
|
// Model ist das nachgebildete Kartenmodell.
|
|
Model string `json:"model,omitempty"`
|
|
// VLANTag ist das VLAN-Etikett; 0 bedeutet keines.
|
|
VLANTag int `json:"vlan_tag,omitempty"`
|
|
}
|
|
|
|
// Snapshot ist ein Zeitpunktabbild eines Gasts.
|
|
type Snapshot struct {
|
|
// Identifier ist der Name des Abbilds.
|
|
Identifier string `json:"identifier"`
|
|
// GuestID ist der zugehörige Gast.
|
|
GuestID string `json:"guest_id"`
|
|
// CreatedAt ist der Erstellungszeitpunkt in UTC.
|
|
CreatedAt time.Time `json:"created_at"`
|
|
// Description erklärt den Zweck des Abbilds.
|
|
Description string `json:"description,omitempty"`
|
|
// IncludesMemory meldet ein mitgesichertes Arbeitsspeicherabbild.
|
|
IncludesMemory bool `json:"includes_memory"`
|
|
// ConsistencyLevel ist die erreichte Konsistenzstufe.
|
|
ConsistencyLevel ConsistencyLevel `json:"consistency_level"`
|
|
}
|
|
|
|
// SnapshotOptions steuern das Anlegen eines Abbilds.
|
|
type SnapshotOptions struct {
|
|
// Name ist der gewünschte Name.
|
|
Name string
|
|
// Description erklärt den Zweck.
|
|
Description string
|
|
// IncludeMemory sichert den Arbeitsspeicher mit.
|
|
IncludeMemory bool
|
|
// QuiesceGuest stellt die Anwendungen im Gast ruhig.
|
|
//
|
|
// Das setzt einen laufenden Gastdienst voraus. Fehlt er, meldet der
|
|
// Provider das — er senkt die Konsistenzstufe niemals stillschweigend.
|
|
QuiesceGuest bool
|
|
// Timeout begrenzt die Wartezeit; 0 wählt den Standard des Providers.
|
|
Timeout time.Duration
|
|
}
|
|
|
|
// BlockRange beschreibt einen zusammenhängenden Bereich einer Platte.
|
|
type BlockRange struct {
|
|
// OffsetBytes ist der Beginn des Bereichs.
|
|
OffsetBytes int64 `json:"offset_bytes"`
|
|
// LengthBytes ist die Länge des Bereichs.
|
|
LengthBytes int64 `json:"length_bytes"`
|
|
}
|
|
|
|
// ChangedBlockResult ist das Ergebnis einer Abfrage geänderter Blöcke.
|
|
type ChangedBlockResult struct {
|
|
// Ranges sind die geänderten Bereiche in aufsteigender Reihenfolge.
|
|
Ranges []BlockRange `json:"ranges"`
|
|
// BlockSizeBytes ist die Granularität der Nachverfolgung.
|
|
BlockSizeBytes int64 `json:"block_size_bytes"`
|
|
// ChangeTrackingID ist die Kennung für die nächste Abfrage.
|
|
ChangeTrackingID string `json:"change_tracking_id,omitempty"`
|
|
// FullReadRequired meldet, dass die gesamte Platte gelesen werden muss.
|
|
//
|
|
// Das ist der ehrliche Ausgang, wenn die Nachverfolgung zurückgesetzt
|
|
// wurde. Eine leere Bereichsliste zurückzugeben wäre bequem und falsch:
|
|
// die Sicherung hielte die Platte für unverändert.
|
|
FullReadRequired bool `json:"full_read_required"`
|
|
}
|
|
|
|
// RestoreTargetKind benennt das Ziel einer Wiederherstellung.
|
|
type RestoreTargetKind string
|
|
|
|
const (
|
|
// RestoreToOriginalHost stellt am Ursprungsort wieder her.
|
|
RestoreToOriginalHost RestoreTargetKind = "original_host"
|
|
// RestoreToAlternateHost stellt auf einem anderen Wirt wieder her.
|
|
RestoreToAlternateHost RestoreTargetKind = "alternate_host"
|
|
// RestoreAsNewGuest legt einen neuen Gast an und lässt das Original bestehen.
|
|
RestoreAsNewGuest RestoreTargetKind = "new_guest"
|
|
)
|
|
|
|
// RestoreRequest beschreibt eine Wiederherstellung.
|
|
type RestoreRequest struct {
|
|
// TargetKind ist die Art des Ziels.
|
|
TargetKind RestoreTargetKind
|
|
// SourceGuestID ist der ursprüngliche Gast.
|
|
SourceGuestID string
|
|
// TargetGuestID ist die Kennung des Ziels; leer wählt die nächste freie.
|
|
TargetGuestID string
|
|
// TargetHostID ist der Zielwirt; leer wählt den Ursprungswirt.
|
|
TargetHostID string
|
|
// TargetStorageID lenkt die Platten auf einen anderen Speicher.
|
|
TargetStorageID string
|
|
// ArchiveReference benennt das wiederherzustellende Abbild.
|
|
ArchiveReference string
|
|
// Metadata ist die wiederherzustellende Konfiguration.
|
|
Metadata *GuestMetadata
|
|
// StartAfterRestore startet den Gast nach der Wiederherstellung.
|
|
//
|
|
// Standardmäßig bleibt er aus. Eine wiederhergestellte Maschine, die sich
|
|
// unaufgefordert mit derselben Adresse ins Netz meldet wie das noch
|
|
// laufende Original, richtet mehr Schaden an als der Ausfall.
|
|
StartAfterRestore bool
|
|
// OverwriteExisting erlaubt das Überschreiben eines vorhandenen Gasts.
|
|
OverwriteExisting bool
|
|
// ProgressCallback meldet den Fortschritt.
|
|
ProgressCallback func(RestoreProgress)
|
|
}
|
|
|
|
// RestoreProgress meldet den Stand einer Wiederherstellung.
|
|
type RestoreProgress struct {
|
|
// Stage benennt den aktuellen Schritt.
|
|
Stage string `json:"stage"`
|
|
// PercentComplete ist der Fortschritt in Prozent, sofern bekannt.
|
|
PercentComplete float64 `json:"percent_complete"`
|
|
// Message ist eine erläuternde Meldung der Plattform.
|
|
Message string `json:"message,omitempty"`
|
|
}
|
|
|
|
// RestoreResult beschreibt eine abgeschlossene Wiederherstellung.
|
|
type RestoreResult struct {
|
|
// GuestID ist die Kennung des wiederhergestellten Gasts.
|
|
GuestID string `json:"guest_id"`
|
|
// HostID ist der Wirt, auf dem er liegt.
|
|
HostID string `json:"host_id"`
|
|
// Started meldet, ob der Gast gestartet wurde.
|
|
Started bool `json:"started"`
|
|
// Duration ist die Gesamtdauer.
|
|
Duration time.Duration `json:"duration"`
|
|
// Warnings sind Hinweise, die den Erfolg nicht aufheben, aber Beachtung
|
|
// verlangen — etwa eine auf einen anderen Speicher verschobene Platte.
|
|
Warnings []string `json:"warnings,omitempty"`
|
|
}
|
|
|
|
// DiskReadRequest beschreibt den Lesezugriff auf Plattendaten.
|
|
type DiskReadRequest struct {
|
|
// GuestID ist der Gast.
|
|
GuestID string
|
|
// SnapshotID ist das Abbild, aus dem gelesen wird.
|
|
SnapshotID string
|
|
// DiskIdentifier benennt die Platte.
|
|
DiskIdentifier string
|
|
// Ranges beschränkt das Lesen auf bestimmte Bereiche; leer liest alles.
|
|
Ranges []BlockRange
|
|
}
|
|
|
|
// VirtualizationProvider ist die herstellerneutrale Schnittstelle
|
|
// (SYNCOVA_ARCHITECTURE.md §5).
|
|
//
|
|
// Jede Umsetzung muss zwei Regeln einhalten:
|
|
//
|
|
// 1. Nicht unterstützte Fähigkeiten werden mit ErrNotSupported gemeldet, nicht
|
|
// mit einem leeren Ergebnis umgangen. Ein leerer Rückgabewert sähe aus wie
|
|
// „nichts zu tun" und führte zu einem Backup, das nichts enthält.
|
|
// 2. Kein Aufruf verändert den Gast, ohne dass der Aufrufer es verlangt hat.
|
|
type VirtualizationProvider interface {
|
|
// Name benennt den Provider für Protokolle und Oberfläche.
|
|
Name() string
|
|
|
|
// Connect stellt die Verbindung her und prüft die Anmeldedaten.
|
|
Connect(connectContext context.Context) error
|
|
|
|
// Disconnect gibt die Verbindung frei.
|
|
Disconnect() error
|
|
|
|
// ListClusters ermittelt die erreichbaren Verbünde.
|
|
ListClusters(listContext context.Context) ([]Cluster, error)
|
|
|
|
// ListHosts ermittelt die Wirte eines Verbunds.
|
|
ListHosts(listContext context.Context, clusterID string) ([]Host, error)
|
|
|
|
// ListVMs ermittelt die Gäste; ein leerer Wirt bedeutet alle Wirte.
|
|
ListVMs(listContext context.Context, hostID string) ([]Guest, error)
|
|
|
|
// GetVMInfo liefert die Angaben zu einem einzelnen Gast.
|
|
GetVMInfo(infoContext context.Context, guestID string) (*Guest, error)
|
|
|
|
// GetVMDisks liefert die Platten eines Gasts.
|
|
GetVMDisks(diskContext context.Context, guestID string) ([]Disk, error)
|
|
|
|
// GetVMMetaData liefert die Konfiguration eines Gasts.
|
|
GetVMMetaData(metadataContext context.Context, guestID string) (*GuestMetadata, error)
|
|
|
|
// CreateSnapshot legt ein Zeitpunktabbild an.
|
|
CreateSnapshot(snapshotContext context.Context, guestID string, snapshotOptions SnapshotOptions) (*Snapshot, error)
|
|
|
|
// RemoveSnapshot entfernt ein Zeitpunktabbild.
|
|
RemoveSnapshot(removeContext context.Context, guestID string, snapshotID string) error
|
|
|
|
// ReadChangedBlocks ermittelt die seit einer früheren Sicherung geänderten
|
|
// Bereiche.
|
|
//
|
|
// Ist keine Nachverfolgung verfügbar, wird ErrNotSupported gemeldet. Der
|
|
// Aufrufer liest dann die ganze Platte — langsamer, aber richtig.
|
|
ReadChangedBlocks(blockContext context.Context, guestID string, diskIdentifier string, previousTrackingID string) (*ChangedBlockResult, error)
|
|
|
|
// OpenDisk öffnet die Plattendaten zum Lesen.
|
|
//
|
|
// Der zurückgegebene Datenstrom wird von der Backup Engine verarbeitet; der
|
|
// Provider kennt weder Chunking noch Verschlüsselung.
|
|
OpenDisk(openContext context.Context, readRequest DiskReadRequest) (io.ReadCloser, error)
|
|
|
|
// RestoreVM stellt einen Gast wieder her.
|
|
RestoreVM(restoreContext context.Context, restoreRequest RestoreRequest) (*RestoreResult, error)
|
|
}
|
|
|
|
// ErrNotSupported meldet eine von dieser Plattform nicht unterstützte Fähigkeit.
|
|
//
|
|
// Der Fehler ist ein vollwertiges Ergebnis, kein Versagen: er erlaubt es dem
|
|
// Aufrufer, auf ein anderes Verfahren auszuweichen. Ihn zu verschweigen wäre
|
|
// ein Fake-Feature (PROMPT.md §138).
|
|
var ErrNotSupported = errors.New("diese fähigkeit wird von der plattform nicht unterstützt")
|
|
|
|
// ErrGuestNotFound meldet einen nicht vorhandenen Gast.
|
|
var ErrGuestNotFound = errors.New("der gast wurde auf der plattform nicht gefunden")
|
|
|
|
// ErrNotConnected meldet einen Aufruf ohne bestehende Verbindung.
|
|
var ErrNotConnected = errors.New("es besteht keine verbindung zur plattform")
|
|
|
|
// ErrGuestRunning meldet einen Eingriff, der einen angehaltenen Gast verlangt.
|
|
var ErrGuestRunning = errors.New("der gast läuft; der vorgang verlangt einen angehaltenen gast")
|