#!/usr/bin/env bash # One-way sync: local workstation project tree → Dev VM (5700) /srv/projects # for coordinated development over SSH (Cursor Remote-SSH, shared tree). # # Prerequisites: # - CT 5700 exists, ssh dev1@IP_DEV_VM works, /srv/projects is writable (see setup-dev-vm-users-and-gitea.sh). # - Run from a machine that has your local clone (default: ~/projects). # # Usage: # ./scripts/deployment/sync-local-projects-to-dev-vm.sh --dry-run # ./scripts/deployment/sync-local-projects-to-dev-vm.sh # ./scripts/deployment/sync-local-projects-to-dev-vm.sh --delete-remote # mirror: remove remote files absent locally # RSYNC_RSH='ssh -o ProxyCommand="cloudflared access ssh --hostname ssh.dev.d-bis.org"' \ # DEV_VM_HOST=ssh.dev.d-bis.org ./scripts/deployment/sync-local-projects-to-dev-vm.sh # # Env: # SOURCE — local directory (default: ~/projects) # DEV_VM_USER — SSH user on dev VM (default: dev1) # DEV_VM_HOST — override IP/hostname (default: IP_DEV_VM from config) # DEV_VM_PROJECTS — remote path (default: /srv/projects) # RSYNC_EXTRA_OPTS — extra rsync args (quoted string) # RSYNC_RSH — passed to rsync as SSH transport (e.g. cloudflared ProxyCommand) # # If --delete-remote fails with rsync code 23 (Permission denied on delete), run: # ./scripts/deployment/fix-dev-vm-srv-projects-ownership.sh # See: docs/04-configuration/DEV_VM_WORKSTATION_MIGRATION_RUNBOOK.md set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" # shellcheck source=/dev/null source "${PROJECT_ROOT}/scripts/lib/load-project-env.sh" 2>/dev/null || true # shellcheck source=/dev/null source "${PROJECT_ROOT}/config/ip-addresses.conf" 2>/dev/null || true SOURCE="${SOURCE:-${HOME}/projects}" DEV_VM_USER="${DEV_VM_USER:-dev1}" DEV_VM_HOST="${DEV_VM_HOST:-${IP_DEV_VM:-192.168.11.59}}" DEV_VM_PROJECTS="${DEV_VM_PROJECTS:-/srv/projects}" RSYNC_EXTRA_OPTS="${RSYNC_EXTRA_OPTS:-}" DRY_RUN=() DELETE_REMOTE=0 while [[ $# -gt 0 ]]; do case "$1" in --dry-run) DRY_RUN=(-n); shift ;; --delete-remote) DELETE_REMOTE=1; shift ;; --help|-h) sed -n '1,35p' "$0" | tail -n +2 exit 0 ;; *) echo "ERROR: unknown argument: $1 (try --help)" >&2 exit 1 ;; esac done if [[ ! -d "$SOURCE" ]]; then echo "ERROR: SOURCE is not a directory: $SOURCE" >&2 exit 1 fi REMOTE="${DEV_VM_USER}@${DEV_VM_HOST}:${DEV_VM_PROJECTS}/" RSYNC_DELETE=() if [[ "$DELETE_REMOTE" == "1" ]]; then RSYNC_DELETE=(--delete-delay) echo "WARNING: --delete-remote — files on Dev VM under DEST not present locally (after excludes) will be REMOVED." echo "" else echo "Safe mode: no remote delete (omit files only on VM). Use --delete-remote to mirror (destructive)." echo "" fi echo "=== Sync local projects → Dev VM ===" echo "SOURCE: $SOURCE" echo "DEST: $REMOTE" echo "Mode: ${DRY_RUN[*]:-live}" if [[ -n "${RSYNC_RSH:-}" ]]; then echo "RSYNC_RSH: set (custom SSH / cloudflared)" fi echo "" echo "NOTE: Review secrets after sync — chmod 600 remote .env; share only with trusted dev users." echo "" # Heavy / reproducible artifacts (keep .git; omit bulky caches) RSYNC_EXCLUDES=( --exclude=node_modules --exclude=__pycache__ --exclude=.pnpm-store --exclude=.pnpm --exclude=venv --exclude=.venv --exclude=.venv-* --exclude=dist --exclude=build --exclude=.next --exclude=out --exclude=.turbo --exclude=.cache --exclude=.parcel-cache --exclude=coverage --exclude=.pytest_cache --exclude=.mypy_cache --exclude=.ruff_cache --exclude=forge-cache --exclude=artifacts --exclude=broadcast --exclude=tmp --exclude=.tmp --exclude=.codex-artifacts ) # Optional: skip known multi-GB trees (re-clone or sync later with --no-skip-large) if [[ "${SKIP_LARGE_LOCAL_TREES:-1}" == "1" ]]; then RSYNC_EXCLUDES+=( --exclude=MEV_Bot --exclude=the-order ) echo "SKIP_LARGE_LOCAL_TREES=1: excluding MEV_Bot, the-order (set SKIP_LARGE_LOCAL_TREES=0 to include)." echo "" fi if [[ -n "${RSYNC_RSH:-}" ]]; then export RSYNC_RSH else unset RSYNC_RSH 2>/dev/null || true fi set -x # shellcheck disable=SC2086 set +e rsync -av "${DRY_RUN[@]}" "${RSYNC_DELETE[@]}" --omit-dir-times \ "${RSYNC_EXCLUDES[@]}" \ ${RSYNC_EXTRA_OPTS} \ "${SOURCE}/" "${REMOTE}" rsync_ec=$? set -e set +x if [[ "$rsync_ec" -ne 0 ]]; then echo "" >&2 echo "ERROR: rsync exited with code $rsync_ec." >&2 if [[ "$rsync_ec" -eq 23 && "$DELETE_REMOTE" == "1" ]]; then echo " Common cause: root-owned files on the VM block deletes. From repo root (Proxmox root SSH to the node that runs CT 5700):" >&2 echo " ./scripts/deployment/fix-dev-vm-srv-projects-ownership.sh" >&2 fi exit "$rsync_ec" fi echo "" echo "Done. Next: SSH to dev VM, open /srv/projects/proxmox in Cursor (Remote-SSH), run pnpm/npm install where needed."