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 <[email protected]>
This commit is contained in:
2026-09-06 23:20:10 +02:00
co-authored by Claude Opus 5
parent 233cd76d78
commit 70039714c5
2 changed files with 117 additions and 7 deletions
+33 -6
View File
@@ -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"
+84 -1
View File
@@ -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
--------------------------------------------------------------------- */