diff --git a/docs/quickstart-30min.md b/docs/quickstart-30min.md index 452790e..5c08be3 100644 --- a/docs/quickstart-30min.md +++ b/docs/quickstart-30min.md @@ -133,3 +133,11 @@ cd ~/openclaude-LLM-local/docs/rag-mcp-starter chmod +x backup.sh ./backup.sh ``` + +## 14) Восстановление из бэкапа + +```bash +cd ~/openclaude-LLM-local/docs/rag-mcp-starter +chmod +x restore.sh +./restore.sh --from /path/to/backup_YYYYMMDD_HHMMSS +``` diff --git a/docs/rag-mcp-starter/README.md b/docs/rag-mcp-starter/README.md index 14b6219..c24c896 100644 --- a/docs/rag-mcp-starter/README.md +++ b/docs/rag-mcp-starter/README.md @@ -103,6 +103,27 @@ chmod +x backup.sh ./backup.sh --collection mydocs ``` +## Restore + +Полуавтоматическое восстановление из бэкапа: + +```bash +chmod +x restore.sh +./restore.sh --from /path/to/backup_YYYYMMDD_HHMMSS +``` + +Восстановление snapshot points в Qdrant (best-effort): + +```bash +./restore.sh --from /path/to/backup_YYYYMMDD_HHMMSS --apply-qdrant +``` + +После восстановления: + +```bash +./healthcheck.sh --mode full +``` + ## Переменные окружения - `QDRANT_URL` (default `http://127.0.0.1:6333`) diff --git a/docs/rag-mcp-starter/restore.sh b/docs/rag-mcp-starter/restore.sh new file mode 100755 index 0000000..4d6ebb2 --- /dev/null +++ b/docs/rag-mcp-starter/restore.sh @@ -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"