#!/bin/bash # ===================================================================== # Wiederherstellung aus einer Sicherung. # # Aufruf: sudo bash wiederherstellen.sh 2026-08-26 # sudo bash wiederherstellen.sh (zeigt die Auswahl) # # WARUM ES DIESES SKRIPT GIBT # # Wiederherstellen passiert immer im schlechtesten Moment: Etwas ist # kaputt, es eilt, und man tippt Befehle, die man seit Monaten nicht # gebraucht hat. Genau dann werden Fehler gemacht, die den Schaden # vergroessern. Deshalb ist der Weg hier einmal in Ruhe aufgeschrieben # und geprueft worden. # # DIE BEGLEITDATEIEN # # Neben der Datenbank liegen ".db-wal" und ".db-shm". Beide werden hier # mit beiseitegeraeumt. # # Zur Ehrlichkeit: Das wurde nachgemessen, und SQLite schuetzt sich # dagegen besser als oft behauptet. Jedes WAL traegt eine Kennung, die # zu seiner Datenbank passen muss; stimmt sie nicht, wird das WAL # verworfen statt angewendet. Ein zurueckgelassenes WAL fuehrt also in # aller Regel NICHT zur gefuerchteten Vermischung zweier Staende. # # Der Grund fuers Aufraeumen ist ein anderer, und der ist real: Solange # der Dienst laeuft, haelt er die alte Datenbank offen. Tauscht man die # Datei unter ihm aus, schreibt er weiter -- gegen eine Datei, die es so # nicht mehr gibt. Deshalb ist Schritt 2 (Dienst anhalten) der # eigentliche Schutz, und das Wegraeumen der Begleitdateien sorgt # dafuer, dass danach ein sauberer, eindeutiger Zustand vorliegt statt # Resten, die beim naechsten Problem niemand mehr einordnen kann. # # NICHTS WIRD GELOESCHT # # Der bisherige Stand wird nicht ueberschrieben, sondern zur Seite # gelegt. Stellt sich heraus, dass die Wiederherstellung die falsche # Entscheidung war, ist der Weg zurueck noch offen. # ===================================================================== set -uo pipefail ZIEL_DB="/var/lib/dogfather-internal/dogfather-internal.db" ZIEL_UPLOADS="/var/lib/dogfather-internal/uploads" SICHERUNGEN="/var/backups/dogfather" DIENST="dogiintern.service" if [ "$(id -u)" != "0" ]; then echo "Bitte mit sudo starten -- sonst koennen Rechte und Dienst nicht gesetzt werden." exit 1 fi STAND="${1:-}" if [ -z "$STAND" ]; then echo "Verfuegbare Staende:" echo echo " taeglich:" ls -1 "$SICHERUNGEN/taeglich" 2>/dev/null | sort -r | sed 's/^/ /' echo " woechentlich:" ls -1 "$SICHERUNGEN/woechentlich" 2>/dev/null | sort -r | sed 's/^/ /' echo echo "Aufruf: sudo bash $0 " exit 0 fi QUELLE="" for ordner in "$SICHERUNGEN/taeglich/$STAND" "$SICHERUNGEN/woechentlich/$STAND"; do [ -d "$ordner" ] && QUELLE="$ordner" && break done [ -n "$QUELLE" ] || { echo "Stand '$STAND' nicht gefunden."; exit 1; } echo "Stand: $QUELLE" # --------------------------------------------------------------------- # 1. Die Sicherung PRUEFEN, bevor irgendetwas angefasst wird # # Erst pruefen, dann eingreifen. Andersherum stuende man mit einem # gestoppten Dienst und einer unbrauchbaren Sicherung da. # --------------------------------------------------------------------- [ -f "$QUELLE/dogfather-internal.db" ] || { echo "Keine Datenbank in diesem Stand."; exit 1; } PRUEFUNG=$(sqlite3 "$QUELLE/dogfather-internal.db" "PRAGMA integrity_check;" 2>&1) [ "$PRUEFUNG" = "ok" ] || { echo "Diese Sicherung ist beschaedigt: $PRUEFUNG"; exit 1; } TABELLEN=$(sqlite3 "$QUELLE/dogfather-internal.db" \ "SELECT count(*) FROM sqlite_master WHERE type='table';") echo "Geprueft: unversehrt, $TABELLEN Tabellen" echo read -r -p "Diesen Stand einspielen? Der aktuelle wird beiseitegelegt. [ja/nein] " ANTWORT [ "$ANTWORT" = "ja" ] || { echo "Abgebrochen."; exit 0; } BEISEITE="/var/backups/dogfather/vor-wiederherstellung-$(date '+%Y-%m-%d_%H%M%S')" mkdir -p "$BEISEITE" # --------------------------------------------------------------------- # 2. Dienst anhalten # # Eine Datenbank auszutauschen, waehrend ein Programm sie offen haelt, # fuehrt zu genau der Vermischung, die oben beschrieben ist. # --------------------------------------------------------------------- systemctl stop "$DIENST" && echo "Dienst angehalten" sleep 1 # --------------------------------------------------------------------- # 3. Bisherigen Stand beiseiteraeumen -- samt Begleitdateien # --------------------------------------------------------------------- BESITZER=$(stat -c '%U:%G' "$ZIEL_DB" 2>/dev/null || echo "root:root") for datei in "$ZIEL_DB" "$ZIEL_DB-wal" "$ZIEL_DB-shm"; do [ -f "$datei" ] && mv "$datei" "$BEISEITE/" && echo "beiseite: $(basename "$datei")" done if [ -d "$ZIEL_UPLOADS" ] && [ -f "$QUELLE/uploads.tar.gz" ]; then mv "$ZIEL_UPLOADS" "$BEISEITE/uploads" && echo "beiseite: uploads" fi # --------------------------------------------------------------------- # 4. Einspielen # --------------------------------------------------------------------- cp "$QUELLE/dogfather-internal.db" "$ZIEL_DB" chown "$BESITZER" "$ZIEL_DB" chmod 640 "$ZIEL_DB" echo "Datenbank eingespielt" if [ -f "$QUELLE/uploads.tar.gz" ]; then tar -xzf "$QUELLE/uploads.tar.gz" -C "$(dirname "$ZIEL_UPLOADS")" chown -R "$BESITZER" "$ZIEL_UPLOADS" echo "Dateien eingespielt ($(find "$ZIEL_UPLOADS" -type f | wc -l) Stueck)" fi # --------------------------------------------------------------------- # 5. Dienst starten und nachsehen, ob er auch laeuft # --------------------------------------------------------------------- systemctl start "$DIENST" sleep 3 if systemctl is-active --quiet "$DIENST"; then echo echo "Fertig. Der Dienst laeuft." echo "Der vorherige Stand liegt in: $BEISEITE" echo "Bitte im Verwaltungsbereich nachsehen, ob die Daten stimmen," echo "bevor dieser Ordner geloescht wird." else echo echo "ACHTUNG: Der Dienst startet nicht." echo " systemctl status $DIENST" echo "Der vorherige Stand liegt unversehrt in: $BEISEITE" exit 1 fi