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>
This commit is contained in:
+3
-2
@@ -30,7 +30,8 @@ echo "Sichere ProxLedger (${ART}) nach ${ZIEL} …"
|
||||
|
||||
if [ "$ART" = "docker" ]; then
|
||||
echo "Sichere Datenbank …"
|
||||
docker compose exec -T postgres pg_dump -U "${POSTGRES_USER:-proxledger}" "${POSTGRES_DB:-proxledger}" \
|
||||
docker compose exec -T postgres pg_dump --clean --if-exists \
|
||||
-U "${POSTGRES_USER:-proxledger}" "${POSTGRES_DB:-proxledger}" \
|
||||
| gzip > "${ZIEL}/datenbank-${STAND}.sql.gz"
|
||||
|
||||
echo "Sichere hochgeladene Dateien …"
|
||||
@@ -48,7 +49,7 @@ else
|
||||
}
|
||||
|
||||
echo "Sichere Datenbank …"
|
||||
pg_dump "$DATABASE_URL" | gzip > "${ZIEL}/datenbank-${STAND}.sql.gz"
|
||||
pg_dump --clean --if-exists "$DATABASE_URL" | gzip > "${ZIEL}/datenbank-${STAND}.sql.gz"
|
||||
|
||||
echo "Sichere hochgeladene Dateien …"
|
||||
UPLOADS="${UPLOAD_DIR:-${VERZEICHNIS}/data/uploads}"
|
||||
|
||||
Executable
+127
@@ -0,0 +1,127 @@
|
||||
#!/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"
|
||||
Reference in New Issue
Block a user