#!/bin/bash set -euo pipefail # Deploy MacSync client to plum (MacBook) via rsync + SSH. # # Usage: # ./deploy-remote.sh # use default SERVER_URL # ./deploy-remote.sh http://10.0.0.11:3201 # override server URL # ./deploy-remote.sh uninstall # uninstall from plum # ./deploy-remote.sh frontend # re-sync webapp only (preserves TCC) PLUM_HOST="${PLUM_HOST:-plum}" SERVER_URL="${MAC_SYNC_SERVER_URL:-http://10.0.0.11:3201}" STAGING_DIR="mac-sync-staging" SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" MAC_SYNC_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' print_step() { echo -e "${GREEN}▸${NC} $1"; } print_info() { echo -e "${BLUE}ℹ${NC} $1"; } print_warning() { echo -e "${YELLOW}⚠${NC} $1"; } print_error() { echo -e "${RED}✗${NC} $1"; } print_success() { echo -e "${GREEN}✓${NC} $1"; } KEEP_STAGING=false for arg in "$@"; do case "$arg" in uninstall|frontend|--uninstall|--frontend|--skip-app) ;; --keep-staging) KEEP_STAGING=true ;; http://*|https://*) SERVER_URL="$arg" ;; --*) ;; *) SERVER_URL="$arg" ;; esac done echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" echo -e "${BLUE} MacSync — Remote Deploy to $PLUM_HOST${NC}" echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" echo "" check_plum() { print_step "Checking SSH to $PLUM_HOST..." if ! ssh -o ConnectTimeout=5 "$PLUM_HOST" 'echo ok' >/dev/null 2>&1; then print_error "Cannot reach $PLUM_HOST — check SSH config" exit 1 fi print_success "Connected" } cmd_install() { check_plum local remote_root="$STAGING_DIR/@applications/@mac-sync" # Sync mac-sync source print_step "Syncing @mac-sync source to $PLUM_HOST:~/$remote_root/..." ssh "$PLUM_HOST" "mkdir -p ~/$remote_root" # Exclude generated + VCS + dev-tool noise. plum regenerates Swift/node deps # as needed (Swift via `swift build` in install.sh; no node deps needed on plum). rsync -az --delete \ --exclude '.build/' \ --exclude 'DerivedData/' \ --exclude '.swiftpm/' \ --exclude 'node_modules/' \ --exclude '.git/' \ --exclude '.github/' \ --exclude '.vscode/' \ --exclude '.idea/' \ --exclude '.DS_Store' \ --exclude '*.log' \ --exclude 'dist/' \ --exclude 'coverage/' \ --exclude '.cache/' \ --exclude '.next/' \ --exclude '.turbo/' \ --exclude '.parcel-cache/' \ --exclude '__pycache__/' \ --exclude '*.pyc' \ --exclude '.venv/' \ "$MAC_SYNC_ROOT/" \ "$PLUM_HOST:~/$remote_root/" # Sync local Swift package dependencies (referenced via relative paths in Package.swift) local swift_root swift_root="$(cd "$MAC_SYNC_ROOT/../../@packages/@swift" && pwd 2>/dev/null)" || true local tray_root tray_root="$(cd "$MAC_SYNC_ROOT/../../@packages/@tray" && pwd 2>/dev/null)" || true if [[ -d "${swift_root:-}" ]]; then print_step "Syncing Swift packages..." for pkg in "@macos/agent-core" "@macos/menu-bar" "@macos/sync-framework" "@foundations/logging" "@foundations/settings"; do local local_pkg="$swift_root/$pkg" [[ -d "$local_pkg" ]] || continue local remote_pkg="$STAGING_DIR/@packages/@swift/$pkg" ssh "$PLUM_HOST" "mkdir -p ~/$remote_pkg" rsync -az --delete \ --exclude '.build/' --exclude '.swiftpm/' --exclude 'DerivedData/' \ --exclude 'node_modules/' --exclude '.git/' --exclude '.DS_Store' \ "$local_pkg/" "$PLUM_HOST:~/$remote_pkg/" done print_success "Swift packages synced" fi if [[ -d "${tray_root:-}" ]]; then for pkg in "tray-resources"; do local local_pkg="$tray_root/$pkg" [[ -d "$local_pkg" ]] || continue local remote_pkg="$STAGING_DIR/@packages/@tray/$pkg" ssh "$PLUM_HOST" "mkdir -p ~/$remote_pkg" rsync -az --delete \ --exclude '.build/' --exclude '.swiftpm/' --exclude 'DerivedData/' \ --exclude 'node_modules/' --exclude '.git/' --exclude '.DS_Store' \ "$local_pkg/" "$PLUM_HOST:~/$remote_pkg/" done fi # Build webapp locally (plum may not have bun/pnpm) local web_src="$MAC_SYNC_ROOT/web" if [[ -d "$web_src" ]]; then print_step "Building webapp locally..." (cd "$web_src" && bun install --frozen-lockfile && bun run build) 2>&1 if [[ -d "$web_src/dist" ]]; then print_step "Syncing web/dist to $PLUM_HOST..." rsync -az --delete \ "$web_src/dist/" \ "$PLUM_HOST:~/$remote_root/web/dist/" print_success "Webapp built and synced" else print_error "web/dist not produced" exit 1 fi fi # Sync VERSION.json local version_file="$MAC_SYNC_ROOT/VERSION.json" [[ -f "$version_file" ]] && rsync -az "$version_file" "$PLUM_HOST:~/$remote_root/VERSION.json" # Run install.sh on plum print_step "Building and installing on $PLUM_HOST..." ssh -t "$PLUM_HOST" "cd ~/$remote_root && chmod +x deploy/install.sh && ./deploy/install.sh '$SERVER_URL'" # Verify print_step "Verifying agent status..." ssh "$PLUM_HOST" bash <<'CHECK' sleep 2 if pgrep -x MacSyncApp >/dev/null; then echo "Agent is running" tail -5 ~/Library/Application\ Support/MacSync/stderr.log 2>/dev/null || true else echo "Agent not running — grant Full Disk Access in System Settings" fi CHECK # Clean staging (unless --keep-staging) if [[ "$KEEP_STAGING" == "true" ]]; then print_info "Staging kept at ~/$STAGING_DIR (per --keep-staging)" else print_step "Cleaning staging..." ssh "$PLUM_HOST" "rm -rf ~/$STAGING_DIR" print_success "Staging cleaned" fi echo "" print_success "Deploy complete" print_info "Logs: ssh $PLUM_HOST 'tail -f ~/Library/Application\\ Support/MacSync/stderr.log'" } cmd_uninstall() { check_plum print_step "Uninstalling MacSync from $PLUM_HOST..." ssh -t "$PLUM_HOST" bash <<'UNINSTALL' set -e PLIST="$HOME/Library/LaunchAgents/com.lilith.mac-sync.plist" APP="$HOME/Applications/MacSync.app" if [[ -f "$PLIST" ]]; then launchctl unload "$PLIST" 2>/dev/null || true rm "$PLIST" echo "LaunchAgent removed" fi pkill -x MacSyncApp 2>/dev/null || true [[ -d "$APP" ]] && rm -rf "$APP" && echo "App bundle removed" echo "Uninstall complete" UNINSTALL } cmd_frontend() { check_plum local web_src="$MAC_SYNC_ROOT/web" local remote_webapp="Applications/MacSync.app/Contents/Resources/webapp" print_step "Building webapp locally..." (cd "$web_src" && bun install --frozen-lockfile && bun run build) [[ -d "$web_src/dist" ]] || { print_error "web/dist not produced"; exit 1; } print_step "Syncing to $PLUM_HOST:~/$remote_webapp/..." rsync -az --delete "$web_src/dist/" "$PLUM_HOST:~/$remote_webapp/" print_success "Frontend synced" print_step "Re-signing app bundle (webapp update invalidates sealed resources)..." ssh "$PLUM_HOST" "codesign --remove-signature ~/Applications/MacSync.app 2>/dev/null; codesign -s - --force --deep ~/Applications/MacSync.app" \ && print_success "App re-signed (ad-hoc)" \ || print_warning "Re-signing failed — app may not launch" print_step "Restarting agent..." ssh "$PLUM_HOST" "pkill -x MacSyncApp 2>/dev/null || true" sleep 2 ssh "$PLUM_HOST" "/usr/bin/open ~/Applications/MacSync.app 2>/dev/null; sleep 3 && pgrep -x MacSyncApp >/dev/null" \ && print_success "Agent restarted" \ || print_warning "Agent did not restart" echo "" print_success "Frontend deploy complete (TCC preserved)" } case "${1:-}" in uninstall) cmd_uninstall ;; frontend) cmd_frontend ;; *) cmd_install ;; esac