syncova-backup/packages/providers/provider.go
Jerrit Fritzsche 610719c316
Some checks failed
CI / Backend (Go) (push) Failing after 3m7s
CI / Frontend (React/TypeScript) (push) Successful in 37s
CI / Sicherheitsprüfungen (push) Successful in 44s
Syncova Backups V1
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>
2026-08-17 09:10:54 +02:00

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")