Files
swarm-master/backup-openclaw-vm.sh
William Valentin aceeb7b542 Initial commit — OpenClaw VM infrastructure
- ansible/: VM provisioning playbooks and roles
  - provision-vm.yml: create KVM VM from Ubuntu cloud image
  - install.yml: install OpenClaw on guest (upstream)
  - customize.yml: swappiness, virtiofs fstab, linger
  - roles/vm/: libvirt domain XML, cloud-init templates
  - inventory.yml + host_vars/zap.yml: zap instance config
- backup-openclaw-vm.sh: daily rsync + MinIO upload
- restore-openclaw-vm.sh: full redeploy from scratch
- README.md: full operational documentation

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 12:18:31 -07:00

85 lines
2.7 KiB
Bash
Executable File

#!/bin/bash
# Sync OpenClaw VM config to ~/lab/swarm and back up to MinIO
#
# Local: rsync ~/.openclaw/ → ~/lab/swarm/openclaw/ (live mirror, no archive)
# MinIO: timestamped archive → s3://zap/backups/ (point-in-time recovery)
set -euo pipefail
INSTANCE="${1:-zap}"
REGISTRY="${HOME}/.claude/state/openclaw-instances.json"
SYNC_DIR="${HOME}/lab/swarm/openclaw"
MINIO_BUCKET="zap"
MINIO_PREFIX="backups"
KEEP=7
# Resolve instance from registry
GUEST_HOST=$(python3 -c "
import json, sys
data = json.load(open('${REGISTRY}'))
inst = next((i for i in data['instances'] if i['name'] == '${INSTANCE}'), None)
if not inst:
print('ERROR: unknown instance', file=sys.stderr); sys.exit(1)
if not inst.get('host'):
print('ERROR: instance has no host', file=sys.stderr); sys.exit(1)
print(inst['host'])
")
GUEST_USER=$(python3 -c "
import json
data = json.load(open('${REGISTRY}'))
inst = next(i for i in data['instances'] if i['name'] == '${INSTANCE}')
print(inst['user'])
")
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Syncing ${INSTANCE} (${GUEST_HOST})..."
# Check VM is reachable
if ! ssh -o ConnectTimeout=5 -o BatchMode=yes "root@${GUEST_HOST}" "echo ok" &>/dev/null; then
echo "[$(date '+%Y-%m-%d %H:%M:%S')] ERROR: Cannot reach ${GUEST_HOST}" >&2
exit 1
fi
mkdir -p "${SYNC_DIR}"
# Rsync ~/.openclaw/ → ~/lab/swarm/openclaw/
# Excludes large/ephemeral data not needed for redeployment
rsync -az --delete \
--exclude='workspace/' \
--exclude='extensions-quarantine/' \
--exclude='logs/' \
--exclude='*.bak' \
--exclude='*.bak.*' \
--exclude='*.bak-*' \
--exclude='*.backup-*' \
--exclude='*.pre-*' \
--exclude='*.failed' \
-e "ssh -o BatchMode=yes" \
"root@${GUEST_HOST}:/home/${GUEST_USER}/.openclaw/" \
"${SYNC_DIR}/"
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Synced to ${SYNC_DIR}"
# Create archive from synced files and upload to MinIO
ARCHIVE=$(mktemp --suffix=".tar.gz")
trap 'rm -f "${ARCHIVE}"' EXIT
tar czf "${ARCHIVE}" -C "${HOME}/lab/swarm" openclaw
MINIO_KEY="${MINIO_PREFIX}/${INSTANCE}-${TIMESTAMP}.tar.gz"
aws s3 cp "${ARCHIVE}" "s3://${MINIO_BUCKET}/${MINIO_KEY}" --quiet
SIZE=$(du -sh "${ARCHIVE}" | cut -f1)
echo "[$(date '+%Y-%m-%d %H:%M:%S')] MinIO: s3://${MINIO_BUCKET}/${MINIO_KEY} (${SIZE})"
# Prune MinIO backups, keep last N
mapfile -t OLD_KEYS < <(aws s3 ls "s3://${MINIO_BUCKET}/${MINIO_PREFIX}/${INSTANCE}-" \
| awk '{print $4}' | sort | head -n -${KEEP})
for key in "${OLD_KEYS[@]:-}"; do
[[ -z "${key}" ]] && continue
aws s3 rm "s3://${MINIO_BUCKET}/${MINIO_PREFIX}/${key}" --quiet
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Pruned MinIO: ${key}"
done
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Done."