Files
florian.wildeandClaude Opus 5 409a02561d feat(deploy): Wiederherstellung proben und Skript dafuer (#045)
Einer der vier Punkte bis 1.0.0: eine Sicherung einmal wirklich
zurueckspielen. Mit einem eigenen Stapel durchgespielt - Datenstand
festgehalten, Marke gesetzt, PDF abgelegt, gesichert, alle Volumes
zerstoert, zurueckgespielt. Datenbank, PDF und Anmeldung waren zurueck.

Zwei Luecken kamen erst dabei heraus:

1. Ohne --clean liess sich die Sicherung nur in eine LEERE Datenbank
   zurueckspielen - im Ernstfall also gerade nicht. Jetzt
   pg_dump --clean --if-exists, gegen eine Datenbank mit Daten geprueft.
2. Fuer die hochgeladenen PDFs gab es keinen beschriebenen Weg zurueck.

deploy/restore.sh haelt die Dienste an, spielt Datenbank und PDFs zurueck,
startet wieder und prueft die Erreichbarkeit. Die .env bleibt unangetastet:
Sie traegt APP_SECRET, und ein falscher Wert dort macht gespeicherte
Zugangsdaten unlesbar.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-10-10 19:31:35 +02:00

128 lines
4.7 KiB
Bash
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
# ProxLedger aus einer Sicherung wiederherstellen (#046).
#
# bash deploy/restore.sh /var/backups/proxledger # jüngste Sicherung
# bash deploy/restore.sh /var/backups/proxledger 20261010-192437
# bash deploy/restore.sh … --ja # ohne Rückfrage
#
# Spielt Datenbank und hochgeladene PDFs zurück. Die `.env` wird **nicht** angefasst:
# Sie trägt APP_SECRET, und ein falscher Wert dort macht gespeicherte Zugangsdaten unlesbar.
# Die passende Fassung liegt als env-<Stand>.txt in der Sicherung – im Zweifel von Hand vergleichen.
set -o errexit
set -o nounset
set -o pipefail
VERZEICHNIS=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
cd "$VERZEICHNIS"
blau() { printf '\033[1;36m› %s\033[0m\n' "$*"; }
gruen() { printf '\033[1;32m✓ %s\033[0m\n' "$*"; }
fehler() {
printf '\033[1;31m✗ %s\033[0m\n' "$*" >&2
exit 1
}
QUELLE="${1:-/var/backups/proxledger}"
STAND="${2:-}"
[ "${2:-}" = "--ja" ] && STAND=""
OHNE_RUECKFRAGE="nein"
for argument in "$@"; do [ "$argument" = "--ja" ] && OHNE_RUECKFRAGE="ja"; done
[ -d "$QUELLE" ] || fehler "Kein Sicherungsordner unter ${QUELLE}."
# Jüngste Sicherung nehmen, wenn kein Stand angegeben ist.
if [ -z "$STAND" ]; then
DATENBANK=$(ls -1t "${QUELLE}"/datenbank-*.sql.gz 2>/dev/null | head -n1 || true)
else
DATENBANK="${QUELLE}/datenbank-${STAND}.sql.gz"
fi
[ -n "${DATENBANK:-}" ] && [ -f "$DATENBANK" ] || fehler "Keine Datenbanksicherung in ${QUELLE} gefunden."
UPLOADS="${DATENBANK/datenbank-/uploads-}"
UPLOADS="${UPLOADS/.sql.gz/.tar.gz}"
# Siehe deploy/update.sh – der Dienst ist das eindeutigere Merkmal.
if systemctl cat proxledger.service >/dev/null 2>&1; then
ART="dienst"
else
ART="docker"
fi
printf '\033[1;33m! Achtung: Der aktuelle Datenbestand wird durch die Sicherung ersetzt.\033[0m\n'
echo " Installationsart: ${ART}"
echo " Datenbank: $(basename "$DATENBANK")"
echo " Uploads: $([ -f "$UPLOADS" ] && basename "$UPLOADS" || echo 'keine in dieser Sicherung')"
if [ "$OHNE_RUECKFRAGE" != "ja" ]; then
read -r -p "Wirklich zurückspielen? [tippen Sie ja] " antwort
[ "$antwort" = "ja" ] || fehler "Abgebrochen."
fi
# ---------------------------------------------------------------- Datenbank
blau "Datenbank zurückspielen …"
if [ "$ART" = "docker" ]; then
# Web und Worker anhalten: Sie würden während des Einspielens weiterschreiben.
docker compose stop web worker >/dev/null 2>&1 || true
docker compose up -d postgres >/dev/null
# Kurz warten, bis Postgres Verbindungen annimmt.
for _ in $(seq 1 30); do
docker compose exec -T postgres pg_isready -U "${POSTGRES_USER:-proxledger}" >/dev/null 2>&1 && break
sleep 1
done
gunzip -c "$DATENBANK" | docker compose exec -T postgres psql -U "${POSTGRES_USER:-proxledger}" \
-d "${POSTGRES_DB:-proxledger}" >/dev/null
else
systemctl stop proxledger proxledger-worker >/dev/null 2>&1 || true
# shellcheck disable=SC1091
set -o allexport
. ./.env
set +o allexport
[ -n "${DATABASE_URL:-}" ] || fehler "DATABASE_URL fehlt in der .env."
gunzip -c "$DATENBANK" | psql "$DATABASE_URL" >/dev/null
fi
gruen "Datenbank zurückgespielt."
# ---------------------------------------------------------------- Uploads
if [ -f "$UPLOADS" ]; then
blau "Hochgeladene PDFs zurückspielen …"
if [ "$ART" = "docker" ]; then
# Das Archiv enthält den Pfad /data/uploads – deshalb nach / entpacken.
docker compose run --rm -T --entrypoint tar web -xzf - -C / < "$UPLOADS"
else
ZIEL_UPLOADS="${UPLOAD_DIR:-${VERZEICHNIS}/data/uploads}"
mkdir -p "$(dirname "$ZIEL_UPLOADS")"
tar -xzf "$UPLOADS" -C "$(dirname "$ZIEL_UPLOADS")"
fi
gruen "PDFs zurückgespielt."
fi
# ---------------------------------------------------------------- Starten und prüfen
blau "Dienste starten …"
if [ "$ART" = "docker" ]; then
docker compose up -d >/dev/null
else
systemctl start proxledger proxledger-worker
fi
PORT="${PORT:-3000}"
for versuch in $(seq 1 45); do
if [ "$ART" = "docker" ]; then
erreichbar=$(docker compose exec -T web wget -qO- "http://127.0.0.1:3000/api/gesundheit" >/dev/null 2>&1 && echo ja || echo nein)
else
erreichbar=$(curl -fsS -o /dev/null --max-time 2 "http://localhost:${PORT}/api/gesundheit" && echo ja || echo nein)
fi
if [ "$erreichbar" = "ja" ]; then
gruen "ProxLedger läuft wieder (nach ${versuch} s)."
echo
echo "Bitte jetzt anmelden und stichprobenartig prüfen, ob die Daten vollständig sind."
echo "Passt das Passwort nicht, stammt die Sicherung aus einer Zeit vor der letzten Änderung –"
echo "dann hilft: npm run benutzer -- passwort <email>"
exit 0
fi
sleep 1
done
fehler "Das Dashboard antwortet nicht. Protokoll ansehen: docker compose logs -n 50 web"