#!/usr/bin/env bash # Wiederherstellung eines mc2-state-Backups (siehe backup.sh). # Stoppt die Dienste, spielt den Zustand zurück, startet neu — und macht VORHER # automatisch ein Sicherheits-Backup des aktuellen Zustands (Restore ist reversibel). # # Nutzung: # bash restore.sh --list # vorhandene Backups zeigen (lokal + Off-Box) # bash restore.sh --dry-run # nur anzeigen, was passieren würde # bash restore.sh [--yes] # wiederherstellen (--yes = ohne Rückfrage) # bash restore.sh --pull-offsite # Off-Box-Kopie (Proxmox) nach lokal holen # # Off-Box (C12): fehlt ein Backup lokal (z. B. Box-Platte neu), wird es automatisch # vom Zweitgerät (Proxmox) geholt. Ziel/Key wie in backup.sh (überschreibbar per Env). set -euo pipefail MODELS_DIR="${MC_MODELS_DIR:-/srv/models}" DEST_DIR="$MODELS_DIR/mc2-backups" MEM0_DIR="${MC_MEM0_DIR:-/srv/models/mem0}" LSWAP="${MC_CONFIG_PATH:-/etc/llama-swap/config.yaml}" HERMES="${HERMES_HOME:-$HOME/.hermes}" SRC="${MC2_SRC:-$HOME/mission-control-v2}" SERVICES="mem0-service mission-control-2 hermes-gateway" OFFSITE="${MC_BACKUP_OFFSITE:-root@192.168.178.108:/var/lib/vz/mc2-backups}" OFFSITE_KEY="${MC_BACKUP_OFFSITE_KEY:-$HOME/.ssh/mc2_offsite}" OFFSITE_SSH="ssh -i $OFFSITE_KEY -o BatchMode=yes -o ConnectTimeout=10 -o StrictHostKeyChecking=accept-new" # Off-Box-Tarballs (Dateinamen) auflisten — leer, wenn nicht erreichbar/konfiguriert. offsite_ls() { [ -n "$OFFSITE" ] && [ -f "$OFFSITE_KEY" ] || return 0 local host="${OFFSITE%%:*}" path="${OFFSITE#*:}" $OFFSITE_SSH "$host" "ls -1 $path/mc2-state-*.tar.gz 2>/dev/null" 2>/dev/null | xargs -r -n1 basename || true } # Ein einzelnes Off-Box-Tarball nach lokal holen. $1 = Dateiname (basename). offsite_fetch() { [ -n "$OFFSITE" ] && [ -f "$OFFSITE_KEY" ] || return 1 mkdir -p "$DEST_DIR" echo "→ hole $1 vom Zweitgerät ($OFFSITE)…" >&2 rsync -a -e "$OFFSITE_SSH" "$OFFSITE/$1" "$DEST_DIR/" } usage() { echo "Nutzung: restore.sh --list | restore.sh --pull-offsite | restore.sh [--dry-run] [--yes] "; } list_backups() { echo "Verfügbare Backups in $DEST_DIR:" local found=0 f for f in $(ls -1t "$DEST_DIR"/mc2-state-*.tar.gz 2>/dev/null || true); do printf " %s (%s)\n" "$(basename "$f")" "$(du -h "$f" | cut -f1)" found=1 done [ "$found" = 1 ] || echo " (keine)" local off; off="$(offsite_ls || true)" if [ -n "$off" ]; then echo "Off-Box ($OFFSITE):" echo "$off" | sed 's/^/ /' fi } [ $# -eq 0 ] && { usage; exit 1; } DRY=0; YES=0; TARGET="" for a in "$@"; do case "$a" in --list) list_backups; exit 0 ;; --pull-offsite) mkdir -p "$DEST_DIR" [ -n "$OFFSITE" ] && [ -f "$OFFSITE_KEY" ] || { echo "Off-Box nicht konfiguriert."; exit 1; } echo "→ spiegle Off-Box → lokal ($DEST_DIR)…" rsync -a -e "$OFFSITE_SSH" --include='mc2-state-*.tar.gz' --exclude='*' "$OFFSITE/" "$DEST_DIR/" list_backups; exit 0 ;; --dry-run) DRY=1 ;; --yes) YES=1 ;; -*) usage; exit 1 ;; *) TARGET="$a" ;; esac done [ -z "$TARGET" ] && { usage; exit 1; } # 'latest' bevorzugt lokal; fehlt lokal alles, das jüngste Off-Box-Tarball holen. if [ "$TARGET" = "latest" ]; then TARGET="$(ls -1t "$DEST_DIR"/mc2-state-*.tar.gz 2>/dev/null | head -1 || true)" if [ -z "$TARGET" ]; then latest_off="$(offsite_ls | sort | tail -1 || true)" [ -n "$latest_off" ] && offsite_fetch "$latest_off" && TARGET="$DEST_DIR/$latest_off" fi fi # Konkreten Namen zuerst lokal suchen, sonst vom Zweitgerät holen. if [ ! -f "$TARGET" ]; then base="$(basename "${TARGET:-}")" if [ -f "$DEST_DIR/$base" ]; then TARGET="$DEST_DIR/$base" elif [ -n "$base" ] && offsite_ls | grep -qx "$base"; then offsite_fetch "$base" && TARGET="$DEST_DIR/$base" fi fi [ -f "$TARGET" ] || { echo "Backup nicht gefunden: ${TARGET:-}"; echo; list_backups; exit 1; } echo "Restore-Quelle: $TARGET" STAGE="$(mktemp -d)"; trap 'rm -rf "$STAGE"' EXIT tar -xzf "$TARGET" -C "$STAGE" echo "--- Inhalt ---"; cat "$STAGE/MANIFEST.txt" 2>/dev/null || true; echo "--------------" if [ "$DRY" = 1 ]; then echo "[dry-run] würde zurücksetzen:" [ -d "$STAGE/mem0" ] && echo " • mem0 → $MEM0_DIR (wird ersetzt)" [ -f "$STAGE/hermes/config.yaml" ] && echo " • $HERMES/config.yaml" [ -f "$STAGE/hermes/.env" ] && echo " • $HERMES/.env" [ -d "$STAGE/hermes/plugins" ] && echo " • $HERMES/plugins/" [ -f "$STAGE/llama-swap/config.yaml" ] && echo " • $LSWAP" echo " • Dienste neu starten: $SERVICES" exit 0 fi if [ "$YES" != 1 ]; then echo "WARNUNG: Das überschreibt den AKTUELLEN Zustand und startet die Dienste neu." read -r -p "Fortfahren? (tippe 'ja'): " ans [ "$ans" = "ja" ] || { echo "Abgebrochen."; exit 1; } fi echo "→ Sicherheits-Backup des aktuellen Zustands…" bash "$SRC/deploy/backup.sh" || echo " (Pre-Restore-Backup fehlgeschlagen — fahre fort)" echo "→ Dienste stoppen…" systemctl --user stop $SERVICES 2>/dev/null || true if [ -d "$STAGE/mem0" ]; then rm -rf "$MEM0_DIR"; mkdir -p "$MEM0_DIR"; cp -a "$STAGE/mem0/." "$MEM0_DIR/"; fi [ -f "$STAGE/hermes/config.yaml" ] && { mkdir -p "$HERMES"; cp -a "$STAGE/hermes/config.yaml" "$HERMES/"; } [ -f "$STAGE/hermes/.env" ] && cp -a "$STAGE/hermes/.env" "$HERMES/" [ -d "$STAGE/hermes/plugins" ] && { mkdir -p "$HERMES/plugins"; cp -a "$STAGE/hermes/plugins/." "$HERMES/plugins/"; } [ -f "$STAGE/llama-swap/config.yaml" ] && cp -a "$STAGE/llama-swap/config.yaml" "$LSWAP" 2>/dev/null || true # Hermes-Desktop-/Gate-Infrastruktur (Gegenstück zu backup.sh, 09.07.2026): [ -d "$STAGE/hermes/agent-hooks" ] && { mkdir -p "$HERMES/agent-hooks"; cp -a "$STAGE/hermes/agent-hooks/." "$HERMES/agent-hooks/"; chmod +x "$HERMES/agent-hooks/"*.sh 2>/dev/null || true; } [ -f "$STAGE/hermes/shell-hooks-allowlist.json" ] && cp -a "$STAGE/hermes/shell-hooks-allowlist.json" "$HERMES/" [ -f "$STAGE/hermes/desktop-gateway-token" ] && { cp -a "$STAGE/hermes/desktop-gateway-token" "$HERMES/"; chmod 600 "$HERMES/desktop-gateway-token"; } [ -f "$STAGE/hermes/pc-paths.yaml" ] && cp -a "$STAGE/hermes/pc-paths.yaml" "$HERMES/" [ -f "$STAGE/hermes/systemd-dropins/session-token.conf" ] && { mkdir -p "$HOME/.config/systemd/user/hermes-builtin-ui.service.d"; cp -a "$STAGE/hermes/systemd-dropins/session-token.conf" "$HOME/.config/systemd/user/hermes-builtin-ui.service.d/"; chmod 600 "$HOME/.config/systemd/user/hermes-builtin-ui.service.d/session-token.conf"; systemctl --user daemon-reload 2>/dev/null || true; } echo "→ Dienste starten…" systemctl --user start $SERVICES 2>/dev/null || true sleep 4 printf "→ Health: "; curl -sf http://127.0.0.1:9001/api/health && echo || echo "(Backend nicht erreichbar — Logs prüfen)" echo "OK — Restore abgeschlossen."