Leonard Surac

Zur Startseite

Im Betrieb · seit 2026

Pi Dev Center

Ich betreibe rund zwanzig Dienste zu Hause und wusste nie sicher, ob meine Backups im Ernstfall funktionieren. Also habe ich mir eine Betriebszentrale gebaut, die es beweist.

ASP.NET Core 10 · EF Core · Angular 22 · PostgreSQL · PowerShell · GitHub Actions · Tailscale

Problem

In meinem Homelab laufen rund zwanzig Dienste auf zwei Rechnern, von Fotos und Dokumenten bis zu meinen eigenen Apps. Gesichert wurde per Cron-Skript. Ob ein Backup gelaufen war und ob es sich wirklich zurückspielen ließ, stand verstreut in Logdateien, und ich hätte es erst im Ernstfall erfahren.

Meine Rolle

Ich habe die Lösung konzipiert, entwickelt und betreibe sie, samt 74 Host-Skripten, die ihr berichten.

Fallstudie

Meine Lösung

Ich wollte nicht wissen, ob ein Backup-Skript gelaufen ist, sondern ob ich meine Daten zurückbekomme. Deshalb melden die Skripte ihr Ergebnis an eine zentrale App, und die spielt jede Sicherung regelmäßig in einer Wegwerf-Umgebung ein und zählt nach.

  • Backup-Zentrale für lokale und verschlüsselte Offsite-Sicherungen
  • Geplante Restore-Proben für jeden Bestand, auch für Dokumente mit echten Dateien
  • Dienstkatalog gegen die Lage, die beide Rechner alle 15 Minuten melden
  • Betriebsjournal und eine Inbox für alles, was meine Aufmerksamkeit braucht

Umsetzung

Die App ist mit ASP.NET Core, EF Core, PostgreSQL und Angular gebaut. Weil sie Restores auslösen kann, gibt es keinen Login, sondern Operator-Schlüssel mit Ablaufdatum und getrennten Rechten. Ich speichere nur den Hash und vergleiche in konstanter Zeit:

PiDevCenter.Api/Shared/Infrastructure/Security/OperatorKeyAuthenticationHandler.cs
var key = settings.Keys.SingleOrDefault(item => item.Id == parts[0]);
if (key is null || !key.Enabled || key.ExpiresAt <= TimeProvider.GetUtcNow()) return Invalid();
var hash = SHA256.HashData(Encoding.UTF8.GetBytes(parts[1]));
if (!CryptographicOperations.FixedTimeEquals(hash, Convert.FromHexString(key.SecretSha256))) return Invalid();

Jeder Push rollt per GitHub Actions aus, mit Probelauf, Rollback-Probe und automatischer Abnahme danach. Erreichbar ist alles nur über HTTPS in meinem privaten Netz.

Entscheidungen

Schlüssel statt Login
Ich habe mich für Operator-Schlüssel mit Pflicht-Ablaufdatum und vier getrennten Rechten entschieden (lesen, testen, wiederherstellen, verwalten). Verwaltet werden sie in einer Datei statt in einer Oberfläche, dafür kann ein Schlüssel zum Lesen nie einen Restore auslösen.
Ich lösche nur, was ich nachweislich selbst angelegt habe
Das Recovery-Labor protokolliert jede Ressource, bevor es sie anlegt, und räumt nur diese wieder ab. Liegengebliebenes muss ich dann von Hand entfernen, aber nichts Fremdes verschwindet je.
Der geprüfte Dump ist der Export
Statt eine Export-Funktion zu bauen, ist der nächtliche Dump, den eine Restore-Probe jede Woche zurückspielt, der vollständige Export. Weniger Code, dafür ein technisches Format.

Ergebnis

Heute sehe ich auf einen Blick, was läuft und was nicht. In den ersten 30 Tagen lief jedes tägliche Backup an jedem Tag erfolgreich, und jede Sicherung wird regelmäßig zur Probe wiederhergestellt; die Kontrolldatenbank mit 3,2 GiB ist in 35 Sekunden zurück.