"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]>
142 lines
6.6 KiB
Bash
142 lines
6.6 KiB
Bash
#!/usr/bin/env bash
|
|
# ===================================================================
|
|
# Holt die Server-Sicherungen auf diesen Rechner, prueft sie und meldet
|
|
# sich beim Server zurueck.
|
|
#
|
|
# Eine Sicherung, die auf derselben Platte liegt wie das Original, ist
|
|
# keine -- sie hilft gegen versehentliches Loeschen, aber nicht gegen
|
|
# einen Plattenausfall. Deshalb dieser Schritt.
|
|
#
|
|
# Laeuft normalerweise von allein (Windows-Aufgabenplanung, taeglich,
|
|
# siehe tools/sicherung-einrichten.ps1). Von Hand geht auch:
|
|
# bash tools/sicherung-holen.sh
|
|
# ===================================================================
|
|
set -uo pipefail
|
|
|
|
ZIEL="$HOME/Documents/Obelix/Sicherungen/workspace"
|
|
PROTOKOLL="$ZIEL/lauf-protokoll.txt"
|
|
SCHLUESSEL_DATEI="$HOME/.dogfather-kopie-schluessel"
|
|
mkdir -p "$ZIEL"
|
|
|
|
# Jeder Lauf wird mitgeschrieben -- auch der gescheiterte. Ohne das
|
|
# waere hinterher nicht zu klaeren, ob die Aufgabe gar nicht lief oder
|
|
# ob sie lief und scheiterte. Das sind zwei sehr verschiedene Fehler.
|
|
notiere() { echo "$(date '+%Y-%m-%d %H:%M') $*" >> "$PROTOKOLL"; }
|
|
|
|
scheitern() {
|
|
echo "FEHLGESCHLAGEN: $*" >&2
|
|
notiere "FEHLER: $*"
|
|
exit 1
|
|
}
|
|
|
|
echo "Hole Sicherungen vom Server ..."
|
|
scp -q dogfather-server:/home/dogiweb/workspace-daten/sicherungen/*.db "$ZIEL/" \
|
|
|| scheitern "Server nicht erreichbar oder keine Sicherung vorhanden"
|
|
|
|
# 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.
|
|
#
|
|
# scp loescht nie etwas. Wer auf dem Server versehentlich ein PDF
|
|
# entfernt, hat es hier weiterhin -- das ist Absicht, nicht Nachlaessigkeit.
|
|
# 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 $(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
|
|
# SQLite seit Fassung 22 selbst mit, und auf diesem Windows-Rechner ist
|
|
# sqlite3 gar nicht vorhanden -- eine erste Fassung gab dort nur
|
|
# Fragezeichen aus und meldete trotzdem "fertig".
|
|
ERGEBNIS=$(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.error("Keine Sicherung gefunden!"); process.exit(1); }
|
|
let schlecht = 0, bytes = 0;
|
|
for (const name of dateien) {
|
|
const pfad = join(ziel, name);
|
|
const groesse = statSync(pfad).size;
|
|
bytes += groesse;
|
|
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;
|
|
d.close();
|
|
const gut = heil === "ok" && p > 0;
|
|
if (!gut) schlecht++;
|
|
zeile = `${gut ? "ok " : "FEHL"} ${heil}, ${p} Personen`;
|
|
} catch (f) { schlecht++; zeile = "FEHL nicht lesbar: " + f.message; }
|
|
console.error(` ${name.padEnd(28)} ${String(Math.round(groesse/1024) + " KB").padStart(8)} ${zeile}`);
|
|
}
|
|
/* Nur diese eine Zeile geht nach stdout -- das Skript liest sie aus. */
|
|
console.log(JSON.stringify({ anzahl: dateien.length, bytes, schlecht }));
|
|
process.exit(schlecht ? 1 : 0);
|
|
' "$ZIEL") || scheitern "eine oder mehrere Sicherungen sind unbrauchbar"
|
|
|
|
ANZAHL=$(node -e 'process.stdout.write(String(JSON.parse(process.argv[1]).anzahl))' "$ERGEBNIS")
|
|
BYTES=$(node -e 'process.stdout.write(String(JSON.parse(process.argv[1]).bytes))' "$ERGEBNIS")
|
|
|
|
# --- Rueckmeldung an den Server ------------------------------------------
|
|
#
|
|
# Das ist der Teil, der verhindert, dass die Sache STILL aufhoert zu
|
|
# laufen. Ohne ihn glaubt man, man haette eine Kopie ausserhalb -- und
|
|
# merkt erst im Ernstfall, dass der Rechner seit Wochen aus war oder die
|
|
# geplante Aufgabe klemmt. Auf der Automationen-Seite steht danach, wie
|
|
# alt die Kopie ist.
|
|
if [ -f "$SCHLUESSEL_DATEI" ]; then
|
|
SCHLUESSEL=$(tr -d ' \r\n' < "$SCHLUESSEL_DATEI")
|
|
ANTWORT=$(curl -s -o /dev/null -w "%{http_code}" -X POST \
|
|
-H "Content-Type: application/json" \
|
|
-H "X-Kopie-Schluessel: $SCHLUESSEL" \
|
|
-d "{\"dateien\":$DATEIEN,\"sicherungen\":$ANZAHL,\"bytes\":$BYTES,\"rechner\":\"$(hostname)\"}" \
|
|
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"
|
|
fi
|
|
else
|
|
echo " Hinweis: kein Schluessel unter $SCHLUESSEL_DATEI -- keine Rueckmeldung."
|
|
fi
|
|
|
|
echo
|
|
echo "Fertig: $ANZAHL Sicherung(en), $DATEIEN Datei(en), alle lesbar."
|
|
notiere "ok: $ANZAHL Sicherungen, $DATEIEN Dateien, $((BYTES/1024)) KB"
|