Add restore script for backup recovery workflow.
Made-with: Cursor
This commit is contained in:
Executable
+165
@@ -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"
|
||||
Reference in New Issue
Block a user