#!/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. # # CHAT-ANHAENGE GEHOEREN DAZU (ergaenzt 09.09.2026). # # Seit diesem Tag koennen im Chat Fotos und PDFs geschickt werden # (workspace-chat.js: chat-anhaenge/). Der Ordner wurde hier im selben # Zug eingetragen -- nicht spaeter, wenn es auffaellt: Genau so ist die # Luecke bei den Profilbildern entstanden. # # MATERIAL FEHLTE -- GEFUNDEN AM 23.09.2026, NICHT BEMERKT SEIT DEM # 22.09. # # server/workspace-material.js legt seit dem 22.09. einen Ordner # "material" an (2,4 MB auf dem Server). Er stand hier nicht. Nach # einem Plattenausfall waere die Materialbibliothek weg gewesen, ohne # dass die taegliche Meldung je etwas gesagt haette -- genau die # Luecke, wegen der diese Probe ueberhaupt gebaut wurde. # # Gefunden hat es nicht das Lesen, sondern tools/wiederherstellung- # proben.mjs: Sie liest die Ordner aus dem Quelltext (join(DATEN_ORDNER, # "...")) und vergleicht sie mit dieser Zeile. Das ist der Unterschied # zwischen einer Liste, die jemand pflegen muss, und einer, die # nachgezaehlt wird. # # DIE GIF-KISTE GEHOERT DAZU (ergaenzt 23.09.2026, im selben Zug wie # die Funktion -- nicht spaeter, wenn es auffaellt). Sie liegt in # chat-gifs/ und ist das einzige, was NUR dort liegt: Verschickte GIFs # sind Kopien in chat-anhaenge/, die Kiste selbst nicht. Ginge die # Platte kaputt, waere ohne diesen Eintrag die gesammelte Auswahl des # Teams weg, und die Nachrichten damit auch nicht mehr nachlegbar. # # 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, Anhaenge und die Bibliothek ..." ORDNER="dateien wissen profilbilder chat-anhaenge chat-gifs material" 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"