#!/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. 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 DATEIEN=$(find "$ZIEL/dateien" "$ZIEL/wissen" -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://dogfather-universe.com/workspace/api/zustand/kopie || echo "000") if [ "$ANTWORT" = "200" ]; then echo " Dem Server zurueckgemeldet." 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"