Generalize the photos-originals rclone-mount pattern to a video-projects prefix so the video studio (and imajin ETL, per storage-portability-plan §2.3) can read/write multi-GB project sources/renders as local files while only hot data stays resident on plum (bounded VFS LRU cache). Lets a small-disk laptop work with large footage without filling APFS. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
148 lines
5.2 KiB
Bash
Executable file
148 lines
5.2 KiB
Bash
Executable file
#!/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'"
|