From 0c21aed7af4f4e08ce9a367b6de31d9a9bb37588 Mon Sep 17 00:00:00 2001 From: Mikei386 <44135113+Mikei386@users.noreply.github.com> Date: Fri, 21 Aug 2026 14:11:45 +0200 Subject: [PATCH] Automate reference host data restore --- docs/MIGRATION.md | 22 ++++ docs/RECOVERY_REQUIREMENTS.md | 9 +- .../migration/restore-reference-backup.sh | 119 ++++++++++++++++++ 3 files changed, 147 insertions(+), 3 deletions(-) create mode 100755 platform/migration/restore-reference-backup.sh diff --git a/docs/MIGRATION.md b/docs/MIGRATION.md index 88727b0..56728fb 100644 --- a/docs/MIGRATION.md +++ b/docs/MIGRATION.md @@ -37,5 +37,27 @@ 12. Standardbenchmark und Sicherheitsprüfung ausführen. 13. Erst danach Clients umstellen. +## Referenz-Backup automatisiert einspielen + +Nach einem erfolgreichen Lauf von `install.sh` kann eine Sicherung des alten +Referenzhosts gezielt importiert werden: + +```bash +sudo ./platform/migration/restore-reference-backup.sh \ + /data/ki-migration-backup-YYYYMMDD +``` + +Das Skript prüft zuerst die Archiv-Hashes und übernimmt ausschließlich: + +- die persistente OpenWebUI-Datenbank samt Einstellungen und Uploads, +- den dazugehörigen WebUI- und Router-Schlüssel, +- die freigegebenen HA-, ARR- und Unraid-Secrets, +- `runraid`, dessen privaten Schlüssel, Zertifikat und Known-Hosts-Datei. + +Es übernimmt bewusst **keine** alten Compose-Dateien, Routerprofile, +Experimentdienste oder `.before-*`-Altstände. Vor dem Ersetzen des frischen +OpenWebUI-Volumes wird unter `/data/open-webui-before-restore-*.tar.gz` eine +Rückfallsicherung erstellt. Secret-Werte werden nicht ausgegeben. + Der alte Host bleibt bis zum bestandenen Abnahmetest unverändert und dient nur als Referenz. Es werden keine Caches oder unbekannten Altverzeichnisse kopiert. diff --git a/docs/RECOVERY_REQUIREMENTS.md b/docs/RECOVERY_REQUIREMENTS.md index 02ec6c5..0bb42a5 100644 --- a/docs/RECOVERY_REQUIREMENTS.md +++ b/docs/RECOVERY_REQUIREMENTS.md @@ -178,9 +178,12 @@ run-acceptance-tests ``` `platform/mcp/install-tools.sh` installiert den Webbereich automatisch und -aktiviert HA, ARR und Unraid nur bei vorhandenen Secret-/Programmdateien. Offen -bleiben ein kompletter Leerhost-Probelauf und der automatisierte Import einer -bereits bestehenden Open-WebUI-Datenbank. +aktiviert HA, ARR und Unraid nur bei vorhandenen Secret-/Programmdateien. +`platform/migration/restore-reference-backup.sh` importiert eine bestehende +OpenWebUI-Datenbank und ausschließlich die freigegebenen Tool-Secrets, ohne +experimentelle Altcontainer zurückzubringen. Der Leerhost-Probelauf wird auf +Athena praktisch protokolliert und seine Korrekturen fließen direkt in den +Installer zurück. Jeder Schritt muss wiederholbar, einzeln prüfbar und bei Fehlern abbrechbar sein. Ein fehlgeschlagener Schritt darf keinen halb aktivierten Dienst diff --git a/platform/migration/restore-reference-backup.sh b/platform/migration/restore-reference-backup.sh new file mode 100755 index 0000000..99820ba --- /dev/null +++ b/platform/migration/restore-reference-backup.sh @@ -0,0 +1,119 @@ +#!/usr/bin/env bash +# Restore persistent UI data and the approved secret subset from a reference +# host backup without reviving its obsolete containers or configuration files. +set -Eeuo pipefail +umask 077 + +BACKUP=${1:-} +STACK_DIR=${STACK_DIR:-/opt/mike-ai/stack} +SECRETS_DIR=${SECRETS_DIR:-/etc/mike-ai} +OPENWEBUI_VOLUME=${OPENWEBUI_VOLUME:-mike-ai_open-webui-data} + +die() { printf 'FEHLER: %s\n' "$*" >&2; exit 1; } +log() { printf '\n==> %s\n' "$*"; } + +[[ $EUID -eq 0 ]] || die "Bitte als root ausführen." +[[ -n $BACKUP && -d $BACKUP ]] || \ + die "Aufruf: $0 /pfad/zum/ki-migration-backup" +[[ -f $BACKUP/SHA256SUMS ]] || die "SHA256SUMS fehlt." +[[ -f $SECRETS_DIR/stack.env ]] || die "$SECRETS_DIR/stack.env fehlt." + +ui_archive=$BACKUP/archives/open-webui-migrated.tar.gz +mcp_archive=$BACKUP/archives/mcp-and-search-config.tar.gz +for archive in "$ui_archive" "$mcp_archive"; do + [[ -f $archive ]] || die "Archiv fehlt: $archive" + expected=$(awk -v suffix="/${archive#"$BACKUP"/}" \ + '$2 ~ suffix "$" {print $1; exit}' "$BACKUP/SHA256SUMS") + [[ -n $expected ]] || die "Keine Prüfsumme für $archive gefunden." + actual=$(sha256sum "$archive" | awk '{print $1}') + [[ $actual == "$expected" ]] || die "Prüfsumme falsch: $archive" +done + +stage=$(mktemp -d /data/mike-ai-restore.XXXXXX) +trap 'rm -rf "$stage"' EXIT + +log "OpenWebUI-Sicherung entpacken und prüfen" +tar -xzf "$ui_archive" -C "$stage" +ui_source=$stage/open-webui-migrated +[[ -s $ui_source/data/webui.db ]] || die "OpenWebUI-Datenbank fehlt oder ist leer." +[[ -f $ui_source/openwebui.env ]] || die "OpenWebUI-Umgebung fehlt." + +old_webui_key=$(sed -n 's/^WEBUI_SECRET_KEY=//p' \ + "$ui_source/openwebui.env" | head -n 1) +[[ -n $old_webui_key ]] || die "WEBUI_SECRET_KEY fehlt in der Sicherung." + +log "Freigegebene MCP-Secrets isoliert entpacken" +mkdir -p "$stage/approved" +tar -xzf "$mcp_archive" -C "$stage/approved" \ + etc/mike-ai/router-api-key \ + etc/mike-ai/homeassistant-admin-mcp.env \ + etc/mike-ai/arr-mcp.env \ + etc/mike-ai/runraid/.env \ + etc/mike-ai/runraid/homeserver-cert.pem \ + etc/mike-ai/keys/unraid_root \ + etc/mike-ai/ssh/known_hosts_unraid_ai \ + usr/local/bin/runraid + +old_router_key=$(<"$stage/approved/etc/mike-ai/router-api-key") +[[ -n $old_router_key ]] || die "Router-Key in der Sicherung ist leer." + +set_env_value() { + local file=$1 key=$2 value=$3 tmp + tmp=$(mktemp "${file}.XXXXXX") + awk -v key="$key" -v value="$value" ' + BEGIN { done=0 } + $0 ~ "^" key "=" { print key "=" value; done=1; next } + { print } + END { if (!done) print key "=" value } + ' "$file" >"$tmp" + install -m 0600 "$tmp" "$file" + rm -f "$tmp" +} + +log "Aktuellen leeren UI-Stand als Rückfall sichern" +volume_path=$(docker volume inspect -f '{{.Mountpoint}}' "$OPENWEBUI_VOLUME") +fallback=/data/open-webui-before-restore-$(date +%Y%m%d-%H%M%S).tar.gz +tar -czf "$fallback" -C "$volume_path" . + +log "OpenWebUI-Daten atomar ersetzen" +docker stop mike-ai-open-webui >/dev/null 2>&1 || true +rsync -a --delete "$ui_source/data/" "$volume_path/" + +log "Schlüssel und aktuelle Tool-Secrets wiederherstellen" +set_env_value "$SECRETS_DIR/stack.env" WEBUI_SECRET_KEY "$old_webui_key" +set_env_value "$SECRETS_DIR/stack.env" ROUTER_API_KEY "$old_router_key" + +install -d -m 0700 "$SECRETS_DIR/runraid" "$SECRETS_DIR/keys" "$SECRETS_DIR/ssh" +install -m 0600 "$stage/approved/etc/mike-ai/homeassistant-admin-mcp.env" \ + "$SECRETS_DIR/homeassistant-admin-mcp.env" +install -m 0600 "$stage/approved/etc/mike-ai/arr-mcp.env" \ + "$SECRETS_DIR/arr-mcp.env" +install -m 0600 "$stage/approved/etc/mike-ai/runraid/.env" \ + "$SECRETS_DIR/runraid/.env" +install -m 0600 "$stage/approved/etc/mike-ai/runraid/homeserver-cert.pem" \ + "$SECRETS_DIR/runraid/homeserver-cert.pem" +install -m 0600 "$stage/approved/etc/mike-ai/keys/unraid_root" \ + "$SECRETS_DIR/keys/unraid_root" +install -m 0600 "$stage/approved/etc/mike-ai/ssh/known_hosts_unraid_ai" \ + "$SECRETS_DIR/ssh/known_hosts_unraid_ai" +install -m 0755 "$stage/approved/usr/local/bin/runraid" /usr/local/bin/runraid + +log "Tool-Container mit der neuen Stackdefinition aktivieren" +"$STACK_DIR/platform/mcp/install-tools.sh" + +log "Router und OpenWebUI mit den wiederhergestellten Schlüsseln neu erstellen" +cd "$STACK_DIR" +docker compose --env-file "$SECRETS_DIR/stack.env" up -d --force-recreate \ + router open-webui + +deadline=$((SECONDS + 180)) +for container in mike-ai-router mike-ai-open-webui; do + until [[ $(docker inspect -f '{{if .State.Health}}{{.State.Health.Status}}{{else}}{{.State.Status}}{{end}}' \ + "$container" 2>/dev/null || true) == healthy ]]; do + (( SECONDS < deadline )) || die "$container wurde nicht rechtzeitig gesund." + sleep 2 + done +done + +log "Wiederherstellung abgeschlossen" +printf 'Rückfallsicherung des vorherigen UI-Stands: %s\n' "$fallback"