Backup Restore Orchestrator
Automatisiertes tägliches Backup-Restore-Testsystem auf Basis von Windmill.
Jeden Tag um 00:11 Uhr werden alle PBS-Backups auf mehreren Restore-Servern wiederhergestellt, auf Bootfähigkeit geprüft, als verschlüsselte 7z-Archive gespeichert und auf einen zentralen Backup-Server übertragen. Ergebnisse werden in einer MySQL-Datenbank gespeichert und per Nextcloud Talk gemeldet.
Repository-Struktur
Windmill Sync
Hinweis: Secrets (skipSecrets: true) werden nicht synchronisiert. Variablen mit sensiblen Werten müssen nach dem Push manuell in Windmill gesetzt werden.
Systemarchitektur
Restore-Server
| Hostname |
max_backup_size_gb |
min_backup_size_gb |
Rsync |
| STI-PROX01 |
200 |
NULL |
ja |
| ITD-PROX01 |
100 |
NULL |
ja |
| STI-BAC01 |
NULL |
250 |
nein (lokal gemountet) |
Flow-Ablauf: F → A → B → G → C → H → D → E
| Step |
ID |
Sprache |
Funktion |
| F |
f |
MySQL |
Aktive Datastores aus DB holen |
| A |
a |
Python |
Job anlegen, PBS-Snapshots holen, Queue aufbauen (größte zuerst) |
| B |
b |
Python |
Freie Restore-Server holen |
| G |
g |
Python |
SSH-Credentials aus Bitwarden |
| C |
c |
Python |
Script deployen, PBS-Storages registrieren, Session speichern |
| H |
h |
Python |
Alte Backup-Ordner auf Backup-Server löschen |
| D |
d |
Python |
Ersten Restore pro Server starten |
| E |
e |
Python |
Webhook verarbeiten, nächsten Restore starten |
Zwei Modi
Schedule-Pfad (täglich 00:11):
Steps F → A → B → G → C → H → D laufen sequenziell. Step D startet den ersten Restore pro Server per SSH non-blocking und gibt waiting_for_webhook zurück.
Webhook-Pfad (nach jedem restore.sh):
Flow-Input enthält job_uuid → Step A erkennt Webhook-Aufruf. Steps B–H werden übersprungen. Step E verarbeitet das Ergebnis, schreibt es in die DB und startet sofort das nächste passende Backup auf demselben Server.
restore.sh — Ablauf
Das Script läuft auf den Restore-Servern unter /opt/windmill-restore/restore.sh.
Gestartet von Step D und Step E via SSH + nohup (non-blocking).
Script-Deployment
Das Script wird von Step C automatisch auf alle Restore-Server deployed:
restore.sh in diesem Repo (Ordner restore-worker/) aktualisieren
- In Gitea pushen:
http://172.17.1.251:8080/sebastian.serfling/BackupScript.git
- Windmill-Variable
f/Backup/restore_version erhöhen (z.B. 1.0.27)
- Nächster Flow-Lauf: Step C erkennt Versionsunterschied → deployed automatisch
Windmill-Variablen
| Variable |
Inhalt |
f/Backup/pbs_variable |
JSON: host, port, user, password, fingerprint |
f/Backup/mysql_config |
JSON: MySQL-Verbindungsdaten |
f/Backup/bitwarden_api_login |
JSON: bw_clientid, bw_clientsecret, bw_masterpassword |
f/Backup/gitea_token |
Gitea Access Token |
f/Backup/restore_version |
Aktuelle Script-Version, z.B. 1.0.26 |
f/Backup/backup_server_host |
Hostname/IP Backup-Server |
f/Backup/backup_server_ssh_password |
SSH-Passwort Backup-Server |
f/Backup/windmill_webhook_url |
Webhook-URL für restore.sh Callbacks |
f/Backup/windmill_webhook_token |
Bearer Token |
f/Backup/nextcloud_talk_url |
https://nextcloud.stines.de |
f/Backup/nextcloud_talk_room |
Room-Token |
f/Backup/nextcloud_talk_user |
Benutzername |
f/Backup/nextcloud_talk_password |
App-Passwort |
Datenbank-Schema (MySQL: Kunden)
bronze.restore.jobs
| Spalte |
Typ |
Beschreibung |
| job_uuid |
VARCHAR(64) PK |
Eindeutige Job-ID |
| started_at |
DATETIME |
Startzeitpunkt |
| finished_at |
DATETIME |
Endzeitpunkt |
| status |
VARCHAR(20) |
running / completed / failed |
| total_backups |
INT |
Anzahl Backups in Queue |
| restored_count |
INT |
Erfolgreich abgeschlossen |
| failed_count |
INT |
Fehlgeschlagen |
bronze.restore.result
| Spalte |
Typ |
Beschreibung |
| job_uuid |
VARCHAR(64) |
Referenz auf Job |
| client_name |
VARCHAR(128) |
z.B. tnp-Invest-GmbH:vm/100 |
| backup_path |
VARCHAR(256) |
Vollpfad mit Timestamp |
| vm_name |
VARCHAR(128) |
Hostname der VM/CT |
| restore_server |
VARCHAR(128) |
Hostname des Restore-Servers |
| status |
VARCHAR(20) |
restoring / done / failed |
| restore_duration_sec |
INT |
Dauer Restore in Sekunden |
| zip_size_bytes |
BIGINT |
Größe des 7z-Archivs |
| rsync_ok |
TINYINT |
Rsync erfolgreich |
| qm_agent_ok |
TINYINT |
Boot-Check erfolgreich |
| error_message |
TEXT |
Fehlermeldung falls failed |
bronze.backup.queue
| Spalte |
Typ |
Beschreibung |
| job_uuid |
VARCHAR(64) |
Referenz auf Job |
| client_name |
VARCHAR(128) |
Backup-Bezeichnung |
| backup_path |
VARCHAR(256) |
Vollpfad mit Timestamp |
| backup_size_bytes |
BIGINT |
Komprimierte PBS-Größe |
| priority |
INT |
0 = größtes (höchste Prio) |
| rsync_target |
VARCHAR(256) |
Zielpfad auf Backup-Server |
| pbs_storage_id |
VARCHAR(128) |
z.B. pbs-firma-gmbh |
| status |
VARCHAR(20) |
queued / assigned / done / failed / obsolete |
bronze.restore.server
| Spalte |
Typ |
Beschreibung |
| hostname |
VARCHAR(128) PK |
Server-Hostname |
| ip |
VARCHAR(45) |
IP-Adresse |
| is_active |
TINYINT |
1 = aktiv |
| free_space_gb |
INT |
Freier Speicher (wird aktualisiert) |
| restore_mount |
VARCHAR(128) |
z.B. /mnt/BTRFS |
| restore_path |
VARCHAR(128) |
PVE-Storage-Name |
| current_job_uuid |
VARCHAR(64) |
NULL = frei |
| max_backup_size_gb |
INT |
NULL = kein Limit |
| min_backup_size_gb |
INT |
NULL = kein Limit |
| script_deployed |
TINYINT |
Script vorhanden |
| script_version |
VARCHAR(20) |
Aktuelle Script-Version |
SQL-Reset bei Problemen
Bekannte Probleme
| Problem |
Ursache |
Fix |
| Falsches Datum in Logs |
Server auf UTC statt CET |
timedatectl set-timezone Europe/Berlin |
/var/tmp voll (ITD-PROX01) |
Proxmox schreibt tmp auf Root-Partition |
mount --bind /mnt/BTRFS/tmp /var/tmp |
| Server bleibt nach letztem Backup belegt |
current_job_uuid nicht zurückgesetzt |
UPDATE bronze.restore.server SET current_job_uuid=NULL WHERE hostname=... |