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>
This commit is contained in:
84
backup-openclaw-vm.sh
Executable file
84
backup-openclaw-vm.sh
Executable file
@@ -0,0 +1,84 @@
|
||||
#!/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."
|
||||
Reference in New Issue
Block a user