#!/usr/bin/env bash set -Eeuo pipefail umask 077 BUNDLE=${1:-} IDENTITY=${2:-} ROOT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd) 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." [[ -s $BUNDLE ]] || die "Recovery-Bundle fehlt." [[ -s $IDENTITY ]] || die "Age-Identität fehlt." if ! command -v age >/dev/null || ! command -v rsync >/dev/null; then apt-get update DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends age rsync fi stage=$(mktemp -d /tmp/mike-ai-restore.XXXXXX) trap 'rm -rf "$stage"' EXIT age -d -i "$IDENTITY" -o "$stage/bundle.tar.gz" "$BUNDLE" tar -C "$stage" -xzf "$stage/bundle.tar.gz" ( cd "$stage" sha256sum -c SHA256SUMS ) tar -tzf "$stage/openwebui-data.tar.gz" ./webui.db >/dev/null 2>&1 || \ die "OpenWebUI-Archiv enthält keine Datenbank." recorded_commit=$(sed -n 's/^source_commit=//p' "$stage/METADATA" | head -n 1) current_commit=$(git -C "$ROOT_DIR" rev-parse HEAD 2>/dev/null || true) if [[ -n $recorded_commit && $recorded_commit != unknown && \ $current_commit != "$recorded_commit" ]]; then die "Repository-Commit stimmt nicht mit dem Backup überein: erwartet $recorded_commit" fi log "Root-only Konfiguration und freigegebene Secrets wiederherstellen" mkdir -p "$stage/rootfs" tar -C "$stage/rootfs" -xzf "$stage/host-config.tar.gz" [[ -s $stage/rootfs/root/mike-ai-install.env ]] || \ die "Installationskonfiguration fehlt im Bundle." install -d -m 0700 /etc/mike-ai rsync -a "$stage/rootfs/etc/mike-ai/" /etc/mike-ai/ install -m 0600 "$stage/rootfs/root/mike-ai-install.env" /root/mike-ai-install.env log "Reproduzierbaren Host-Installer ausführen" set +e "$ROOT_DIR/install.sh" --config /root/mike-ai-install.env status=$? set -e if [[ $status == 20 || $status == 21 ]]; then printf 'REBOOT_REQUIRED code=%s\n' "$status" printf 'Nach dem Neustart denselben Restore-Befehl erneut ausführen.\n' exit "$status" fi [[ $status == 0 ]] || die "Host-Installer ist mit Status $status fehlgeschlagen." log "Gesicherten Platform-Kontext und lokale Dokumentationspflege wiederherstellen" if [[ -d $stage/rootfs/opt/mike-ai/stack/docs ]]; then rsync -a "$stage/rootfs/opt/mike-ai/stack/docs/" /opt/mike-ai/stack/docs/ fi if [[ -d $stage/rootfs/data/mike-ai-platform-context ]]; then install -d -o 10001 -g 10001 -m 0750 /data/mike-ai-platform-context rsync -a "$stage/rootfs/data/mike-ai-platform-context/" /data/mike-ai-platform-context/ chown -R 10001:10001 /data/mike-ai-platform-context fi log "OpenWebUI-Zustand atomar wiederherstellen" volume_path=$(docker volume inspect -f '{{.Mountpoint}}' "$OPENWEBUI_VOLUME") [[ -d $volume_path && $volume_path == /* && $volume_path != / && \ $volume_path != /data && $volume_path != /var && \ $volume_path != /var/lib && $volume_path != /var/lib/docker ]] || \ die "Unsicherer Docker-Volume-Pfad: $volume_path" fallback=/data/openwebui-before-disaster-restore-$(date +%Y%m%d-%H%M%S).tar.gz docker stop mike-ai-open-webui >/dev/null 2>&1 || true tar -C "$volume_path" -czf "$fallback" . find "$volume_path" -mindepth 1 -maxdepth 1 -exec rm -rf -- {} + tar -C "$volume_path" -xzf "$stage/openwebui-data.tar.gz" docker start mike-ai-open-webui >/dev/null deadline=$((SECONDS + 240)) until [[ $(docker inspect -f '{{.State.Health.Status}}' mike-ai-open-webui 2>/dev/null || true) == healthy ]]; do (( SECONDS < deadline )) || die "OpenWebUI wurde nicht rechtzeitig gesund." sleep 3 done log "Versionierte Modelle, Filter und Tool-Verbindungen nachziehen" "$ROOT_DIR/platform/openwebui/install-models.sh" "$ROOT_DIR/platform/openwebui/install-filters.sh" "$ROOT_DIR/platform/mcp/install-tools.sh" if [[ -s /etc/mike-ai/navidrome-mcp.env ]]; then "$ROOT_DIR/platform/mcp/verify-navidrome.sh" fi printf 'BARE_METAL_RECOVERY_OK\n' printf 'Rückfallsicherung des leeren OpenWebUI-Stands: %s\n' "$fallback"