#!/bin/bash set -euo pipefail # Deploy mac-sync-server to black (10.0.0.11). # # Usage: # ./deploy-server.sh # ./deploy-server.sh --skip-build BLACK_HOST="${BLACK_HOST:-10.0.0.11}" REMOTE_DIR="/opt/mac-sync-server" ENV_DIR="/etc/mac-sync-server" SERVICE_NAME="mac-sync-server" SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" SERVER_SRC="$SCRIPT_DIR/../src/server" SKIP_BUILD=false 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"; } for arg in "$@"; do case "$arg" in --skip-build) SKIP_BUILD=true ;; esac done echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" echo -e "${BLUE} Mac Sync Server — Deploy to black ($BLACK_HOST)${NC}" echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" echo "" check_black() { print_step "Checking SSH to $BLACK_HOST..." if ! ssh -o ConnectTimeout=5 "$BLACK_HOST" 'echo ok' >/dev/null 2>&1; then print_error "Cannot reach $BLACK_HOST — check network/SSH config" exit 1 fi print_success "Connected" } sync_source() { print_step "Syncing src/server/ to $BLACK_HOST:$REMOTE_DIR..." ssh "$BLACK_HOST" "sudo mkdir -p $REMOTE_DIR && sudo chown lilith:lilith $REMOTE_DIR" rsync -az --delete \ --exclude 'node_modules/' \ --exclude '.bun/' \ --exclude 'data/' \ "$SERVER_SRC/" \ "$BLACK_HOST:$REMOTE_DIR/" print_success "Source synced" } install_deps() { print_step "Installing dependencies on black..." ssh "$BLACK_HOST" "cd $REMOTE_DIR && bun install --frozen-lockfile" print_success "Dependencies installed" } provision_env() { # Create /etc/mac-sync-server/env if it doesn't exist. # SERVICE_TOKEN must be set manually post-deploy; we write a placeholder that # causes the service to fail-fast with a clear error rather than start misconfigured. if ssh "$BLACK_HOST" "test -f $ENV_DIR/env" 2>/dev/null; then print_info "env file exists at $ENV_DIR/env — skipping (preserving existing secrets)" return fi print_step "Creating env file at $BLACK_HOST:$ENV_DIR/env..." ssh "$BLACK_HOST" "sudo mkdir -p $ENV_DIR && sudo tee $ENV_DIR/env > /dev/null" <<'EOF' PORT=3201 NODE_ENV=production QUINN_MACSYNC_DB_URL=REPLACE_WITH_DB_URL SERVICE_TOKEN=REPLACE_WITH_SECRET SSO_VALIDATE_URL=http://localhost:3025/auth/validate # Object storage. local = ./data/blobs (dev). s3 = any S3-compatible store. STORAGE_BACKEND=s3 STORAGE_LOCAL_PATH=/opt/mac-sync-server/data/blobs # DO Spaces (nyc3). Path-style is required for this bucket's writes. S3_ENDPOINT=https://nyc3.digitaloceanspaces.com S3_ACCESS_KEY=REPLACE_FROM_VAULT_do-spaces-uvlava.access S3_SECRET_KEY=REPLACE_FROM_VAULT_do-spaces-uvlava.secret S3_BUCKET=lilith-quinn-media S3_REGION=us-east-1 S3_FORCE_PATH_STYLE=true S3_PRESIGN_TTL_SECONDS=900 # Embedding inference endpoint — required, no default (fail fast on misconfig). MODEL_BOSS_EMBED_URL=REPLACE_WITH_INFERENCE_URL EOF ssh "$BLACK_HOST" "sudo chmod 640 $ENV_DIR/env && sudo chown root:lilith $ENV_DIR/env" print_warning "env file written — set SERVICE_TOKEN before starting: sudo nano $ENV_DIR/env" } install_systemd() { print_step "Installing systemd unit..." rsync -az "$SCRIPT_DIR/systemd/mac-sync-server.service" \ "$BLACK_HOST:/tmp/mac-sync-server.service" ssh "$BLACK_HOST" \ "sudo cp /tmp/mac-sync-server.service /etc/systemd/system/mac-sync-server.service && \ sudo systemctl daemon-reload && \ sudo systemctl enable mac-sync-server" print_success "Systemd unit installed and enabled" } restart_service() { print_step "Restarting $SERVICE_NAME..." ssh "$BLACK_HOST" "sudo systemctl restart $SERVICE_NAME" sleep 3 local status status=$(ssh "$BLACK_HOST" "systemctl is-active $SERVICE_NAME" 2>/dev/null || echo "unknown") if [[ "$status" == "active" ]]; then print_success "Service active" else print_warning "Service status: $status" print_info "Check: ssh $BLACK_HOST 'journalctl -u $SERVICE_NAME -n 30'" fi } verify_health() { print_step "Checking health endpoint..." local port=3201 if ssh "$BLACK_HOST" "curl -sf http://localhost:$port/health > /dev/null 2>&1"; then print_success "Health check passed (port $port)" else print_warning "Health check failed — service may still be starting or SERVICE_TOKEN unset" fi } check_black sync_source install_deps provision_env install_systemd restart_service verify_health echo "" print_success "Server deploy complete" echo "" print_info "If SERVICE_TOKEN was just set, run: ssh $BLACK_HOST 'sudo systemctl restart $SERVICE_NAME'" print_info "View logs: ssh $BLACK_HOST 'journalctl -u $SERVICE_NAME -f'"