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>
149 lines
5.8 KiB
Go
149 lines
5.8 KiB
Go
package backupformat
|
|
|
|
import (
|
|
"time"
|
|
)
|
|
|
|
// ContainerHeader steht am Anfang jedes Containers.
|
|
//
|
|
// Er enthält genau die Angaben, die zum Deuten des restlichen Inhalts nötig
|
|
// sind. Alles Weitere steht in den Abschnitten. Diese Trennung hält den Header
|
|
// klein — er wird bei jedem Öffnen gelesen, auch wenn nur die Version
|
|
// interessiert (SYNCOVA_ARCHITECTURE.md §8).
|
|
type ContainerHeader struct {
|
|
// BackupID ist die Kennung des enthaltenen Backups.
|
|
BackupID string `json:"backup_id"`
|
|
// ChainID verbindet das Backup mit seiner Kette.
|
|
ChainID string `json:"chain_id"`
|
|
// ParentBackupID benennt das Elternbackup einer Zusatzsicherung.
|
|
//
|
|
// Ohne diese Angabe liesse sich eine Kette aus einzelnen Containern nicht
|
|
// rekonstruieren.
|
|
ParentBackupID string `json:"parent_backup_id,omitempty"`
|
|
// SourceID ist die Kennung der gesicherten Quelle.
|
|
SourceID string `json:"source_id"`
|
|
// SourceType benennt die Art der Quelle, z. B. proxmox_vm oder filesystem.
|
|
SourceType string `json:"source_type"`
|
|
// CreatedAt ist der Zeitpunkt der Erzeugung in UTC.
|
|
CreatedAt time.Time `json:"created_at"`
|
|
// CreatedByVersion ist die erzeugende Programmversion.
|
|
//
|
|
// Bei einer Notfallanalyse lässt sich damit feststellen, welche Software
|
|
// den Container geschrieben hat.
|
|
CreatedByVersion string `json:"created_by_version"`
|
|
// RepositoryID benennt das Ursprungs-Repository.
|
|
RepositoryID string `json:"repository_id,omitempty"`
|
|
// Encryption beschreibt die Verschlüsselung des Inhalts.
|
|
Encryption EncryptionMetadata `json:"encryption"`
|
|
// Compression beschreibt die Kompression des Inhalts.
|
|
Compression CompressionMetadata `json:"compression"`
|
|
}
|
|
|
|
// EncryptionMetadata beschreibt die Verschlüsselung eines Containers.
|
|
//
|
|
// Die Angaben stehen bewusst im Klartext im Header: ohne sie liesse sich nicht
|
|
// einmal feststellen, welcher Schlüssel benötigt wird. Sie verraten nichts über
|
|
// den Inhalt (PROMPT.md §12).
|
|
type EncryptionMetadata struct {
|
|
// Algorithm benennt das Verfahren; leer bedeutet unverschlüsselt.
|
|
Algorithm string `json:"algorithm,omitempty"`
|
|
// KeyVersion benennt den zur Entschlüsselung nötigen Schlüssel.
|
|
//
|
|
// Ohne diese Angabe wäre ein Container nach einer Schlüsselrotation nicht
|
|
// mehr zuzuordnen und damit unlesbar (PROMPT.md §143).
|
|
KeyVersion string `json:"key_version,omitempty"`
|
|
// KeyDerivation benennt das Verfahren der Schlüsselableitung.
|
|
KeyDerivation string `json:"key_derivation,omitempty"`
|
|
}
|
|
|
|
// IsEncrypted meldet, ob der Inhalt verschlüsselt ist.
|
|
func (encryptionMetadata EncryptionMetadata) IsEncrypted() bool {
|
|
return encryptionMetadata.Algorithm != ""
|
|
}
|
|
|
|
// CompressionMetadata beschreibt die Kompression eines Containers.
|
|
type CompressionMetadata struct {
|
|
// Algorithm benennt das Verfahren; leer bedeutet unkomprimiert.
|
|
Algorithm string `json:"algorithm,omitempty"`
|
|
// Level ist die verwendete Stufe.
|
|
Level int `json:"level,omitempty"`
|
|
}
|
|
|
|
// IsCompressed meldet, ob der Inhalt komprimiert ist.
|
|
func (compressionMetadata CompressionMetadata) IsCompressed() bool {
|
|
return compressionMetadata.Algorithm != ""
|
|
}
|
|
|
|
// ChunkIndexEntry beschreibt einen Chunk im Container.
|
|
//
|
|
// Das Verzeichnis erlaubt es, einen einzelnen Chunk zu finden, ohne den
|
|
// gesamten Datenbereich zu lesen — Voraussetzung für eine Wiederherstellung
|
|
// einzelner Dateien aus einem großen Backup.
|
|
type ChunkIndexEntry struct {
|
|
// Identifier ist der Inhaltshash des Chunks.
|
|
Identifier string `json:"id"`
|
|
// ContainerOffset ist die Position im Datenbereich des Containers.
|
|
ContainerOffset int64 `json:"container_offset"`
|
|
// StoredLength ist die Länge der abgelegten Daten in Byte.
|
|
StoredLength int64 `json:"stored_length"`
|
|
// LogicalLength ist die Länge der ursprünglichen Daten in Byte.
|
|
LogicalLength int64 `json:"logical_length"`
|
|
}
|
|
|
|
// ChunkIndex ist das Verzeichnis aller Chunks eines Containers.
|
|
type ChunkIndex struct {
|
|
// Entries sind die Chunks in ihrer Ablagereihenfolge.
|
|
Entries []ChunkIndexEntry `json:"entries"`
|
|
}
|
|
|
|
// FindByIdentifier sucht einen Chunk anhand seiner Kennung.
|
|
func (chunkIndex *ChunkIndex) FindByIdentifier(chunkIdentifier string) (ChunkIndexEntry, bool) {
|
|
for _, indexEntry := range chunkIndex.Entries {
|
|
if indexEntry.Identifier == chunkIdentifier {
|
|
return indexEntry, true
|
|
}
|
|
}
|
|
|
|
return ChunkIndexEntry{}, false
|
|
}
|
|
|
|
// TotalStoredBytes liefert die Gesamtgröße der abgelegten Chunks.
|
|
func (chunkIndex *ChunkIndex) TotalStoredBytes() int64 {
|
|
var totalBytes int64
|
|
for _, indexEntry := range chunkIndex.Entries {
|
|
totalBytes += indexEntry.StoredLength
|
|
}
|
|
|
|
return totalBytes
|
|
}
|
|
|
|
// ContainerFooter schließt einen Container ab.
|
|
//
|
|
// Er steht am Ende, weil erst dort alle Prüfsummen feststehen. Genau darin
|
|
// liegt seine Aussagekraft: ein abgebrochener Schreib- oder Übertragungsvorgang
|
|
// hinterlässt einen Container ohne Footer, der damit zuverlässig als
|
|
// unvollständig erkannt wird (SYNCOVA_ARCHITECTURE.md §8).
|
|
type ContainerFooter struct {
|
|
// ManifestHash ist die Prüfsumme des Manifestabschnitts.
|
|
ManifestHash string `json:"manifest_hash"`
|
|
// ContentHash ist die Prüfsumme über Header und alle Abschnitte.
|
|
//
|
|
// Sie belegt, dass der Container als Ganzes unverändert ist — auch dann,
|
|
// wenn ein Angreifer einen einzelnen Abschnitt samt seiner Prüfsumme
|
|
// ausgetauscht hätte.
|
|
ContentHash string `json:"content_hash"`
|
|
// SectionCount ist die Zahl der geschriebenen Abschnitte.
|
|
//
|
|
// Sie deckt einen entfernten Abschnitt auf, selbst wenn der Rest stimmig wäre.
|
|
SectionCount int `json:"section_count"`
|
|
// TotalBytes ist die Gesamtgröße des Containers ohne den Footer.
|
|
TotalBytes int64 `json:"total_bytes"`
|
|
// CompletedAt ist der Abschlusszeitpunkt in UTC.
|
|
CompletedAt time.Time `json:"completed_at"`
|
|
// Complete ist der ausdrückliche Abschlussvermerk.
|
|
//
|
|
// Ein Container ohne diesen Vermerk beschreibt kein verwendbares Backup
|
|
// (PROMPT.md §140).
|
|
Complete bool `json:"complete"`
|
|
}
|