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>
116 lines
4.9 KiB
SQL
116 lines
4.9 KiB
SQL
-- Aufträge, die ein Agent auf seinem System ausführt (Phase 5).
|
|
--
|
|
-- Bis hierher war der Agent ein angemeldeter Beobachter: Er meldete sich an und
|
|
-- gab Lebenszeichen, aber der Server konnte ihm nichts zu tun geben. Zentral
|
|
-- gesteuerte Sicherungen liefen deshalb nur für Quellen, die der Control-Server
|
|
-- selbst erreicht.
|
|
--
|
|
-- Der Agent **holt** seine Aufträge ab; der Server drückt sie ihm nicht zu. Das
|
|
-- ist keine Bequemlichkeit, sondern die einzige Wahl, die in echten Netzen
|
|
-- funktioniert: Ein Agent steht hinter einer Firewall, oft hinter NAT, und ist
|
|
-- vom Server aus nicht erreichbar. Die Verbindung geht immer vom Agenten aus.
|
|
|
|
CREATE TABLE agent_tasks (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
|
|
-- Der ausführende Agent.
|
|
agent_id UUID NOT NULL REFERENCES agents(id) ON DELETE CASCADE,
|
|
|
|
-- Der Lauf, zu dem der Auftrag gehört.
|
|
--
|
|
-- Über ihn findet das Ergebnis zurück in die Auftragsverwaltung. Ohne
|
|
-- diesen Bezug wäre eine Agentensicherung ein Backup ohne Herkunft.
|
|
job_run_id UUID NOT NULL REFERENCES backup_job_runs(id) ON DELETE CASCADE,
|
|
|
|
-- Die zu sichernde Quelle.
|
|
job_source_id UUID NOT NULL REFERENCES backup_job_sources(id) ON DELETE CASCADE,
|
|
|
|
task_type TEXT NOT NULL DEFAULT 'backup',
|
|
status TEXT NOT NULL DEFAULT 'pending',
|
|
|
|
-- Der vollständige Auftrag als JSON.
|
|
--
|
|
-- Er enthält Quellpfad, Repositorypfad, Backupkennung, Kette, Elternbackup
|
|
-- und die Einstellungen. Als JSON und nicht als Spalten, weil der Auftrag
|
|
-- ein **Vertrag** zwischen Server und Agent ist: Beide Seiten werden
|
|
-- getrennt aktualisiert, und ein zusätzliches Feld darf keine Migration auf
|
|
-- jedem Agenten erzwingen.
|
|
task_payload JSONB NOT NULL,
|
|
|
|
-- Fortschritt und Ergebnis.
|
|
bytes_processed BIGINT NOT NULL DEFAULT 0,
|
|
bytes_written BIGINT NOT NULL DEFAULT 0,
|
|
files_processed BIGINT NOT NULL DEFAULT 0,
|
|
files_skipped BIGINT NOT NULL DEFAULT 0,
|
|
backup_id_in_repository TEXT,
|
|
|
|
error_code TEXT,
|
|
error_message TEXT,
|
|
failure_class TEXT,
|
|
|
|
-- Lebendmeldung des ausführenden Agenten.
|
|
--
|
|
-- Stirbt der Agent mitten im Lauf, bliebe der Auftrag sonst dauerhaft auf
|
|
-- 'running' stehen und blockierte den Agenten für immer — ohne dass jemand
|
|
-- einen Fehler sähe. Dieselbe Überlegung wie beim Scheduler (Phase 8).
|
|
heartbeat_at TIMESTAMPTZ,
|
|
|
|
claimed_at TIMESTAMPTZ,
|
|
started_at TIMESTAMPTZ,
|
|
completed_at TIMESTAMPTZ,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
|
|
CONSTRAINT agent_tasks_status_valid CHECK (
|
|
status IN ('pending', 'claimed', 'running', 'succeeded', 'partial_failure',
|
|
'failed', 'cancelled')
|
|
),
|
|
|
|
CONSTRAINT agent_tasks_type_valid CHECK (task_type IN ('backup', 'restore')),
|
|
|
|
-- Ein abgeschlossener Auftrag hat einen Abschlusszeitpunkt.
|
|
CONSTRAINT agent_tasks_completed_when_done CHECK (
|
|
status IN ('pending', 'claimed', 'running') OR completed_at IS NOT NULL
|
|
),
|
|
|
|
-- Übergangene Objekte und Erfolg schließen sich aus.
|
|
--
|
|
-- Dieselbe Regel wie bei den Sicherungsläufen (Phase 8): Ein Lauf mit
|
|
-- übergangenen Objekten ist niemals ein Erfolg, sondern ein Teilfehler
|
|
-- (verbindliche Regel 1). Sie steht als CHECK in der Datenbank, weil sie im
|
|
-- Code an jeder Stelle eingehalten werden müsste — und eine vergisst es.
|
|
CONSTRAINT agent_tasks_skipped_is_not_success CHECK (
|
|
files_skipped = 0 OR status <> 'succeeded'
|
|
),
|
|
|
|
-- Ein gescheiterter Auftrag nennt seine Fehlerklasse.
|
|
--
|
|
-- Ohne sie kann die Ausführungsschleife nicht entscheiden, ob eine
|
|
-- Wiederholung sinnvoll ist (Phase 21).
|
|
CONSTRAINT agent_tasks_failure_is_classified CHECK (
|
|
status <> 'failed' OR failure_class IS NOT NULL
|
|
)
|
|
);
|
|
|
|
-- Ein Agent führt höchstens einen Auftrag gleichzeitig aus.
|
|
--
|
|
-- Der Teilindex ist die Durchsetzung: Zwei gleichzeitige Sicherungen desselben
|
|
-- Systems konkurrierten um dieselben Dateien und dieselbe Schreibsperre des
|
|
-- Repositorys. Im Code ließe sich das nur mit einer Prüfung zwischen Lesen und
|
|
-- Schreiben lösen — und dazwischen passt ein zweiter Control-Server.
|
|
CREATE UNIQUE INDEX agent_tasks_single_active_idx
|
|
ON agent_tasks (agent_id)
|
|
WHERE status IN ('claimed', 'running');
|
|
|
|
-- Die Abholung sucht den ältesten offenen Auftrag eines Agenten.
|
|
CREATE INDEX agent_tasks_pending_idx
|
|
ON agent_tasks (agent_id, created_at)
|
|
WHERE status = 'pending';
|
|
|
|
-- Die Zuordnung eines Ergebnisses zum Lauf.
|
|
CREATE INDEX agent_tasks_job_run_idx ON agent_tasks (job_run_id);
|
|
|
|
-- Die Freigabe verwaister Aufträge sucht über die Lebendmeldung.
|
|
CREATE INDEX agent_tasks_heartbeat_idx
|
|
ON agent_tasks (heartbeat_at)
|
|
WHERE status IN ('claimed', 'running');
|