Add restore script for backup recovery workflow.
Made-with: Cursor
This commit is contained in:
@@ -133,3 +133,11 @@ cd ~/openclaude-LLM-local/docs/rag-mcp-starter
|
|||||||
chmod +x backup.sh
|
chmod +x backup.sh
|
||||||
./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
|
||||||
|
```
|
||||||
|
|||||||
@@ -103,6 +103,27 @@ chmod +x backup.sh
|
|||||||
./backup.sh --collection mydocs
|
./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`)
|
- `QDRANT_URL` (default `http://127.0.0.1:6333`)
|
||||||
|
|||||||
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