Erfahrungen · Lessons Learned
Wenn das Backup fehlt und ein Skript löscht
Dieser Erfahrungsbericht fasst einen Incident zusammen, der sich bei der Konsolidierung einer größeren Menge Finanz- und Steuerunterlagen ereignet hat. Er ist anonymisiert, folgt aber exakt der technischen Fehlerkette. Ziel ist es, Betreiber von SMB-Freigaben, Nextcloud-Spiegeln und Git-Repositories davor zu bewahren, die gleichen Fehler zu machen.
Ausgangslage
Ein Einzelunternehmer im Forschungs- und Freelance-Umfeld wollte seine jahrelang gewachsenen Finanzdokumente von einer Quell-Freigabe (Samba/NAS) in eine neue, nach Jahren geordnete Zielstruktur (Ordner FY2015 … FY2026 sowie FY_undatiert) überführen. Die Quelle enthielt stark redundante Kopien; als Abschluss war eine Deduplizierung („keine Datei doppelt“) geplant.
Was schiefging
Zwei Fehler kombinierten sich zu einem massiven Datenverlust:
- Kein verifiziertes Backup vor Beginn. Erst im Nachgang wurde ein externes 8-TB-Backup (vom 16.08.) eingehängt. Wäre es vorher geprüft worden, hätte der folgende Fehler kaum geschadet.
- Ein Deduplizierungs-Skript mit unquotierter Pfadliste. Die Liste der zu durchsuchenden Verzeichnisse wurde unquotiert in
findübergeben. Da einige Pfade Leerzeichen enthalten (z. B.Freigabe/Banken/Konto 1234 5678/2024), spaltete die Shell diese Pfade auf. Das führte dazu, dass Eltern- und Kindverzeichnisse mehrfach als Startpunkte erschienen —findlistete dieselbe Datei mehrfach auf.
Das Dedup-Skript verglich aufeinanderfolgende SHA-256-Hashes und löschte den jeweils zweiten „gleichen“ Eintrag. Da dieselbe Datei durch die mehrfache Auflistung als „Duplikat“ ihrer selbst erschien, wurde sie — zusammen mit allen weiteren Auflistungen — gelöscht. Es wurden einzigartige Dateien vernichtet, keine redundanten Kopien.
(find $SCOPE)"] --> B["Shell trennt Pfade mit
Leerzeichen in Fragmente"] B --> C["Eltern- und Kindverzeichnisse
erscheinen mehrfach als Startpunkte"] C --> D["find listet dieselbe Datei
N-mal (N > 1)"] D --> E["Dedup: aufeinanderfolgende
Hash-Werte vergleichen"] E --> F["h == prev bei wiederholter
Listung derselben Datei"] F --> G["rm der 'Duplikate' =
Löschung einzigartiger Dateien"] G --> H["~6.400 einzigartige Dateien
vernichtet"] style G fill:#c0392b,color:#fff style H fill:#c0392b,color:#fff
Die Rettung
Sobald der Fehler bemerkt wurde, wurde die laufende Operation sofort gestoppt. Es gab kein lokales Git-Repository als Quelle, aber drei weitere Kopien existierten auf dem Server bzw. extern:
- Nextcloud-Sync-Spiegel der Quell-Freigabe auf demselben Server → lieferte ~5.300 Dateien zurück.
- Überlebende ZIP-Archive in der Zielstruktur → ~70 weitere Dateien durch einfaches Entpacken.
- Externes 8-TB-Backup (vom 16.08.) → ~600 weitere Dateien.
- Git-Origin (Gitea) für ein betroffenes Teil-Repository → die vollständige History konnte per Clone rekonstruiert werden.
(~6.400)"] --> M["Nextcloud-Sync-Spiegel
der Quelle"] D --> Z["Überlebende ZIP-Archive"] D --> B["Externes 8-TB-Backup
(16.08.)"] D --> G["Git-Origin (Gitea)
für Teil-Repo"] M -->|"~5.300"| R["Wiederhergestellt"] Z -->|"~70"| R B -->|"~600"| R G -->|"History"| R R --> S["~98 % gerettet
(~6.300 von ~6.400)"] style S fill:#27ae60,color:#fff
Insgesamt konnten rund 98 % der gelöschten Dateien wiederhergestellt werden. Die verbleibenden ~2 % entfielen auf Dateien, die in keiner der verfügbaren Kopien existierten (u. a. ein Postfach-Export und einige nach dem Backup angelegte Dokumente).
Warum das GitCover-Konzept hier hilft
Die Geschichte zeigt: Wer auf Git-native Strukturen setzt, kommt auch nach unbeabsichtigten Fehlern schnell wieder arbeitsfähig — und dokumentiert zugleich GoBD-konform.
Lessons Learned
- Backup vorher verifizieren, nicht nachher. Idealerweise 3-2-1: 3 Kopien, 2 Medien, 1 offline. Ein Backup, das erst nach dem Schaden eingehängt wird, hilft nicht.
- Pfade immer null-delimited und quotiert verarbeiten.
find … -print0 | xargs -0statt unquotiertemfind $LIST. Leerzeichen in Pfaden sind der klassische Solver-Killer. - Dedup darf nie bei wiederholter Auflistung löschen. Korrekt ist: genau eine Kopie pro eindeutigem Pfad + Inhalt behalten; wird dieselbe Datei mehrfach aufgelistet, darf nichts gelöscht werden (inode- oder hash+pfad-basiert entscheiden).
- Mehrere unabhängige Recovery-Quellen vorhalten. Spiegel, Archive und Versionskontrolle ergänzen einander; keine einzelne reicht.
- Unversionierte Working-Tree-Dateien commitschützen. Liegen sie nur als untracked im Repo, reicht ein
git clean, um sie zu vernichten. Ein Commit macht sie teil der History.
Fazit: Ein einziger unquotierter Variablenaufruf genügt, um Tausende einzigartige Dateien zu vernichten. Die Rettung gelang nur, weil mehrere unabhängige Kopien existierten. Struktur, Prozess und Disziplin sind keine Luxus-Themen — sie sind der einzige Schutz, den es gibt.
Teil der Rubrik Erfahrungen. Weitere Beiträge folgen.