From 5a51940e60cc8d60b652041ce47d06c8c328b237 Mon Sep 17 00:00:00 2001 From: Mikei386 <44135113+Mikei386@users.noreply.github.com> Date: Sun, 23 Aug 2026 15:54:54 +0200 Subject: [PATCH] Keep OpenWebUI online during recovery backups --- docs/BARE_METAL_RECOVERY.md | 7 ++- platform/recovery/create-recovery-bundle.sh | 50 ++++++++++++++------- 2 files changed, 36 insertions(+), 21 deletions(-) diff --git a/docs/BARE_METAL_RECOVERY.md b/docs/BARE_METAL_RECOVERY.md index 86ce185..ffbf319 100644 --- a/docs/BARE_METAL_RECOVERY.md +++ b/docs/BARE_METAL_RECOVERY.md @@ -58,10 +58,9 @@ Das Skript nimmt ausschließlich auf: Der Klartext liegt nur in einem kurzlebigen root-only Verzeichnis unter `/tmp` und wird beim Ende entfernt. Das Ergebnis ist vollständig mit age verschlüsselt. Ohne erfolgreiche Ausgabe `RECOVERY_BUNDLE_OK` gilt die -Sicherung als fehlgeschlagen. Für ein konsistentes SQLite-Abbild stoppt das -Skript OpenWebUI kurz und startet es anschließend auch bei einem Fehler über -eine Aufräumroutine wieder. Laufende LLM-Profile und MCP-Dienste bleiben dabei -unangetastet. +Sicherung als fehlgeschlagen. Für ein konsistentes SQLite-Abbild verwendet das +Skript die Online-Backup-Schnittstelle der Datenbank. OpenWebUI, laufende +LLM-Profile und MCP-Dienste bleiben während der Sicherung verfügbar. ## Wiederherstellung nach SSD-Verlust diff --git a/platform/recovery/create-recovery-bundle.sh b/platform/recovery/create-recovery-bundle.sh index 710a748..98cf0e4 100755 --- a/platform/recovery/create-recovery-bundle.sh +++ b/platform/recovery/create-recovery-bundle.sh @@ -21,12 +21,9 @@ install -d -m 0700 "$(dirname "$OUTPUT")" [[ ! -e $OUTPUT ]] || die "Zieldatei existiert bereits: $OUTPUT" stage=$(mktemp -d /tmp/mike-ai-recovery.XXXXXX) -openwebui_was_running=false +sqlite_snapshot="" cleanup() { - if $openwebui_was_running; then - docker start "$OPENWEBUI_CONTAINER" >/dev/null 2>&1 || \ - printf 'WARNUNG: OpenWebUI konnte nach dem Backup nicht gestartet werden.\n' >&2 - fi + [[ -z $sqlite_snapshot ]] || rm -f -- "$sqlite_snapshot" rm -rf "$stage" } trap cleanup EXIT @@ -44,18 +41,37 @@ tar -C "$stage/rootfs" -czf "$stage/payload/host-config.tar.gz" . volume_path=$(docker volume inspect -f '{{.Mountpoint}}' "$OPENWEBUI_VOLUME") [[ -s $volume_path/webui.db ]] || die "OpenWebUI-Datenbank fehlt oder ist leer." -# OpenWebUI uses SQLite. A filesystem archive while the database is being -# written can be internally inconsistent even if tar itself succeeds. Keep the -# interruption short, but always restart the container through the EXIT trap. -if [[ $(docker inspect -f '{{.State.Running}}' "$OPENWEBUI_CONTAINER" 2>/dev/null || true) == true ]]; then - openwebui_was_running=true - docker stop --time 30 "$OPENWEBUI_CONTAINER" >/dev/null -fi -tar -C "$volume_path" -czf "$stage/payload/openwebui-data.tar.gz" . -if $openwebui_was_running; then - docker start "$OPENWEBUI_CONTAINER" >/dev/null - openwebui_was_running=false -fi +# OpenWebUI uses SQLite. The online backup API creates a transactionally +# consistent snapshot while the service remains available. The archive omits +# the live DB/WAL/SHM and stores that snapshot under the canonical DB name. +[[ $(docker inspect -f '{{.State.Running}}' "$OPENWEBUI_CONTAINER" 2>/dev/null || true) == true ]] || \ + die "OpenWebUI läuft nicht; Online-Datenbanksicherung nicht möglich." +sqlite_snapshot="$volume_path/.mike-ai-recovery-webui.db" +rm -f -- "$sqlite_snapshot" +docker exec -i "$OPENWEBUI_CONTAINER" python - <<'PY' +import os +import sqlite3 + +source = "/app/backend/data/webui.db" +snapshot = "/app/backend/data/.mike-ai-recovery-webui.db" +if os.path.exists(snapshot): + os.unlink(snapshot) +with sqlite3.connect(source) as src, sqlite3.connect(snapshot) as dst: + src.backup(dst) +with sqlite3.connect(snapshot) as check: + result = check.execute("PRAGMA integrity_check").fetchone() +if not result or result[0] != "ok": + raise SystemExit("SQLite integrity_check failed") +PY +[[ -s $sqlite_snapshot ]] || die "Konsistenter OpenWebUI-Snapshot wurde nicht erzeugt." +tar -C "$volume_path" \ + --exclude='./webui.db' \ + --exclude='./webui.db-wal' \ + --exclude='./webui.db-shm' \ + --transform='s#^\./\.mike-ai-recovery-webui\.db$#./webui.db#' \ + -czf "$stage/payload/openwebui-data.tar.gz" . +tar -tzf "$stage/payload/openwebui-data.tar.gz" | grep -Eq '(^|/)webui\.db$' || \ + die "OpenWebUI-Archiv enthält den konsistenten Datenbanksnapshot nicht." source_commit=unknown [[ ! -s $STACK_DIR/.mike-ai-source-commit ]] || source_commit=$(<"$STACK_DIR/.mike-ai-source-commit")