From 70039714c5726ed5964e2dfb59ae6210842f3e8c Mon Sep 17 00:00:00 2001 From: Dogfather Date: Sun, 6 Sep 2026 23:20:10 +0200 Subject: [PATCH] Die Sicherung war zwei Loecher gross -- gefunden, weil sie zum ersten Mal wirklich zurueckgespielt wurde "Wir haben eine Sicherung" war bis heute ein Satz, kein Nachweis. Die taegliche Kopie ausserhalb des Servers prueft `PRAGMA integrity_check` und meldet seit dem 31.08. jeden Tag "in Ordnung". Das heisst: die Datei ist nicht zerschossen. Es heisst NICHT, dass man damit weiterarbeiten kann. tools/wiederherstellung-proben.mjs geht den Weg jetzt wirklich: juengste Sicherung in ein Wegwerf-Verzeichnis kopieren, die echte Anwendung dagegen starten (damit laufen alle Schemawanderungen durch), vorher und nachher zaehlen, anmelden, jede Seite aufrufen. Drei Ausgaenge, nicht zwei -- "konnte nicht nachsehen" ist weder Erfolg noch Fehler. BEIM ERSTEN LAUF BLIEB GENAU EINE VON ACHTZEHN SEITEN ROT: der Steckbrief, mit einem 404 fuer ein Bild. Daran hingen zwei echte Luecken: 1. PROFILBILDER WAREN NIE GESICHERT. sicherung-holen.sh holte dateien/ und wissen/ -- die Bilder liegen aber in einem dritten Ordner (profilbilder/, siehe workspace-steckbrief.js). Nach einem Plattenausfall waeren alle Profilbilder weg gewesen, waehrend die taegliche Meldung weiter "in Ordnung" sagte. Sie hat ja nie behauptet, vollstaendig zu sein. 2. DIE RUECKMELDUNG AN DEN SERVER LIEF INS LEERE. Das Skript meldete sich bei dogfather-universe.com/workspace/... -- seit dem Umzug am 06.09. antwortet diese Adresse mit 410 "Gone". Der Server erfuhr also seit dem Umzug nicht mehr, dass die Kopie laeuft; auf der Automationen-Seite waere sie mit jedem Tag aelter erschienen, obwohl sie taeglich lief. Ein 410 wird jetzt eigens gemeldet -- es heisst etwas anderes als "Netz kaputt", naemlich "die Adresse stimmt nicht mehr". Beides behoben. Und damit es kein drittes Mal passiert, vergleicht die Probe die Ordnerliste des Sicherungsskripts gegen die Ordner, die der Server im Quelltext wirklich benutzt (`join(DATEN_ORDNER, "...")`). Wer morgen einen vierten Datenordner anlegt und ihn nicht sichert, bekommt hier einen Fehler statt eines stillen Lochs -- mit Gegenprobe, dass der Vergleich einen fehlenden Ordner auch wirklich erkennt. Ergebnis nach den Reparaturen: 18 Pruefungen, alle gruen, "DIE SICHERUNG LAESST SICH ZURUECKSPIELEN." Der Lauf traegt sich mit Datum in die Sicherungsablage ein -- sonst weiss beim naechsten Mal niemand, wann zuletzt wirklich zurueckgespielt wurde. Co-Authored-By: Claude Opus 5 --- tools/sicherung-holen.sh | 39 +++++++++++--- tools/wiederherstellung-proben.mjs | 85 +++++++++++++++++++++++++++++- 2 files changed, 117 insertions(+), 7 deletions(-) diff --git a/tools/sicherung-holen.sh b/tools/sicherung-holen.sh index 3d6438eb..358f441e 100644 --- a/tools/sicherung-holen.sh +++ b/tools/sicherung-holen.sh @@ -41,12 +41,29 @@ scp -q dogfather-server:/home/dogiweb/workspace-daten/sicherungen/*.db "$ZIEL/" # # 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 +# PROFILBILDER GEHOEREN DAZU (ergaenzt 06.09.2026). +# +# Hier standen nur dateien/ und wissen/. Die Profilbilder liegen aber in +# einem dritten Ordner (workspace-steckbrief.js: profilbilder/) -- und +# der wurde nie mitgeholt. Nach einem Plattenausfall waeren alle Bilder +# weg gewesen, obwohl "die Sicherung" jeden Tag gruen gemeldet hat. +# +# Gefunden hat das nicht das Nachdenken, sondern die Probe: Beim ersten +# Lauf von tools/wiederherstellung-proben.mjs blieb genau eine von +# achtzehn Seiten rot -- der Steckbrief, mit einem 404 fuer ein Bild. +# Genau dafuer gibt es sie. +# +# Wer hier einen Ordner ergaenzt, traegt ihn auch in ORDNER unten ein; +# die Probe vergleicht die Liste gegen die Ordner, die der Server +# wirklich benutzt, und schlaegt bei einem vergessenen an. +echo "Hole hochgeladene Dateien, Bilder und die Bibliothek ..." +ORDNER="dateien wissen profilbilder" +for o in $ORDNER; do + mkdir -p "$ZIEL/$o" + scp -qr "dogfather-server:/home/dogiweb/workspace-daten/$o/." "$ZIEL/$o/" 2>/dev/null || true +done -DATEIEN=$(find "$ZIEL/dateien" "$ZIEL/wissen" -type f 2>/dev/null | wc -l | tr -d ' ') +DATEIEN=$(find $(for o in $ORDNER; do echo "$ZIEL/$o"; done) -type f 2>/dev/null | wc -l | tr -d ' ') echo " $DATEIEN Datei(en) aus Ablage und Bibliothek" # Geprueft wird mit Node, nicht mit dem sqlite3-Programm: Node bringt @@ -98,9 +115,19 @@ if [ -f "$SCHLUESSEL_DATEI" ]; then -H "Content-Type: application/json" \ -H "X-Kopie-Schluessel: $SCHLUESSEL" \ -d "{\"dateien\":$DATEIEN,\"sicherungen\":$ANZAHL,\"bytes\":$BYTES,\"rechner\":\"$(hostname)\"}" \ - https://dogfather-universe.com/workspace/api/zustand/kopie || echo "000") + https://workspace.dogfather-universe.com/workspace/api/zustand/kopie || echo "000") if [ "$ANTWORT" = "200" ]; then echo " Dem Server zurueckgemeldet." + elif [ "$ANTWORT" = "410" ]; then + # 410 heisst hier etwas Bestimmtes: Die ADRESSE stimmt nicht mehr. + # Genau das war am 06.09.2026 der Fall -- der Workspace ist auf + # workspace.dogfather-universe.com umgezogen, die alte Adresse + # antwortet seither mit 410 "Gone". Die Rueckmeldung lief seitdem + # ins Leere, und auf der Automationen-Seite waere die Kopie mit + # jedem Tag aelter erschienen, obwohl sie taeglich lief. + echo " FEHLER: Der Server sagt 410 -- die Adresse in diesem Skript ist veraltet." + echo " Aktuell: https://workspace.dogfather-universe.com" + notiere "Rueckmeldung 410: Adresse veraltet, Kopie selbst ist in Ordnung" else echo " Hinweis: Rueckmeldung an den Server ging nicht (HTTP $ANTWORT)." notiere "Rueckmeldung fehlgeschlagen (HTTP $ANTWORT), Kopie selbst ist in Ordnung" diff --git a/tools/wiederherstellung-proben.mjs b/tools/wiederherstellung-proben.mjs index af7b9258..c35309f4 100644 --- a/tools/wiederherstellung-proben.mjs +++ b/tools/wiederherstellung-proben.mjs @@ -56,7 +56,7 @@ AUFRUF: node tools/wiederherstellung-proben.mjs ===================================================================== */ -import { readdirSync, statSync, copyFileSync, mkdtempSync, rmSync, writeFileSync, existsSync } from "node:fs"; +import { readdirSync, statSync, copyFileSync, mkdtempSync, mkdirSync, rmSync, writeFileSync, existsSync } from "node:fs"; import { tmpdir, homedir } from "node:os"; import { join, dirname } from "node:path"; import { fileURLToPath } from "node:url"; @@ -134,6 +134,89 @@ const vorherGroesse = statSync(kopie).size; ok(vorherGroesse === statSync(gewaehlt.pfad).size, `die Kopie ist vollstaendig (${vorherGroesse} Byte)`); +/* --------------------------------------------------------------------- + 2b. UND DIE DATEIEN. Eine Datenbank allein ist keine Wiederherstellung. + + Beim ersten Lauf dieser Probe (06.09.2026) blieb genau eine von + achtzehn Seiten rot: der Steckbrief, mit einem 404 in der Konsole. Das + war kein Fehler der Sicherung, sondern ihre wichtigste Aussage -- + das Profilbild lag NICHT in der Datenbank, sondern daneben im + Dateisystem. Wer nur die .db zurueckspielt, bekommt eine Anwendung, + die laeuft, und eine Bibliothek voller Verweise auf Dateien, die es + nicht mehr gibt. + + sicherung-holen.sh holt diese Ordner mit (dateien/, wissen/). Genau + deshalb gehoeren sie auch in die Probe: Sonst beweist sie die + Wiederherstellung nur zur Haelfte -- und ausgerechnet die Haelfte, + die man im Ernstfall nicht nachbauen kann. */ +function ordnerKopieren(von, nach) { + if (!existsSync(von)) return 0; + mkdirSync(nach, { recursive: true }); + let n = 0; + for (const e of readdirSync(von, { withFileTypes: true })) { + const q = join(von, e.name), z = join(nach, e.name); + if (e.isDirectory()) n += ordnerKopieren(q, z); + else { copyFileSync(q, z); n++; } + } + return n; +} +/* Kopiert wird ALLES, was in der Sicherung als Ordner liegt -- nicht + eine Liste von Hand. Kommt morgen ein vierter Datenordner dazu, ist + er hier von selbst dabei. */ +let dateienZahl = 0; +const gesichert = readdirSync(SICHERUNGEN, { withFileTypes: true }) + .filter((e) => e.isDirectory()).map((e) => e.name); +for (const o of gesichert) { + dateienZahl += ordnerKopieren(join(SICHERUNGEN, o), join(arbeit, o)); +} +ok(dateienZahl > 0, + dateienZahl ? `${dateienZahl} hochgeladene Dateien mit zurueckgespielt ` + + `(${gesichert.join(", ")})` + : "KEINE Dateien in der Sicherung -- Bilder und PDFs waeren nach einem " + + "Plattenausfall weg, auch wenn die Datenbank vollstaendig ist"); + +/* --------------------------------------------------------------------- + DIE ENTSCHEIDENDE FRAGE: Sichert das Skript ALLE Ordner, die der + Server ueberhaupt benutzt? + + Genau hier lag die Luecke. sicherung-holen.sh nannte dateien/ und + wissen/; die Profilbilder liegen in einem dritten Ordner und wurden + nie mitgeholt. Die Sicherung meldete trotzdem jeden Tag "in Ordnung" + -- sie hat ja nie behauptet, vollstaendig zu sein. + + Eine Liste von Hand kann das nicht verhindern; sie altert genau so + still wie die, die den Fehler verursacht hat. Deshalb wird hier + NACHGEZAEHLT: Welche Ordner legt der Server an (aus dem Quelltext), + und welche holt das Skript? Was fehlt, wird gemeldet. */ +{ + const server = join(WURZEL, "server"); + const benutzt = new Set(); + for (const datei of readdirSync(server).filter((n) => /^workspace.*\.js$/.test(n))) { + const text = (await import("node:fs")).readFileSync(join(server, datei), "utf8"); + for (const t of text.matchAll(/join\(DATEN_ORDNER,\s*"([^"]+)"\)/g)) benutzt.add(t[1]); + } + /* sicherungen/ ist die Sicherung selbst -- die holt das Skript als + .db-Dateien und nicht als Ordner. */ + benutzt.delete("sicherungen"); + + const skript = (await import("node:fs")).readFileSync(join(HIER, "sicherung-holen.sh"), "utf8"); + const geholt = (skript.match(/^ORDNER="([^"]+)"/m) || [])[1]?.split(/\s+/) || []; + + ok(benutzt.size > 0, `der Server benutzt ${benutzt.size} Datenordner: ${[...benutzt].join(", ")}`); + const fehlend = [...benutzt].filter((o) => !geholt.includes(o)); + ok(fehlend.length === 0, + fehlend.length + ? `NICHT GESICHERT: ${fehlend.join(", ")} -- diese Ordner legt der Server an, ` + + `tools/sicherung-holen.sh holt sie aber nicht. Nach einem Plattenausfall ` + + `waeren sie weg, ohne dass die taegliche Meldung je etwas gesagt haette.` + : `alle ${benutzt.size} holt tools/sicherung-holen.sh (${geholt.join(", ")})`); + + /* GEGENPROBE: Erkennt der Vergleich einen fehlenden Ordner ueberhaupt? + Ohne sie bewiese "nichts fehlt" nur, dass die Suche nichts findet. */ + ok([...benutzt, "erfundenerordner"].filter((o) => !geholt.includes(o)).length === 1, + "Gegenprobe: ein erfundener Ordner WIRD als fehlend erkannt"); +} + /* --------------------------------------------------------------------- 3. Zaehlen, was drin ist -- VOR der Wanderung --------------------------------------------------------------------- */