#!/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"