#!/usr/bin/env bash # Build the BossAI production binary, package Alpine/musl runtime libraries, # and deploy the runnable bundle to a Debian server. set -euo pipefail ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" cd "$ROOT_DIR" log() { echo "[$(date '+%H:%M:%S')] $*"; } ok() { echo "[$(date '+%H:%M:%S')] ✓ $*"; } fail() { echo "[$(date '+%H:%M:%S')] ✗ $*" >&2; exit 1; } REMOTE_HOST="${BOSSAI_DEBIAN_HOST:-129.211.30.104}" REMOTE_USER="${BOSSAI_DEBIAN_USER:-root}" REMOTE_DIR="${BOSSAI_DEBIAN_DIR:-/root/BossAI}" SSH_KEY="${BOSSAI_DEBIAN_SSH_KEY:-$HOME/.ssh/id_rsa_mbp}" BOSSAI_SOURCE_DIR="${BOSSAI_SOURCE_DIR:-/root/BossAI-build/main}" BOSSAI_REMOTE="${BOSSAI_REMOTE:-bossai}" BOSSAI_REF="${BOSSAI_REF:-main}" BOSSAI_WORKFLOW_UI_SOURCE="${BOSSAI_WORKFLOW_UI_SOURCE:-$ROOT_DIR/vendor/ComfyUI_frontend}" PRODUCTION_DIR="${BOSSAI_PRODUCTION_DIR:-$BOSSAI_SOURCE_DIR/dist/bossai-production}" BUNDLE_DIR="${BOSSAI_DEBIAN_BUNDLE_DIR:-$BOSSAI_SOURCE_DIR/dist/bossai-debian-bundle}" BUNDLE_ARCHIVE="${BOSSAI_DEBIAN_BUNDLE_ARCHIVE:-$BOSSAI_SOURCE_DIR/dist/bossai-debian-bundle.tar.gz}" FRONTEND_PM="${BOOSAPI_FRONTEND_PM:-auto}" SKIP_BUILD=0 SKIP_VERIFY=0 SKIP_SOURCE_UPDATE=0 usage() { cat <<'EOF' Usage: scripts/deploy-bossai-debian.sh [options] Build BossAI from the BossAI source tree, bundle the musl runtime libraries needed by the Alpine-built binary, upload it to a Debian host, extract it, and run a config check. Options: --host Debian host/IP. Default: 129.211.30.104 --user SSH user. Default: root --dir Remote deploy directory. Default: /root/BossAI --key SSH private key. Default: ~/.ssh/id_rsa_mbp --source-dir BossAI source worktree. Default: /root/BossAI-build/main --remote Git remote for BossAI. Default: bossai --ref BossAI branch/ref to build. Default: main --skip-build Reuse dist/bossai-production from a previous build --skip-source-update Do not fetch/reset the BossAI source worktree --skip-verify Skip remote ./run-boosapi.sh --check-config -h, --help Show this help Environment overrides: BOSSAI_DEBIAN_HOST BOSSAI_DEBIAN_USER BOSSAI_DEBIAN_DIR BOSSAI_DEBIAN_SSH_KEY BOSSAI_SOURCE_DIR BOSSAI_REMOTE BOSSAI_REF BOSSAI_WORKFLOW_UI_SOURCE BOSSAI_PRODUCTION_DIR BOSSAI_DEBIAN_BUNDLE_DIR BOSSAI_DEBIAN_BUNDLE_ARCHIVE BOOSAPI_FRONTEND_PM auto / bun / npm, forwarded to build-and-run.sh Remote start command after deploy: cd /root/BossAI && ./run-boosapi.sh EOF } while [ $# -gt 0 ]; do case "$1" in --host) shift [ $# -gt 0 ] || fail "--host requires a value" REMOTE_HOST="$1" ;; --user) shift [ $# -gt 0 ] || fail "--user requires a value" REMOTE_USER="$1" ;; --dir) shift [ $# -gt 0 ] || fail "--dir requires a value" REMOTE_DIR="$1" ;; --key) shift [ $# -gt 0 ] || fail "--key requires a value" SSH_KEY="$1" ;; --source-dir) shift [ $# -gt 0 ] || fail "--source-dir requires a value" BOSSAI_SOURCE_DIR="$1" PRODUCTION_DIR="${BOSSAI_PRODUCTION_DIR:-$BOSSAI_SOURCE_DIR/dist/bossai-production}" BUNDLE_DIR="${BOSSAI_DEBIAN_BUNDLE_DIR:-$BOSSAI_SOURCE_DIR/dist/bossai-debian-bundle}" BUNDLE_ARCHIVE="${BOSSAI_DEBIAN_BUNDLE_ARCHIVE:-$BOSSAI_SOURCE_DIR/dist/bossai-debian-bundle.tar.gz}" ;; --remote) shift [ $# -gt 0 ] || fail "--remote requires a value" BOSSAI_REMOTE="$1" ;; --ref) shift [ $# -gt 0 ] || fail "--ref requires a value" BOSSAI_REF="$1" ;; --skip-build) SKIP_BUILD=1 ;; --skip-source-update) SKIP_SOURCE_UPDATE=1 ;; --skip-verify) SKIP_VERIFY=1 ;; -h|--help) usage exit 0 ;; *) echo "Unknown argument: $1" >&2 usage >&2 exit 1 ;; esac shift done require_file() { local path="$1" [ -f "$path" ] || fail "Missing required file: $path" } require_command() { command -v "$1" >/dev/null 2>&1 || fail "Missing required command: $1" } ensure_bossai_source() { require_command git if [ "$SKIP_SOURCE_UPDATE" -eq 1 ]; then log "Skipping BossAI source update; using $BOSSAI_SOURCE_DIR" [ -d "$BOSSAI_SOURCE_DIR" ] || fail "BossAI source directory does not exist: $BOSSAI_SOURCE_DIR" return 0 fi log "Preparing BossAI source from $BOSSAI_REMOTE/$BOSSAI_REF → $BOSSAI_SOURCE_DIR" git fetch "$BOSSAI_REMOTE" "$BOSSAI_REF" if [ -d "$BOSSAI_SOURCE_DIR" ] && git -C "$BOSSAI_SOURCE_DIR" rev-parse --is-inside-work-tree >/dev/null 2>&1; then git -C "$BOSSAI_SOURCE_DIR" reset --hard "$BOSSAI_REMOTE/$BOSSAI_REF" git -C "$BOSSAI_SOURCE_DIR" clean -fd else rm -rf "$BOSSAI_SOURCE_DIR" mkdir -p "$(dirname "$BOSSAI_SOURCE_DIR")" git worktree add "$BOSSAI_SOURCE_DIR" "$BOSSAI_REMOTE/$BOSSAI_REF" fi require_file "$BOSSAI_SOURCE_DIR/build-and-run.sh" ok "BossAI source ready: $(git -C "$BOSSAI_SOURCE_DIR" rev-parse --short HEAD)" } prepare_workflow_ui_source() { if [ -n "${BOOSAPI_WORKFLOW_UI_REPO:-}" ]; then log "Using BOOSAPI_WORKFLOW_UI_REPO=$BOOSAPI_WORKFLOW_UI_REPO" return 0 fi if [ ! -d "$BOSSAI_WORKFLOW_UI_SOURCE/.git" ] && [ ! -f "$BOSSAI_WORKFLOW_UI_SOURCE/.git" ]; then log "No local Workflow UI source at $BOSSAI_WORKFLOW_UI_SOURCE; install script may fetch from GitHub" return 0 fi log "Using local ComfyUI_frontend source: $BOSSAI_WORKFLOW_UI_SOURCE" export BOOSAPI_WORKFLOW_UI_REPO="$BOSSAI_WORKFLOW_UI_SOURCE" } runtime_lib() { local path="$1" require_file "$path" cp "$path" "$BUNDLE_DIR/lib/" } write_runner() { cat > "$BUNDLE_DIR/run-boosapi.sh" <<'EOF' #!/usr/bin/env sh set -eu DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) exec "$DIR/lib/ld-musl-x86_64.so.1" --library-path "$DIR/lib" "$DIR/boosapi" "$@" EOF chmod +x "$BUNDLE_DIR/run-boosapi.sh" } package_bundle() { require_file "$PRODUCTION_DIR/boosapi" require_file "$PRODUCTION_DIR/config.toml" rm -rf "$BUNDLE_DIR" "$BUNDLE_ARCHIVE" mkdir -p "$BUNDLE_DIR/lib" cp "$PRODUCTION_DIR/boosapi" "$BUNDLE_DIR/boosapi" cp "$PRODUCTION_DIR/config.toml" "$BUNDLE_DIR/config.toml" chmod +x "$BUNDLE_DIR/boosapi" runtime_lib /lib/ld-musl-x86_64.so.1 runtime_lib /usr/lib/libssl.so.3 runtime_lib /usr/lib/libcrypto.so.3 runtime_lib /usr/lib/libgcc_s.so.1 write_runner tar -C "$BUNDLE_DIR" -czf "$BUNDLE_ARCHIVE" . ok "Bundle ready: $BUNDLE_ARCHIVE" sha256sum "$BUNDLE_ARCHIVE" "$BUNDLE_DIR/boosapi" ls -lh "$BUNDLE_ARCHIVE" } deploy_bundle() { require_file "$SSH_KEY" require_command ssh require_command scp local remote="${REMOTE_USER}@${REMOTE_HOST}" local ssh_opts=(-i "$SSH_KEY" -o StrictHostKeyChecking=accept-new) log "Preparing remote directory: $remote:$REMOTE_DIR" ssh "${ssh_opts[@]}" "$remote" "mkdir -p '$REMOTE_DIR'" log "Uploading bundle..." scp "${ssh_opts[@]}" "$BUNDLE_ARCHIVE" "$remote:$REMOTE_DIR/bossai-debian-bundle.tar.gz" log "Extracting bundle on Debian host..." ssh "${ssh_opts[@]}" "$remote" "cd '$REMOTE_DIR' && tar -xzf bossai-debian-bundle.tar.gz && chmod +x boosapi run-boosapi.sh && sha256sum bossai-debian-bundle.tar.gz boosapi" if [ "$SKIP_VERIFY" -eq 0 ]; then log "Checking remote config..." ssh "${ssh_opts[@]}" "$remote" "cd '$REMOTE_DIR' && ./run-boosapi.sh --check-config" fi ok "Deployed BossAI to $remote:$REMOTE_DIR" log "Start with: cd $REMOTE_DIR && ./run-boosapi.sh" } if [ "$SKIP_BUILD" -eq 0 ]; then ensure_bossai_source prepare_workflow_ui_source log "Building BossAI production bundle..." BOOSAPI_FRONTEND_PM="$FRONTEND_PM" "$BOSSAI_SOURCE_DIR/build-and-run.sh" \ --production \ --production-dir "$PRODUCTION_DIR" else [ -d "$BOSSAI_SOURCE_DIR" ] || fail "BossAI source directory does not exist: $BOSSAI_SOURCE_DIR" log "Skipping build; using existing $PRODUCTION_DIR" fi package_bundle deploy_bundle