docs(betrieb): Handbuch, Bedrohungsanalyse, geprobte Sicherung (#029)
Handbuch mit Bildern fuer alle Ablaeufe, darunter der Kern- Anwendungsfall und der Deployment-Zugang fuer Claude Code. Betriebsdoku zu Installation, authentik und Reverse Proxy sowie eine Bedrohungsanalyse mit Notfallplan fuer den Fall, dass sshkv selbst kompromittiert wird. Sicherung und Wiederherstellung als Skripte, auf einem Wegwerf-Stapel geprobt. Dabei fielen zwei Fehler auf: das Schema der Warteschlange liess sich nicht zurueckspielen, und ein Abbruch ohne Transaktion loeschte es halb. Jetzt nur Schema public, in einer Transaktion, mit Pruefwert des Master-Keys. Der Worker startet nicht, wenn der Master-Key nicht zur Datenbank passt. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Executable
+80
@@ -0,0 +1,80 @@
|
||||
#!/usr/bin/env bash
|
||||
# SSHKeyVault aus einer Sicherung wiederherstellen (#029).
|
||||
#
|
||||
# bash deploy/wiederherstellen.sh /var/backups/sshkv # jüngste Sicherung
|
||||
# bash deploy/wiederherstellen.sh /var/backups/sshkv 20261012-170000
|
||||
# bash deploy/wiederherstellen.sh … --ja # ohne Rückfrage
|
||||
#
|
||||
# Voraussetzung: Der zur Sicherung gehoerende Master-Key liegt an der in .env genannten Stelle
|
||||
# (SSHKV_MASTER_KEY_HOST_FILE). Das Skript vergleicht ihn mit dem Pruefwert der Sicherung und
|
||||
# bricht ab, wenn er nicht passt – sonst laeuft sshkv ohne lesbare Private Keys an.
|
||||
|
||||
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/sshkv}"
|
||||
STAND=""
|
||||
OHNE_RUECKFRAGE="nein"
|
||||
for argument in "${@:2}"; do
|
||||
if [ "$argument" = "--ja" ]; then OHNE_RUECKFRAGE="ja"; else STAND="$argument"; fi
|
||||
done
|
||||
|
||||
[ -d "$QUELLE" ] || fehler "Kein Sicherungsordner unter ${QUELLE}."
|
||||
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."
|
||||
PRUEFWERT="${DATENBANK/datenbank-/master-key-pruefwert-}"
|
||||
PRUEFWERT="${PRUEFWERT/.sql.gz/.txt}"
|
||||
|
||||
ENV_DATEI="${SSHKV_ENV_DATEI:-.env}"
|
||||
SCHLUESSEL="$(grep -E '^SSHKV_MASTER_KEY_HOST_FILE=' "$ENV_DATEI" 2>/dev/null | cut -d= -f2- || true)"
|
||||
SCHLUESSEL="${SCHLUESSEL:-./geheim/master.key}"
|
||||
if [ -f "$PRUEFWERT" ]; then
|
||||
[ -r "$SCHLUESSEL" ] || fehler "Master-Key ${SCHLUESSEL} nicht lesbar (mit sudo ausführen?)."
|
||||
[ "$(sha256sum "$SCHLUESSEL" | cut -d' ' -f1)" = "$(cat "$PRUEFWERT")" ] \
|
||||
|| fehler "Der Master-Key unter ${SCHLUESSEL} gehört NICHT zu dieser Sicherung. Abbruch."
|
||||
gruen "Master-Key passt zur Sicherung."
|
||||
else
|
||||
printf '\033[1;33m! Kein Prüfwert zur Sicherung – der Worker prüft den Master-Key beim Start.\033[0m\n'
|
||||
fi
|
||||
|
||||
printf '\033[1;33m! Achtung: Der aktuelle Datenbestand wird durch die Sicherung ersetzt.\033[0m\n'
|
||||
echo " Sicherung: $(basename "$DATENBANK")"
|
||||
if [ "$OHNE_RUECKFRAGE" != "ja" ]; then
|
||||
read -r -p "Wirklich zurückspielen? [tippen Sie ja] " antwort
|
||||
[ "$antwort" = "ja" ] || fehler "Abgebrochen."
|
||||
fi
|
||||
|
||||
blau "Weboberfläche und Worker anhalten …"
|
||||
docker compose stop web worker
|
||||
blau "Datenbank zurückspielen …"
|
||||
# Eine Transaktion: scheitert etwas, bleibt der alte Stand vollstaendig erhalten (in der
|
||||
# Wiederherstellungsprobe hatte ein Abbruch ohne Transaktion das Schema pgboss halb geloescht).
|
||||
gunzip -c "$DATENBANK" | docker compose exec -T db psql -U sshkv -d sshkv -v ON_ERROR_STOP=1 --single-transaction -q >/dev/null \
|
||||
|| fehler "Zurückspielen fehlgeschlagen. Web und Worker sind angehalten – nach Klärung: docker compose up -d"
|
||||
# Die Warteschlange gehoert nicht zur Sicherung: frisch anlegen lassen (macht der Worker beim
|
||||
# Start), offene Vorgaenge abbrechen – ihre Jobs sind damit weg.
|
||||
docker compose exec -T db psql -U sshkv -d sshkv -q -c "DROP SCHEMA IF EXISTS pgboss CASCADE" >/dev/null
|
||||
docker compose exec -T db psql -U sshkv -d sshkv -q -c \
|
||||
"UPDATE \"Vorgang\" SET status = 'ABGEBROCHEN', fehler = 'Durch Wiederherstellung abgebrochen.', \"beendetAm\" = now() WHERE status IN ('WARTEND','LAEUFT')" >/dev/null
|
||||
blau "Dienste starten …"
|
||||
docker compose up -d --wait web worker
|
||||
sleep 3
|
||||
if docker compose logs --tail 20 worker 2>&1 | grep -q "passt nicht zur Datenbank"; then
|
||||
fehler "Der Worker meldet: Master-Key passt nicht zur Datenbank. Siehe docker compose logs worker."
|
||||
fi
|
||||
gruen "Wiederhergestellt aus $(basename "$DATENBANK")."
|
||||
Reference in New Issue
Block a user