syncova-backup/packages/backupformat/header.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

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"`
}