#!/usr/bin/env bash # # Cross-compiles the Oz CLI for Linux x86_64 (musl) on macOS and uploads it # to a remote host via rsync for local remote-server development. # # Uses rsync for delta transfers — after the first deploy, only changed bytes # are sent, which is dramatically faster for iterative development. # # Prerequisites: # brew install filosottile/musl-cross/musl-cross # rustup target add x86_64-unknown-linux-musl # # Usage: # script/deploy_remote_server --host user@hostname [--profile release] set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" WORKSPACE_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" # Defaults PROFILE_MODE="dev-remote" HOST="" usage() { cat < [OPTIONS] Cross-compile the Oz CLI for Linux x86_64 and upload it to a remote host. Required: --host Remote host to upload to Options: --profile Build profile: dev-remote (default, strips symbols), dev, release, or optimized Use --profile dev if you need symbols for remote debugging --help Show this help message EOF exit 0 } # Parse arguments while [[ $# -gt 0 ]]; do case "$1" in --host) HOST="$2" shift 2 ;; --profile) PROFILE_MODE="$2" shift 2 ;; --help) usage ;; *) echo "Error: Unknown argument: $1" >&2 echo "Run with --help for usage." >&2 exit 1 ;; esac done # Validate required arguments if [[ -z "$HOST" ]]; then echo "Error: --host is required." >&2 echo "Run with --help for usage." >&2 exit 1 fi # Validate profile case "$PROFILE_MODE" in dev-remote|dev|release|optimized) ;; *) echo "Error: Unsupported profile '$PROFILE_MODE'. Use 'dev-remote', 'dev', 'release', or 'optimized'." >&2 exit 1 ;; esac # Check for musl-cross linker if ! command -v x86_64-linux-musl-gcc &>/dev/null; then echo "Error: x86_64-linux-musl-gcc not found." >&2 echo "Install it with: brew install filosottile/musl-cross/musl-cross" >&2 exit 1 fi # Check for musl target if ! rustup target list --installed 2>/dev/null | grep -q x86_64-unknown-linux-musl; then echo "Error: x86_64-unknown-linux-musl target not installed." >&2 echo "Install it with: rustup target add x86_64-unknown-linux-musl" >&2 exit 1 fi # Determine build parameters TARGET="x86_64-unknown-linux-musl" case "$PROFILE_MODE" in dev-remote) CARGO_PROFILE="dev-remote" ;; dev) CARGO_PROFILE="dev" ;; release) CARGO_PROFILE="release" ;; optimized) CARGO_PROFILE="release-lto-debug_assertions" ;; esac FEATURES="release_bundle,crash_reporting,standalone,agent_mode_debug" WARP_BIN="warp" BINARY_NAME="oz-local" REMOTE_DIR=".warp-local/remote-server" # Determine the output directory CARGO_TARGET_DIR="${CARGO_TARGET_DIR:-$WORKSPACE_ROOT/target}" case "$CARGO_PROFILE" in dev) OUTPUT_DIR="$CARGO_TARGET_DIR/$TARGET/debug" ;; *) OUTPUT_DIR="$CARGO_TARGET_DIR/$TARGET/$CARGO_PROFILE" ;; esac BUILT_BINARY="$OUTPUT_DIR/$WARP_BIN" echo "==> Building Oz CLI for $TARGET (profile=$CARGO_PROFILE)" echo " Binary: $WARP_BIN -> $BINARY_NAME" echo " Features: $FEATURES" echo "" # Build with linker and rustflags overrides to avoid macOS-specific flags # from .cargo/config.toml CARGO_TARGET_X86_64_UNKNOWN_LINUX_MUSL_LINKER=x86_64-linux-musl-gcc \ CARGO_TARGET_X86_64_UNKNOWN_LINUX_MUSL_RUSTFLAGS="-C symbol-mangling-version=v0" \ cargo build \ -p warp \ --bin "$WARP_BIN" \ --target "$TARGET" \ --profile "$CARGO_PROFILE" \ --features "$FEATURES" if [[ ! -f "$BUILT_BINARY" ]]; then echo "Error: Expected binary not found at $BUILT_BINARY" >&2 exit 1 fi BINARY_SIZE=$(du -h "$BUILT_BINARY" | cut -f1) echo "" echo "==> Build complete ($BINARY_SIZE)" # Resolve $HOME on the remote so our upload lands in the same directory the # Warp client checks. The client runs `test -x ~/.warp-local/...` and lets # the remote login shell expand `~` to $HOME, while rsync's remote path # expansion can differ (e.g. on Namespace devboxes, the initial directory # is /workspaces but $HOME is elsewhere). Using an absolute path derived # from the remote $HOME keeps both sides consistent. REMOTE_HOME=$(ssh "$HOST" 'printf %s "$HOME"') if [[ -z "$REMOTE_HOME" ]]; then echo "Error: could not resolve remote \$HOME on $HOST" >&2 exit 1 fi REMOTE_ABS_DIR="$REMOTE_HOME/$REMOTE_DIR" # Ensure rsync is available on the remote host if ! ssh "$HOST" "command -v rsync" &>/dev/null; then echo "==> rsync not found on remote host, installing..." ssh "$HOST" "sudo apt-get install -y rsync || sudo yum install -y rsync || sudo dnf install -y rsync || sudo apk add rsync" if ! ssh "$HOST" "command -v rsync" &>/dev/null; then echo "Error: failed to install rsync on $HOST. Please install it manually and re-run." >&2 exit 1 fi fi # Ensure the remote directory exists ssh "$HOST" "mkdir -p $REMOTE_ABS_DIR" echo "==> Uploading to $HOST:$REMOTE_ABS_DIR/$BINARY_NAME" # Upload via rsync with delta transfer and compression. # After the first deploy, only changed bytes are transferred. rsync -z -t --partial --progress \ "$BUILT_BINARY" \ "$HOST:$REMOTE_ABS_DIR/$BINARY_NAME" # Set executable permissions (done separately for openrsync compatibility on macOS) ssh "$HOST" "chmod 755 $REMOTE_ABS_DIR/$BINARY_NAME" echo "" echo "==> Done! Binary deployed to $HOST:$REMOTE_ABS_DIR/$BINARY_NAME" echo " (resolved from ~/$REMOTE_DIR/$BINARY_NAME on remote)"