syncova-backup/packages/jobs/executor.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

146 lines
5.3 KiB
Go

package jobs
import (
"context"
"time"
"github.com/google/uuid"
"github.com/syncova/syncova/packages/scheduler"
)
// ExecutionRequest beschreibt einen auszuführenden Sicherungslauf.
type ExecutionRequest struct {
// Job ist der auszuführende Auftrag.
Job *Job
// RunID ist die Kennung des Laufs.
RunID uuid.UUID
// CorrelationID verbindet den Lauf mit seinen Protokollzeilen.
CorrelationID uuid.UUID
// AttemptNumber ist die Nummer des Versuchs, beginnend bei 1.
AttemptNumber int
// ProgressCallback meldet den Fortschritt.
//
// Die Schleife nutzt ihn für die Lebendmeldung: Ein Lauf, der stundenlang
// nichts von sich hören lässt, wäre von einem abgestürzten Server nicht zu
// unterscheiden.
ProgressCallback func(ExecutionProgress)
}
// ExecutionProgress meldet den Stand eines laufenden Vorgangs.
type ExecutionProgress struct {
// BytesProcessed ist die bisher gelesene Datenmenge.
BytesProcessed int64
// FilesProcessed ist die Zahl bisher bearbeiteter Objekte.
FilesProcessed int64
// CurrentSource benennt die gerade bearbeitete Quelle.
CurrentSource string
}
// ExecutionResult ist das Ergebnis eines Sicherungslaufs.
type ExecutionResult struct {
// BytesProcessed ist die gelesene Datenmenge.
BytesProcessed int64
// BytesWritten ist die abgelegte Datenmenge.
BytesWritten int64
// FilesProcessed ist die Zahl bearbeiteter Objekte.
FilesProcessed int64
// FilesSkipped ist die Zahl übergangener Objekte.
//
// Ist sie größer als 0, ist der Lauf ein Teilfehler — unabhängig davon,
// ob der Executor einen Fehler meldet (PROMPT.md §138).
FilesSkipped int64
// SkipReasons erklären die übergangenen Objekte.
//
// Eine Zahl allein hilft nicht weiter: „17 Objekte übergangen" ist keine
// Auskunft, „17 Objekte wegen fehlender Leseberechtigung" schon.
SkipReasons []string
}
// Executor führt einen Sicherungslauf aus.
//
// Die Schnittstelle hält die Ausführungsschleife frei von Backup Engine,
// Repository und Providern. Das ist nicht nur Ordnung: Ohne sie liesse sich das
// Zusammenspiel von Zeitplan, Wartungsfenster, Nebenläufigkeit und Wiederholung
// nur mit einem echten Repository prüfen — also praktisch gar nicht.
//
// Eine Umsetzung muss zwei Regeln einhalten:
//
// 1. Der Kontext wird beachtet. Ein Abbruch muss den Lauf beenden, sonst
// hinge das Herunterfahren des Dienstes am längsten Backup.
// 2. Übergangene Objekte werden in FilesSkipped gemeldet, nicht verschwiegen.
// Ein Lauf mit übergangenen Objekten ist niemals ein Erfolg.
type Executor interface {
// Execute führt den Lauf aus.
Execute(executionContext context.Context, executionRequest ExecutionRequest) (ExecutionResult, error)
}
// ExecutorFunc erlaubt es, eine Funktion als Executor zu verwenden.
type ExecutorFunc func(executionContext context.Context, executionRequest ExecutionRequest) (ExecutionResult, error)
// Execute erfüllt die Executor-Schnittstelle.
func (executorFunction ExecutorFunc) Execute(executionContext context.Context, executionRequest ExecutionRequest) (ExecutionResult, error) {
return executorFunction(executionContext, executionRequest)
}
// NotImplementedExecutor meldet, dass keine Ausführung eingerichtet ist.
//
// Er ist der Standard, solange die Anbindung an die Backup Engine fehlt. Ein
// Executor, der stillschweigend Erfolg meldete, wäre das gefährlichste
// Fake-Feature der ganzen Anlage: Die Oberfläche zeigte grüne Läufe, und im
// Repository läge nichts.
type NotImplementedExecutor struct{}
// Execute meldet die fehlende Ausführung als Fehler.
func (executor *NotImplementedExecutor) Execute(_ context.Context, _ ExecutionRequest) (ExecutionResult, error) {
return ExecutionResult{}, &ExecutionError{
Code: "EXECUTOR_NOT_CONFIGURED",
Message: "Für diesen Dienst ist keine Ausführung eingerichtet; der Auftrag wurde nicht gesichert.",
FailureClass: scheduler.FailureConfiguration,
}
}
// ExecutionError ist ein eingeordneter Fehler eines Sicherungslaufs.
//
// Die Einordnung entscheidet über die Wiederholung. Ein Executor, der nur einen
// gewöhnlichen Fehler zurückgibt, bekommt die sichere Voreinstellung
// „dauerhaft" — es wird also nicht wiederholt. Wer Wiederholungen will, muss
// die Fehlerart ausdrücklich benennen.
type ExecutionError struct {
// Code ist die Fehlerkennung in SCREAMING_SNAKE_CASE.
Code string
// Message ist die verständliche Meldung.
//
// Sie enthält niemals Geheimnisse (PROMPT.md §141).
Message string
// FailureClass ordnet den Fehler ein.
FailureClass scheduler.FailureClass
// Cause ist der zugrunde liegende Fehler.
Cause error
}
// Error erfüllt die Fehlerschnittstelle.
func (executionError *ExecutionError) Error() string {
return executionError.Message
}
// Unwrap gibt die Ursache frei.
func (executionError *ExecutionError) Unwrap() error {
return executionError.Cause
}
// ExecutionOutcome ist das ausgewertete Ergebnis eines Laufs.
type ExecutionOutcome struct {
// Status ist der erreichte Zustand.
Status RunStatus
// Result sind die Kennzahlen.
Result ExecutionResult
// FailureClass ordnet einen Fehler ein.
FailureClass scheduler.FailureClass
// ErrorCode ist die Fehlerkennung.
ErrorCode string
// ErrorMessage ist die verständliche Meldung.
ErrorMessage string
// Duration ist die Dauer des Laufs.
Duration time.Duration
}