#!/usr/bin/env bash # # Probe einer Sicherung: zurückspielen, Munin darauf starten, prüfen, # aufräumen. Eine Sicherung, die nie zurückgespielt wurde, ist nur eine # Hoffnung (BETRIEB.md, "Regelmäßig"). # # Geprüft wird: # 1. Jede Datei der Sicherung lässt sich lesen bzw. zurückspielen. # 2. Munin startet auf dem zurückgespielten Stand und meldet /ready. # 3. Munin sieht so viele Workspaces, wie die Sicherung enthält. # # Der echte Server wird dabei nicht berührt: Die Probe läuft in einem # eigenen Verzeichnis (SQLite) bzw. einer eigenen Datenbank (PostgreSQL), mit # eigenen Geheimnissen und auf einem eigenen Port. # # ── Aufruf ─────────────────────────────────────────────────────────────────── # # scripts/sicherung-pruefen.sh # # Der Ordner ist einer der zeitgestempelten Ordner von scripts/backup.sh. Ohne # Angabe wird der neueste unter BACKUP_DIR (Vorgabe "backups") genommen. # # SQLite (Standard): braucht sqlite3. # # PostgreSQL: DB_DRIVER=postgres und PROBE_DATABASE_URL — eine LEERE # Datenbank nur für die Probe, die einmal angelegt wird und dann bleibt: # # CREATE DATABASE munin_probe OWNER munin TEMPLATE template0 LOCALE 'C' ENCODING 'UTF8'; # # Das Skript spielt dort hinein und löscht danach alle Schemas munin_* wieder # daraus. Enthält die Datenbank vorher schon welche, bricht es ab, statt sie # zu überschreiben. psql und pg_restore lassen sich wie bei backup.sh über # PSQL und PG_RESTORE auf einen Container umstellen. # # Munin selbst: MUNIN_BIN zeigt auf das Programm. Ohne Angabe wird es aus dem # Repository gebaut (braucht Go). PROBE_PORT wählt den Port (Vorgabe 18080). # # Endet mit 0, wenn die Probe bestanden ist, sonst mit 1. set -euo pipefail treiber="$(echo "${DB_DRIVER:-sqlite}" | tr '[:upper:]' '[:lower:]')" port="${PROBE_PORT:-18080}" repo="$(cd "$(dirname "$0")/.." && pwd)" fehler() { echo "PROBE NICHT BESTANDEN: $*" >&2 exit 1 } # ── Welche Sicherung? ──────────────────────────────────────────────────────── sicherung="${1:-}" if [ -z "$sicherung" ]; then sicherung="$(ls -1d "${BACKUP_DIR:-backups}"/*/ 2>/dev/null | sort | tail -n 1 || true)" [ -n "$sicherung" ] || fehler "keine Sicherung unter ${BACKUP_DIR:-backups} gefunden" fi sicherung="${sicherung%/}" [ -d "$sicherung" ] || fehler "$sicherung ist kein Verzeichnis" echo "Sicherung: $sicherung" # ── Arbeitsverzeichnis und Aufräumen ───────────────────────────────────────── arbeit="$(mktemp -d)" munin_pid="" aufraeumen() { if [ -n "$munin_pid" ]; then kill "$munin_pid" 2>/dev/null || true wait "$munin_pid" 2>/dev/null || true fi if [ "$treiber" = "postgres" ] && [ -n "${probe_bereit:-}" ]; then schemas_loeschen || echo "Warnung: Schemas in der Probe-Datenbank konnten nicht gelöscht werden." >&2 fi rm -rf "$arbeit" } trap aufraeumen EXIT # ── Munin besorgen ─────────────────────────────────────────────────────────── munin="${MUNIN_BIN:-}" if [ -z "$munin" ]; then command -v go >/dev/null 2>&1 || fehler "MUNIN_BIN ist nicht gesetzt und Go ist nicht installiert" echo "Baue Munin …" (cd "$repo" && CGO_ENABLED=0 go build -o "$arbeit/munin" .) munin="$arbeit/munin" fi [ -x "$munin" ] || fehler "$munin ist nicht ausführbar" # ── Zurückspielen ──────────────────────────────────────────────────────────── if [ "$treiber" = "sqlite" ]; then command -v sqlite3 >/dev/null 2>&1 || fehler "sqlite3 ist nicht installiert" [ -f "$sicherung/control.db" ] || fehler "control.db fehlt in der Sicherung" mkdir -p "$arbeit/daten/workspaces" cp "$sicherung/control.db" "$arbeit/daten/" erwartet=0 for datei in "$sicherung"/workspaces/*.db; do [ -e "$datei" ] || continue cp "$datei" "$arbeit/daten/workspaces/" erwartet=$((erwartet + 1)) done for datei in "$arbeit/daten/control.db" "$arbeit"/daten/workspaces/*.db; do [ -e "$datei" ] || continue # Bei schwerem Schaden bricht sqlite3 selbst ab; die Meldung zählt dann # als Ergebnis, statt dass set -e das Skript wortlos beendet. ergebnis="$(sqlite3 "$datei" 'PRAGMA integrity_check;' 2>&1)" || true [ "$ergebnis" = "ok" ] || fehler "$(basename "$datei"): $ergebnis" done echo "Alle Dateien unbeschädigt (control.db und $erwartet Workspace(s))" umgebung=(DB_DRIVER=sqlite "DATA_DIR=$arbeit/daten") elif [ "$treiber" = "postgres" ]; then [ -n "${PROBE_DATABASE_URL:-}" ] || fehler "PROBE_DATABASE_URL ist nicht gesetzt (eine leere Datenbank nur für die Probe, siehe Kopf des Skripts)" [ -f "$sicherung/control.dump" ] || fehler "control.dump fehlt in der Sicherung" read -r -a psql <<<"${PSQL:-psql}" read -r -a pg_restore <<<"${PG_RESTORE:-pg_restore}" schemas_der_probe() { "${psql[@]}" --dbname="$PROBE_DATABASE_URL" --no-align --tuples-only --command \ "SELECT nspname FROM pg_namespace WHERE nspname LIKE 'munin\_%' ORDER BY nspname" "$arbeit/munin.log" 2>&1 & munin_pid=$! bereit="" for _ in $(seq 1 60); do if ! kill -0 "$munin_pid" 2>/dev/null; then cat "$arbeit/munin.log" >&2 fehler "Munin hat sich beim Start beendet (Log oben)" fi if curl -fsS "http://127.0.0.1:$port/ready" >/dev/null 2>&1; then bereit=1 break fi sleep 0.5 done if [ -z "$bereit" ]; then cat "$arbeit/munin.log" >&2 fehler "Munin meldet nach 30 Sekunden nicht /ready (Log oben)" fi # Munin nennt beim Start die Zahl der Workspaces: "Munin läuft auf … (N Workspaces)". gesehen="$(sed -n 's/.*Munin läuft auf .* (\([0-9]*\) Workspaces).*/\1/p' "$arbeit/munin.log" | head -n 1)" [ "$gesehen" = "$erwartet" ] || fehler "Munin sieht ${gesehen:-?} Workspace(s), die Sicherung enthält $erwartet" echo "Munin startet, meldet /ready und sieht alle $erwartet Workspace(s)" echo "PROBE BESTANDEN"