diff --git a/WIEDERHERSTELLUNG.md b/WIEDERHERSTELLUNG.md new file mode 100644 index 00000000..3a62871b --- /dev/null +++ b/WIEDERHERSTELLUNG.md @@ -0,0 +1,137 @@ +# Wiederherstellung des Creator Workspace + +Für den Tag, an dem etwas kaputt ist. Geschrieben, damit sie auch dann +funktioniert, wenn man aufgeregt ist und keine Zeit zum Nachdenken hat. + +**Der wichtigste Satz zuerst:** Die Datei `workspace.db` allein ist **nicht** die +Datenbank. Im WAL-Betrieb stehen die neuesten Änderungen in `workspace.db-wal`. +Wer nur die `.db` kopiert, kopiert unter Umständen **nichts** — genau das war am +31.08.2026 der Fall (Datenbank 4 KB, WAL 2,1 MB). + +Deshalb: **Nie Dateien kopieren. Immer die fertigen Sicherungen benutzen.** + +--- + +## Wo alles liegt + +| Was | Wo | +|---|---| +| Datenbank | `/home/dogiweb/workspace-daten/workspace.db` | +| Hochgeladene Dateien | `/home/dogiweb/workspace-daten/dateien/` | +| Wissens-Bibliothek (PDFs) | `/home/dogiweb/workspace-daten/wissen/` | +| Sicherungen (Server) | `/home/dogiweb/workspace-daten/sicherungen/` | +| Sicherungen (dieser PC) | `~/Documents/Obelix/Sicherungen/workspace/` | + +Aufgehoben werden **7 tägliche, 4 wöchentliche, 6 monatliche**. Geschrieben wird +jede Nacht ab 3 Uhr, geprüft direkt danach. + +--- + +## Fall 1: „Ich habe aus Versehen etwas gelöscht" + +Nichts überstürzen. Die Sicherung von heute Nacht hat es noch. + +**Erst nachsehen, ob es sich lohnt** — ohne irgendetwas anzufassen: + +``` +ssh dogfather-server "cd /home/dogiweb/workspace-daten/sicherungen && ls -la && for f in *.db; do echo \"--- \$f\"; sqlite3 \$f 'SELECT COUNT(*) FROM personen; SELECT COUNT(*) FROM aufgaben;'; done" +``` + +Dann weiter mit Fall 2. + +--- + +## Fall 2: Datenbank zurückspielen + +**Ein Block, von oben nach unten.** Er stoppt den Dienst, legt den kaputten Stand +zur Seite (nicht löschen — vielleicht braucht man ihn noch), spielt die Sicherung +ein, prüft sie und startet erst dann wieder. + +`DATUM` vorher auf die gewünschte Sicherung ändern. + +``` +ssh dogfather-server 'DATUM=2026-08-31; S=/home/dogiweb/workspace-daten; sudo systemctl stop dogiweb.service && sleep 2 && mv $S/workspace.db $S/kaputt-$(date +%Y%m%d%H%M).db 2>/dev/null; rm -f $S/workspace.db-wal $S/workspace.db-shm; cp $S/sicherungen/taeglich-$DATUM.db $S/workspace.db && chown dogiweb:dogiweb $S/workspace.db && sqlite3 $S/workspace.db "PRAGMA integrity_check; PRAGMA foreign_key_check; SELECT COUNT(*) || \" Personen\" FROM personen;" && sudo systemctl start dogiweb.service && sleep 3 && systemctl is-active dogiweb.service' +``` + +Danach **auf der echten Domain nachsehen**, nicht nur auf die Erfolgsmeldung +vertrauen: + +``` +curl -s -o /dev/null -w "%{http_code}\n" https://dogfather-universe.com/workspace/ && curl -sI https://dogfather-universe.com/ | grep -i via +``` + +**Wichtig:** `mv` statt `rm`. Die kaputte Datei bleibt als `kaputt-…db` liegen. +Wenn sich herausstellt, dass die Sicherung älter war als gedacht, kann man +daraus noch Einzelnes herausholen. Später von Hand aufräumen. + +--- + +## Fall 3: Der Server ist ganz weg + +Dann liegt die letzte Sicherung auf diesem PC. Zuerst holen, was noch geht — +falls der Server noch antwortet: + +``` +bash ~/Documents/Obelix/DogiHompage/tools/sicherung-holen.sh +``` + +Wiederaufbau in dieser Reihenfolge: + +1. Server neu aufsetzen, Benutzer `dogiweb` anlegen (**macht Filipe**, nicht ich — + siehe `CLAUDE.md`) +2. `git clone` von Gitea nach `/home/dogiweb/dogfather-universe` +3. Ordner `/home/dogiweb/workspace-daten/` anlegen +4. Neueste Sicherung von diesem PC hochladen: + ``` + scp ~/Documents/Obelix/Sicherungen/workspace/taeglich-*.db dogfather-server:/home/dogiweb/workspace-daten/workspace.db + ``` +5. `dogiweb.service` und Caddy einrichten, Dienst starten +6. Auf `https://dogfather-universe.com/workspace/` anmelden und prüfen + +7. Hochgeladene Dateien und Bibliothek zurückspielen — die stecken **nicht** in + der Datenbanksicherung, sondern liegen als Dateien daneben: + ``` + scp -r ~/Documents/Obelix/Sicherungen/workspace/dateien/. dogfather-server:/home/dogiweb/workspace-daten/dateien/ && scp -r ~/Documents/Obelix/Sicherungen/workspace/wissen/. dogfather-server:/home/dogiweb/workspace-daten/wissen/ + ``` + +--- + +## Sicherungen auf diesen PC holen + +Regelmäßig ausführen — am besten einmal die Woche: + +``` +bash ~/Documents/Obelix/DogiHompage/tools/sicherung-holen.sh +``` + +Holt **drei** Dinge: die Datenbanksicherungen, die hochgeladenen Dateien und die +PDFs der Bibliothek. Prüft danach jede Datenbankdatei sofort auf Unversehrtheit und +darauf, dass wirklich Personen darin stehen — und sagt es, wenn etwas nicht stimmt. + +**Warum die Dateien nur hier landen und nicht auch auf dem Server:** Eine zweite +Kopie auf derselben Platte hilft gegen nichts, was die erste nicht schon überlebt +hätte. Und weil `scp` nie löscht, bleibt ein hier einmal geholtes PDF erhalten, +auch wenn es auf dem Server verschwindet. + +--- + +## Von Hand sichern, bevor man etwas Größeres anfasst + +Im Workspace unter **Automationen → Sicherung & Zustand → „Jetzt sichern"**. +Dauert Millisekunden. Oder über die Kommandozeile: + +``` +ssh dogfather-server "sudo systemctl status dogiweb.service --no-pager | head -3" +``` + +--- + +## Was noch fehlt + +- **Der Lauf auf diesem PC ist von Hand.** Solange niemand ihn ausführt, steht die + einzige serverferne Kopie auf dem Stand des letzten Mals. Eine geplante Aufgabe + in Windows wäre der nächste Schritt — dann aber mit Meldung, wenn sie ausfällt, + sonst schweigt sie sich monatelang aus. +- **Nur ein Ort.** Server und dieser PC stehen beide bei uns. Gegen Feuer oder + Diebstahl hilft das nicht. Eine verschlüsselte Kopie an einem dritten Ort wäre + die Ausbaustufe — kostenlos möglich, aber eine eigene Entscheidung. diff --git a/tools/sicherung-holen.sh b/tools/sicherung-holen.sh new file mode 100644 index 00000000..6bd5af58 --- /dev/null +++ b/tools/sicherung-holen.sh @@ -0,0 +1,71 @@ +#!/usr/bin/env bash +# =================================================================== +# Holt die Server-Sicherungen auf diesen Rechner und prueft sie. +# +# Eine Sicherung, die auf derselben Platte liegt wie das Original, ist +# keine Sicherung -- sie hilft gegen versehentliches Loeschen, aber nicht +# gegen einen Plattenausfall. Deshalb dieser Schritt. +# +# Einfach ausfuehren: bash tools/sicherung-holen.sh +# =================================================================== +set -euo pipefail + +ZIEL="$HOME/Documents/Obelix/Sicherungen/workspace" +mkdir -p "$ZIEL" + +echo "Hole Sicherungen vom Server ..." +scp -q dogfather-server:/home/dogiweb/workspace-daten/sicherungen/*.db "$ZIEL/" + +# Die Datenbank kennt hochgeladene Dateien und PDFs nur ueber ihren +# Namen -- die Dateien selbst liegen daneben im Dateisystem und stecken +# NICHT in der Datenbanksicherung. Ohne diesen Schritt haette man nach +# einem Plattenausfall eine Bibliothek voller Verweise auf PDFs, die es +# nicht mehr gibt. +# +# Bewusst nur hier auf dem PC und nicht zusaetzlich auf dem Server: Eine +# zweite Kopie auf derselben Platte hilft gegen nichts, was die erste +# nicht schon ueberlebt haette. +# +# scp loescht nie etwas. Wer auf dem Server versehentlich ein PDF +# entfernt, hat es hier weiterhin -- das ist Absicht, nicht Nachlaessigkeit. +echo "Hole hochgeladene Dateien und die Bibliothek ..." +mkdir -p "$ZIEL/dateien" "$ZIEL/wissen" +scp -qr dogfather-server:/home/dogiweb/workspace-daten/dateien/. "$ZIEL/dateien/" 2>/dev/null || true +scp -qr dogfather-server:/home/dogiweb/workspace-daten/wissen/. "$ZIEL/wissen/" 2>/dev/null || true +echo " Dateien: $(find "$ZIEL/dateien" -type f 2>/dev/null | wc -l | tr -d " ") Stueck" +echo " Bibliothek: $(find "$ZIEL/wissen" -type f 2>/dev/null | wc -l | tr -d " ") Stueck" + +# Geprueft wird mit Node, nicht mit dem sqlite3-Programm: Node bringt +# SQLite seit Fassung 22 selbst mit, und auf diesem Windows-Rechner ist +# sqlite3 gar nicht vorhanden -- die Pruefung haette dann nur +# Fragezeichen ausgegeben und trotzdem "fertig" gemeldet. +node -e ' +const { DatabaseSync } = require("node:sqlite"); +const { readdirSync, statSync } = require("node:fs"); +const { join } = require("node:path"); +const ziel = process.argv[1]; +const dateien = readdirSync(ziel).filter((n) => n.endsWith(".db")).sort(); +if (!dateien.length) { console.log("\nKeine Sicherung gefunden!"); process.exit(1); } +console.log("\nAuf diesem Rechner liegen jetzt:"); +let schlecht = 0; +for (const name of dateien) { + const pfad = join(ziel, name); + const kb = Math.round(statSync(pfad).size / 1024); + let zeile; + try { + const d = new DatabaseSync(pfad, { readOnly: true }); + const heil = Object.values(d.prepare("PRAGMA integrity_check").get())[0]; + const p = d.prepare("SELECT COUNT(*) AS n FROM personen").get().n; + const t = d.prepare( + "SELECT COUNT(*) AS n FROM sqlite_master WHERE type = \x27table\x27").get().n; + d.close(); + const gut = heil === "ok" && p > 0; + if (!gut) schlecht++; + zeile = `${gut ? "ok " : "FEHL"} ${heil}, ${p} Personen, ${t} Tabellen`; + } catch (f) { schlecht++; zeile = "FEHL nicht lesbar: " + f.message; } + console.log(` ${name.padEnd(28)} ${String(kb + " KB").padStart(8)} ${zeile}`); +} +console.log(schlecht ? `\n${schlecht} Sicherung(en) sind unbrauchbar!` + : "\nAlle Sicherungen sind lesbar und enthalten Daten."); +process.exit(schlecht ? 1 : 0); +' "$ZIEL"