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:
131
restore-openclaw-vm.sh
Executable file
131
restore-openclaw-vm.sh
Executable file
@@ -0,0 +1,131 @@
|
||||
#!/bin/bash
|
||||
# Restore OpenClaw VM from backup
|
||||
# Usage: restore-openclaw-vm.sh [instance] [target-host]
|
||||
# instance - registry name (default: zap)
|
||||
# target-host - override host IP for fresh VM deployments
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
INSTANCE="${1:-zap}"
|
||||
TARGET_HOST="${2:-}"
|
||||
REGISTRY="${HOME}/.claude/state/openclaw-instances.json"
|
||||
ANSIBLE_DIR="${HOME}/lab/swarm/ansible"
|
||||
|
||||
# Resolve instance from registry
|
||||
GUEST_USER=$(python3 -c "
|
||||
import json
|
||||
data = json.load(open('${REGISTRY}'))
|
||||
inst = next((i for i in data['instances'] if i['name'] == '${INSTANCE}'), None)
|
||||
if not inst: print('openclaw')
|
||||
else: print(inst['user'])
|
||||
")
|
||||
|
||||
if [[ -z "${TARGET_HOST}" ]]; then
|
||||
TARGET_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 or not inst.get('host'):
|
||||
print('ERROR: no host in registry — pass target-host as second argument', file=sys.stderr)
|
||||
sys.exit(1)
|
||||
print(inst['host'])
|
||||
")
|
||||
fi
|
||||
|
||||
SYNC_DIR="${HOME}/lab/swarm/openclaw"
|
||||
MINIO_BUCKET="zap"
|
||||
MINIO_PREFIX="backups"
|
||||
|
||||
# Find config source — local sync dir first, fall back to MinIO archive
|
||||
USE_MINIO=false
|
||||
if [[ ! -d "${SYNC_DIR}" || -z "$(ls -A "${SYNC_DIR}" 2>/dev/null)" ]]; then
|
||||
echo "No local sync found in ${SYNC_DIR}, fetching latest archive from MinIO..."
|
||||
LATEST_KEY=$(aws s3 ls "s3://${MINIO_BUCKET}/${MINIO_PREFIX}/${INSTANCE}-" \
|
||||
| awk '{print $4}' | sort | tail -1)
|
||||
if [[ -z "${LATEST_KEY}" ]]; then
|
||||
echo "ERROR: No config found locally or in s3://${MINIO_BUCKET}/${MINIO_PREFIX}/" >&2
|
||||
exit 1
|
||||
fi
|
||||
MINIO_ARCHIVE=$(mktemp --suffix=".tar.gz")
|
||||
trap 'rm -f "${MINIO_ARCHIVE}"' EXIT
|
||||
aws s3 cp "s3://${MINIO_BUCKET}/${MINIO_PREFIX}/${LATEST_KEY}" "${MINIO_ARCHIVE}"
|
||||
echo "Downloaded: ${LATEST_KEY}"
|
||||
USE_MINIO=true
|
||||
fi
|
||||
|
||||
SOURCE_DESC="${SYNC_DIR}"
|
||||
[[ "${USE_MINIO}" == true ]] && SOURCE_DESC="MinIO: ${LATEST_KEY}"
|
||||
|
||||
echo "========================================"
|
||||
echo " OpenClaw VM Restore"
|
||||
echo "========================================"
|
||||
echo " Instance : ${INSTANCE}"
|
||||
echo " Target : ${TARGET_HOST}"
|
||||
echo " Source : ${SOURCE_DESC}"
|
||||
echo "========================================"
|
||||
echo ""
|
||||
read -rp "Proceed? [y/N] " confirm
|
||||
[[ "${confirm}" =~ ^[Yy]$ ]] || { echo "Aborted."; exit 0; }
|
||||
|
||||
cd "${ANSIBLE_DIR}"
|
||||
|
||||
# Step 1: Provision VM if a target host override was given (implies fresh VM needed)
|
||||
if [[ -n "${2:-}" ]]; then
|
||||
echo ""
|
||||
echo "==> [1/4] Provisioning VM (libvirt)..."
|
||||
ansible-playbook -i inventory.yml playbooks/provision-vm.yml --limit "${INSTANCE}" \
|
||||
-e "vm_ip=${TARGET_HOST}" -e "ansible_host=${TARGET_HOST}"
|
||||
else
|
||||
echo ""
|
||||
echo "==> [1/4] Skipping VM provisioning (using existing VM at ${TARGET_HOST})"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "==> [2/4] Provisioning guest OS..."
|
||||
ansible-playbook -i inventory.yml playbooks/install.yml --limit "${INSTANCE}" \
|
||||
-e "ansible_host=${TARGET_HOST}"
|
||||
|
||||
echo ""
|
||||
echo "==> [3/4] Applying customizations..."
|
||||
ansible-playbook -i inventory.yml playbooks/customize.yml --limit "${INSTANCE}" \
|
||||
-e "ansible_host=${TARGET_HOST}"
|
||||
|
||||
# Step 3: Restore config
|
||||
echo ""
|
||||
echo "==> [4/5] Restoring OpenClaw config..."
|
||||
|
||||
# Stop service before restoring
|
||||
ssh -o StrictHostKeyChecking=no "root@${TARGET_HOST}" \
|
||||
"su - ${GUEST_USER} -c 'systemctl --user stop openclaw-gateway.service 2>/dev/null || true'"
|
||||
|
||||
# Push config and restore
|
||||
if [[ "${USE_MINIO}" == true ]]; then
|
||||
cat "${MINIO_ARCHIVE}" | ssh -o StrictHostKeyChecking=no "root@${TARGET_HOST}" \
|
||||
"cd /home/${GUEST_USER} && tar xzf - && chown -R ${GUEST_USER}:${GUEST_USER} openclaw"
|
||||
else
|
||||
rsync -az \
|
||||
-e "ssh -o StrictHostKeyChecking=no -o BatchMode=yes" \
|
||||
"${SYNC_DIR}/" \
|
||||
"root@${TARGET_HOST}:/home/${GUEST_USER}/.openclaw/"
|
||||
ssh -o StrictHostKeyChecking=no "root@${TARGET_HOST}" \
|
||||
"chown -R ${GUEST_USER}:${GUEST_USER} /home/${GUEST_USER}/.openclaw"
|
||||
fi
|
||||
|
||||
echo " Config restored from: ${SOURCE_DESC}"
|
||||
|
||||
# Step 4: Start service
|
||||
echo ""
|
||||
echo "==> [5/5] Starting service..."
|
||||
ssh -o StrictHostKeyChecking=no "root@${TARGET_HOST}" \
|
||||
"su - ${GUEST_USER} -c 'systemctl --user daemon-reload && systemctl --user start openclaw-gateway.service'"
|
||||
|
||||
# Verify
|
||||
sleep 3
|
||||
STATUS=$(ssh -o StrictHostKeyChecking=no "root@${TARGET_HOST}" \
|
||||
"su - ${GUEST_USER} -c 'systemctl --user is-active openclaw-gateway.service 2>/dev/null'" || echo "unknown")
|
||||
|
||||
echo ""
|
||||
echo "========================================"
|
||||
echo " Restore complete"
|
||||
echo " Service status: ${STATUS}"
|
||||
echo "========================================"
|
||||
Reference in New Issue
Block a user