Add sync-to-vm.sh to copy local config directories to remote VM
This commit is contained in:
@@ -7,6 +7,7 @@ Scripts for setting up a fresh Linux VM to host opencode-devbox.
|
||||
- **`cloud-init.yml`** — cloud-init user-data template for automated VM provisioning on OpenStack, Proxmox, or any cloud with cloud-init support
|
||||
- **`setup-host.sh`** — interactive post-install script for VMs that weren't provisioned with cloud-init
|
||||
- **`setup-openstack-secgroup.sh`** — creates an OpenStack security group with the right rules (SSH, mosh, ICMP)
|
||||
- **`sync-to-vm.sh`** — syncs local config directories (`~/.aws`, `~/.config/opencode`, etc.) to a remote VM based on which bind mounts are active in its `docker-compose.yml`
|
||||
|
||||
## Supported distributions
|
||||
|
||||
@@ -186,3 +187,13 @@ docker compose exec -u developer devbox opencode
|
||||
```
|
||||
|
||||
> **AWS Bedrock users:** Uncomment the `~/.aws` volume mount in `docker-compose.yml` before starting. You'll also need to copy your `~/.aws/config` from a machine where SSO is already configured, then authenticate inside the container with `aws sso login`.
|
||||
|
||||
### Syncing local config to the VM
|
||||
|
||||
After editing `docker-compose.yml` on the VM to uncomment the bind mounts you need, run `sync-to-vm.sh` from your local machine to copy the corresponding directories:
|
||||
|
||||
```bash
|
||||
./deploy/sync-to-vm.sh devbox-affection
|
||||
```
|
||||
|
||||
The script reads `docker-compose.yml` on the remote VM, detects which bind mounts are active, and syncs only those directories from your local machine. It also creates the remote directories if they don't exist.
|
||||
|
||||
Executable
+103
@@ -0,0 +1,103 @@
|
||||
#!/bin/bash
|
||||
# sync-to-vm.sh — Copy local config to an opencode-devbox VM
|
||||
#
|
||||
# Reads docker-compose.yml on the remote VM to detect which bind mounts
|
||||
# are active, then syncs the corresponding directories from this machine.
|
||||
#
|
||||
# Usage:
|
||||
# ./sync-to-vm.sh <ssh-host>
|
||||
#
|
||||
# Examples:
|
||||
# ./sync-to-vm.sh devbox-affection
|
||||
# ./sync-to-vm.sh devbox@129.192.68.184
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# ── Colors ──────────────────────────────────────────────────────────
|
||||
BOLD="\033[1m"; GREEN="\033[32m"; YELLOW="\033[33m"; RED="\033[31m"; RESET="\033[0m"
|
||||
info() { echo -e "${BOLD}==>${RESET} $*"; }
|
||||
ok() { echo -e "${GREEN}${BOLD}✓${RESET} $*"; }
|
||||
warn() { echo -e "${YELLOW}${BOLD}!${RESET} $*"; }
|
||||
err() { echo -e "${RED}${BOLD}✗${RESET} $*" >&2; }
|
||||
|
||||
# ── Args ────────────────────────────────────────────────────────────
|
||||
if [[ $# -lt 1 ]]; then
|
||||
err "Usage: $0 <ssh-host>"
|
||||
echo " Example: $0 devbox-affection"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
SSH_HOST="$1"
|
||||
REMOTE_COMPOSE="~/opencode-devbox/docker-compose.yml"
|
||||
|
||||
# ── Bind mount patterns to detect ──────────────────────────────────
|
||||
# Maps: grep pattern → local source → remote destination
|
||||
declare -a MOUNT_PATTERNS=(
|
||||
"~/.aws:/home/developer/.aws|$HOME/.aws|~/.aws"
|
||||
"~/.config/opencode:/home/developer/.config/opencode|$HOME/.config/opencode|~/.config/opencode"
|
||||
"~/.config/nvim:/home/developer/.config/nvim|$HOME/.config/nvim|~/.config/nvim"
|
||||
"~/.agents/skills:/home/developer/.agents/skills|$HOME/.agents/skills|~/.agents/skills"
|
||||
)
|
||||
|
||||
# ── Verify SSH connectivity ─────────────────────────────────────────
|
||||
info "Checking SSH connectivity to ${SSH_HOST}..."
|
||||
if ! ssh -o ConnectTimeout=5 "$SSH_HOST" true 2>/dev/null; then
|
||||
err "Cannot connect to ${SSH_HOST}"
|
||||
exit 1
|
||||
fi
|
||||
ok "Connected to ${SSH_HOST}"
|
||||
|
||||
# ── Fetch remote docker-compose.yml ─────────────────────────────────
|
||||
info "Reading docker-compose.yml from ${SSH_HOST}..."
|
||||
REMOTE_COMPOSE_CONTENT=$(ssh "$SSH_HOST" "cat $REMOTE_COMPOSE 2>/dev/null") || {
|
||||
err "Could not read ${REMOTE_COMPOSE} on ${SSH_HOST}"
|
||||
err "Has the VM been set up? Run the post-setup steps first."
|
||||
exit 1
|
||||
}
|
||||
|
||||
# ── Detect active bind mounts ──────────────────────────────────────
|
||||
SYNCED=0
|
||||
|
||||
for entry in "${MOUNT_PATTERNS[@]}"; do
|
||||
IFS='|' read -r pattern local_path remote_path <<< "$entry"
|
||||
|
||||
# Check if the mount is uncommented (active) in docker-compose.yml
|
||||
# Match lines that start with optional whitespace and a dash, NOT preceded by #
|
||||
if echo "$REMOTE_COMPOSE_CONTENT" | grep -qE "^\s*-\s+['\"]?${pattern}" 2>/dev/null; then
|
||||
# Mount is active — check if local source exists
|
||||
if [[ ! -d "$local_path" ]]; then
|
||||
warn "Mount active for ${pattern} but ${local_path} does not exist locally — skipping"
|
||||
continue
|
||||
fi
|
||||
|
||||
# Check if directory has content
|
||||
if [[ -z "$(ls -A "$local_path" 2>/dev/null)" ]]; then
|
||||
warn "${local_path} is empty — skipping"
|
||||
continue
|
||||
fi
|
||||
|
||||
info "Syncing ${local_path} → ${SSH_HOST}:${remote_path}"
|
||||
|
||||
# Ensure remote directory exists
|
||||
ssh "$SSH_HOST" "mkdir -p ${remote_path}"
|
||||
|
||||
# Sync with rsync (fall back to scp if rsync unavailable)
|
||||
if command -v rsync &>/dev/null; then
|
||||
rsync -az --info=progress2 "${local_path}/" "${SSH_HOST}:${remote_path}/"
|
||||
else
|
||||
scp -r "${local_path}/." "${SSH_HOST}:${remote_path}/"
|
||||
fi
|
||||
|
||||
ok "Synced ${local_path}"
|
||||
SYNCED=$((SYNCED + 1))
|
||||
fi
|
||||
done
|
||||
|
||||
# ── Summary ─────────────────────────────────────────────────────────
|
||||
echo ""
|
||||
if [[ $SYNCED -eq 0 ]]; then
|
||||
warn "No active bind mounts detected in remote docker-compose.yml"
|
||||
warn "Uncomment the mounts you need in ${REMOTE_COMPOSE} on the VM, then re-run this script"
|
||||
else
|
||||
ok "Synced ${SYNCED} director$([ $SYNCED -eq 1 ] && echo 'y' || echo 'ies') to ${SSH_HOST}"
|
||||
fi
|
||||
Reference in New Issue
Block a user