Add restore script for backup recovery workflow.

Made-with: Cursor
This commit is contained in:
Andrey Lutsenko
2026-04-20 23:05:23 +10:00
parent e736dd0f37
commit f429b48b8f
3 changed files with 194 additions and 0 deletions
+165
View File
@@ -0,0 +1,165 @@
#!/usr/bin/env bash
set -euo pipefail
# Semi-automatic restore helper for backups created by backup.sh
#
# Restores:
# - Claude/OpenClaude config files (with overwrite confirmation)
# - Prints model restore commands from saved inventory
# - Optionally replays Qdrant points snapshot (best-effort)
#
# Usage:
# ./restore.sh --from /path/to/backup_YYYYMMDD_HHMMSS
# ./restore.sh --from /path/to/backup --apply-qdrant
FROM_DIR=""
APPLY_QDRANT=0
QDRANT_URL="${QDRANT_URL:-http://127.0.0.1:6333}"
COLLECTION="${QDRANT_COLLECTION:-docs}"
while [[ $# -gt 0 ]]; do
case "$1" in
--from)
FROM_DIR="$2"
shift 2
;;
--apply-qdrant)
APPLY_QDRANT=1
shift
;;
--collection)
COLLECTION="$2"
shift 2
;;
*)
echo "Unknown argument: $1"
exit 2
;;
esac
done
if [[ -z "$FROM_DIR" ]]; then
echo "Usage: $0 --from /path/to/backup_dir [--apply-qdrant] [--collection docs]"
exit 2
fi
if [[ ! -d "$FROM_DIR" ]]; then
echo "Backup directory not found: $FROM_DIR"
exit 1
fi
echo "==> Restore source: $FROM_DIR"
confirm_overwrite() {
local target="$1"
if [[ -e "$target" ]]; then
read -r -p "Overwrite existing $target ? [y/N]: " ans
[[ "$ans" =~ ^[Yy]$ ]] || return 1
fi
return 0
}
restore_file_if_exists() {
local src="$1"
local dst="$2"
if [[ -f "$src" ]]; then
mkdir -p "$(dirname "$dst")"
if confirm_overwrite "$dst"; then
cp "$src" "$dst"
echo " restored: $dst"
else
echo " skipped: $dst"
fi
fi
}
restore_dir_if_exists() {
local src="$1"
local dst="$2"
if [[ -d "$src" ]]; then
mkdir -p "$dst"
cp -R "$src"/. "$dst"/
echo " restored directory: $dst"
fi
}
echo "==> Restoring configs"
restore_file_if_exists "$FROM_DIR/config/claude/settings.json" "$HOME/.claude/settings.json"
restore_file_if_exists "$FROM_DIR/config/claude/claude.json" "$HOME/.claude.json"
restore_dir_if_exists "$FROM_DIR/config/openclaude/profiles" "$HOME/.config/openclaude/profiles"
echo "==> Model restore hints"
if [[ -f "$FROM_DIR/ollama_models.txt" ]]; then
echo "Saved models:"
cat "$FROM_DIR/ollama_models.txt"
echo
echo "Replay pulls (manual):"
awk 'NR>1 && $1 != "" {print "ollama pull " $1}' "$FROM_DIR/ollama_models.txt"
else
echo "No ollama_models.txt found"
fi
if [[ "$APPLY_QDRANT" -eq 1 ]]; then
echo "==> Attempting Qdrant replay"
SNAP="$FROM_DIR/qdrant/${COLLECTION}_points.json"
META="$FROM_DIR/qdrant/${COLLECTION}_meta.json"
if ! command -v python3 >/dev/null 2>&1; then
echo "python3 required for Qdrant replay"
exit 1
fi
if ! command -v curl >/dev/null 2>&1; then
echo "curl required for Qdrant replay"
exit 1
fi
if [[ ! -f "$SNAP" ]]; then
echo "Snapshot file not found: $SNAP"
exit 1
fi
if ! curl -fsS "${QDRANT_URL}/collections" >/dev/null 2>&1; then
echo "Qdrant not reachable at ${QDRANT_URL}"
exit 1
fi
if ! curl -fsS "${QDRANT_URL}/collections/${COLLECTION}" >/dev/null 2>&1; then
echo "Collection ${COLLECTION} not found. Create it first or reindex from source docs."
if [[ -f "$META" ]]; then
echo "Collection metadata snapshot exists at: $META"
fi
exit 1
fi
python3 - "$SNAP" "$QDRANT_URL" "$COLLECTION" <<'PY'
import json
import sys
import urllib.request
snap, qdrant_url, collection = sys.argv[1], sys.argv[2], sys.argv[3]
with open(snap, "r", encoding="utf-8") as f:
data = json.load(f)
points = data.get("result", {}).get("points", [])
if not points:
print("No points to replay.")
sys.exit(0)
payload = json.dumps({"points": points}).encode("utf-8")
req = urllib.request.Request(
f"{qdrant_url}/collections/{collection}/points",
data=payload,
headers={"Content-Type": "application/json"},
method="PUT",
)
with urllib.request.urlopen(req, timeout=120) as r:
print(f"Qdrant replay response: {r.status}")
PY
echo "Qdrant replay completed (best-effort)."
else
echo "==> Qdrant restore skipped (use --apply-qdrant to replay points snapshot)"
fi
echo
echo "Restore flow completed."
echo "Recommended: run ./healthcheck.sh --mode full"