126 lines
3.3 KiB
Bash
Executable File
126 lines
3.3 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
# Backs up key OpenClaw state to a versioned ZIP archive.
|
|
# Includes:
|
|
# - ~/.openclaw/openclaw.json
|
|
# - ~/.openclaw/cron/
|
|
# - ~/.openclaw/credentials/
|
|
# - ~/.openclaw/delivery-queue/ (if present)
|
|
#
|
|
# Usage:
|
|
# scripts/backup-openclaw-state.sh [output_dir]
|
|
# Example:
|
|
# scripts/backup-openclaw-state.sh /home/node/.openclaw/backups
|
|
|
|
OPENCLAW_HOME="${OPENCLAW_HOME:-$HOME/.openclaw}"
|
|
OUTPUT_DIR="${1:-$OPENCLAW_HOME/backups}"
|
|
TS="$(date -u +"%Y%m%d-%H%M%SZ")"
|
|
|
|
mkdir -p "$OUTPUT_DIR"
|
|
|
|
get_version() {
|
|
local raw ver
|
|
raw="$(openclaw --version 2>/dev/null || true)"
|
|
if [[ -n "$raw" ]]; then
|
|
ver="$(printf '%s' "$raw" | head -n1 | sed -E 's/[^0-9A-Za-z._-]+/-/g; s/^-+|-+$//g')"
|
|
if [[ -n "$ver" ]]; then
|
|
printf '%s' "$ver"
|
|
return 0
|
|
fi
|
|
fi
|
|
|
|
raw="$(openclaw status 2>/dev/null | grep -E 'Channel|Update' | head -n1 || true)"
|
|
ver="$(printf '%s' "$raw" | sed -E 's/[^0-9A-Za-z._-]+/-/g; s/^-+|-+$//g')"
|
|
if [[ -n "$ver" ]]; then
|
|
printf '%s' "$ver"
|
|
else
|
|
printf 'unknown'
|
|
fi
|
|
}
|
|
|
|
VERSION="$(get_version)"
|
|
ARCHIVE_NAME="openclaw-backup-${TS}-v${VERSION}.zip"
|
|
ARCHIVE_PATH="$OUTPUT_DIR/$ARCHIVE_NAME"
|
|
|
|
# Build include list (required + optional)
|
|
INCLUDE_PATHS=()
|
|
|
|
if [[ -f "$OPENCLAW_HOME/openclaw.json" ]]; then
|
|
INCLUDE_PATHS+=("$OPENCLAW_HOME/openclaw.json")
|
|
else
|
|
echo "WARN: Missing $OPENCLAW_HOME/openclaw.json" >&2
|
|
fi
|
|
|
|
if [[ -d "$OPENCLAW_HOME/cron" ]]; then
|
|
INCLUDE_PATHS+=("$OPENCLAW_HOME/cron")
|
|
else
|
|
echo "WARN: Missing $OPENCLAW_HOME/cron" >&2
|
|
fi
|
|
|
|
if [[ -d "$OPENCLAW_HOME/credentials" ]]; then
|
|
INCLUDE_PATHS+=("$OPENCLAW_HOME/credentials")
|
|
else
|
|
echo "WARN: Missing $OPENCLAW_HOME/credentials" >&2
|
|
fi
|
|
|
|
if [[ -d "$OPENCLAW_HOME/delivery-queue" ]]; then
|
|
INCLUDE_PATHS+=("$OPENCLAW_HOME/delivery-queue")
|
|
fi
|
|
|
|
if [[ ${#INCLUDE_PATHS[@]} -eq 0 ]]; then
|
|
echo "ERROR: Nothing to back up." >&2
|
|
exit 1
|
|
fi
|
|
|
|
# Create a temp metadata file and include it in the archive.
|
|
TMP_DIR="$(mktemp -d)"
|
|
trap 'rm -rf "$TMP_DIR"' EXIT
|
|
META_FILE="$TMP_DIR/backup-metadata.txt"
|
|
|
|
{
|
|
echo "timestamp_utc=$TS"
|
|
echo "openclaw_version=$VERSION"
|
|
echo "openclaw_home=$OPENCLAW_HOME"
|
|
echo "archive_name=$ARCHIVE_NAME"
|
|
echo "included_paths="
|
|
for p in "${INCLUDE_PATHS[@]}"; do
|
|
echo " - $p"
|
|
done
|
|
} > "$META_FILE"
|
|
|
|
# Create zip via Python for consistent behavior.
|
|
python3 - "$ARCHIVE_PATH" "$META_FILE" "${INCLUDE_PATHS[@]}" <<'PY'
|
|
import os
|
|
import sys
|
|
import zipfile
|
|
from pathlib import Path
|
|
|
|
archive = Path(sys.argv[1])
|
|
meta_file = Path(sys.argv[2])
|
|
items = [Path(p) for p in sys.argv[3:]]
|
|
|
|
with zipfile.ZipFile(archive, "w", compression=zipfile.ZIP_DEFLATED) as zf:
|
|
# Put metadata at archive root
|
|
zf.write(meta_file, arcname="backup-metadata.txt")
|
|
|
|
for item in items:
|
|
if not item.exists():
|
|
continue
|
|
|
|
if item.is_file():
|
|
zf.write(item, arcname=str(item).lstrip("/"))
|
|
else:
|
|
for root, dirs, files in os.walk(item):
|
|
root_path = Path(root)
|
|
# preserve empty dirs
|
|
if not files and not dirs:
|
|
zi = zipfile.ZipInfo(str(root_path).lstrip("/") + "/")
|
|
zf.writestr(zi, "")
|
|
for f in files:
|
|
fp = root_path / f
|
|
zf.write(fp, arcname=str(fp).lstrip("/"))
|
|
PY
|
|
|
|
echo "Backup created: $ARCHIVE_PATH"
|