#!/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 pipefailSCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"WORKSPACE_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"# DefaultsPROFILE_MODE="dev-remote"HOST=""usage() { cat <<EOFUsage: $(basename "$0") --host <user@hostname> [OPTIONS]Cross-compile the Oz CLI for Linux x86_64 and upload it to a remote host.Required: --host <user@hostname> Remote host to upload toOptions: --profile <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 messageEOF exit 0}# Parse argumentswhile [[ $# -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 ;; esacdone# Validate required argumentsif [[ -z "$HOST" ]]; then echo "Error: --host is required." >&2 echo "Run with --help for usage." >&2 exit 1fi# Validate profilecase "$PROFILE_MODE" in dev-remote|dev|release|optimized) ;; *) echo "Error: Unsupported profile '$PROFILE_MODE'. Use 'dev-remote', 'dev', 'release', or 'optimized'." >&2 exit 1 ;;esacTARGET="x86_64-unknown-linux-musl"source "$SCRIPT_DIR/linux/configure_musl_toolchain" "$TARGET"# Check for musl targetif ! 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 1fi# Select the requested Cargo build profile.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" ;;esacFEATURES="release_bundle,crash_reporting,standalone,agent_mode_debug,remote_codebase_indexing"WARP_BIN="warp"BINARY_NAME="oz-local"REMOTE_DIR=".warp-local/remote-server"# Global, version-independent resources location read by the daemon. Must# match BUNDLED_RESOURCES_DIR_NAME in crates/remote_server/src/setup.rs.BUNDLED_RESOURCES_DIR_NAME="bundled_resources"# Determine the output directoryCARGO_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" ;;esacBUILT_BINARY="$OUTPUT_DIR/$WARP_BIN"LOCAL_RESOURCES_DIR="$OUTPUT_DIR/remote-server-resources"echo "==> Building Oz CLI for $TARGET (profile=$CARGO_PROFILE)"echo " Binary: $WARP_BIN -> $BINARY_NAME"echo " Features: $FEATURES"echo ""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 1fiBINARY_SIZE=$(du -h "$BUILT_BINARY" | cut -f1)echo ""echo "==> Build complete ($BINARY_SIZE)"# Prepare the bundled resources tree (skills, settings schema) deployed to# the global, version-independent location the daemon reads.rm -rf "$LOCAL_RESOURCES_DIR"SKIP_SETTINGS_SCHEMA=1 "$WORKSPACE_ROOT/script/prepare_bundled_resources" \ "$LOCAL_RESOURCES_DIR" \ local# 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 1fiREMOTE_ABS_DIR="$REMOTE_HOME/$REMOTE_DIR"# Ensure rsync is available on the remote hostif ! 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 fifi# Ensure the remote install directory existsssh "$HOST" "mkdir -p $REMOTE_ABS_DIR"echo "==> Uploading binary 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 "==> Uploading bundled resources to $HOST:$REMOTE_ABS_DIR/$BUNDLED_RESOURCES_DIR_NAME"rsync -z -rlt --delete --partial --progress \ "$LOCAL_RESOURCES_DIR/" \ "$HOST:$REMOTE_ABS_DIR/$BUNDLED_RESOURCES_DIR_NAME/"echo "==> Generating Linux settings schema on $HOST"ssh "$HOST" \ "$REMOTE_ABS_DIR/$BINARY_NAME dump-settings-schema $REMOTE_ABS_DIR/$BUNDLED_RESOURCES_DIR_NAME/settings_schema.json"echo "==> Stopping stale remote-server daemons on $HOST"ssh "$HOST" bash <<'EOF'set -euo pipefailREMOTE_SERVER_ROOT="$HOME/.warp-local/remote-server"shopt -s nullglobfor pid_file in "$REMOTE_SERVER_ROOT"/*/server.pid; do daemon_dir="$(dirname "$pid_file")" pid="$(cat "$pid_file" 2>/dev/null || true)" if [[ -z "$pid" || ! "$pid" =~ ^[0-9]+$ ]]; then rm -f "$daemon_dir/server.sock" "$pid_file" continue fi if [[ ! -r "/proc/$pid/cmdline" ]]; then rm -f "$daemon_dir/server.sock" "$pid_file" continue fi cmdline="$(tr '\0' ' ' <"/proc/$pid/cmdline" || true)" if [[ "$cmdline" != *remote-server-daemon* ]]; then continue fi echo " Stopping stale daemon pid=$pid dir=$daemon_dir" kill "$pid" 2>/dev/null || true for _ in {1..50}; do if ! kill -0 "$pid" 2>/dev/null; then break fi sleep 0.1 done if kill -0 "$pid" 2>/dev/null; then echo " Force-stopping stale daemon pid=$pid" kill -9 "$pid" 2>/dev/null || true fi rm -f "$daemon_dir/server.sock" "$pid_file"doneEOFecho ""echo "==> Done! Binary deployed to $HOST:$REMOTE_ABS_DIR/$BINARY_NAME"echo " Bundled resources: $REMOTE_ABS_DIR/$BUNDLED_RESOURCES_DIR_NAME"echo " (resolved from ~/$REMOTE_DIR on remote)"
#!/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 <<EOF
Usage: $(basename "$0") --host <user@hostname> [OPTIONS]
Cross-compile the Oz CLI for Linux x86_64 and upload it to a remote host.
Required:
--host <user@hostname> Remote host to upload to
Options:
--profile <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
TARGET="x86_64-unknown-linux-musl"
source "$SCRIPT_DIR/linux/configure_musl_toolchain" "$TARGET"
# 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
# Select the requested Cargo build profile.
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,remote_codebase_indexing"
WARP_BIN="warp"
BINARY_NAME="oz-local"
REMOTE_DIR=".warp-local/remote-server"
# Global, version-independent resources location read by the daemon. Must
# match BUNDLED_RESOURCES_DIR_NAME in crates/remote_server/src/setup.rs.
BUNDLED_RESOURCES_DIR_NAME="bundled_resources"
# 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"
LOCAL_RESOURCES_DIR="$OUTPUT_DIR/remote-server-resources"
echo "==> Building Oz CLI for $TARGET (profile=$CARGO_PROFILE)"
echo " Binary: $WARP_BIN -> $BINARY_NAME"
echo " Features: $FEATURES"
echo ""
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)"
# Prepare the bundled resources tree (skills, settings schema) deployed to
# the global, version-independent location the daemon reads.
rm -rf "$LOCAL_RESOURCES_DIR"
SKIP_SETTINGS_SCHEMA=1 "$WORKSPACE_ROOT/script/prepare_bundled_resources" \
"$LOCAL_RESOURCES_DIR" \
local
# 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 install directory exists
ssh "$HOST" "mkdir -p $REMOTE_ABS_DIR"
echo "==> Uploading binary 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 "==> Uploading bundled resources to $HOST:$REMOTE_ABS_DIR/$BUNDLED_RESOURCES_DIR_NAME"
rsync -z -rlt --delete --partial --progress \
"$LOCAL_RESOURCES_DIR/" \
"$HOST:$REMOTE_ABS_DIR/$BUNDLED_RESOURCES_DIR_NAME/"
echo "==> Generating Linux settings schema on $HOST"
ssh "$HOST" \
"$REMOTE_ABS_DIR/$BINARY_NAME dump-settings-schema $REMOTE_ABS_DIR/$BUNDLED_RESOURCES_DIR_NAME/settings_schema.json"
echo "==> Stopping stale remote-server daemons on $HOST"
ssh "$HOST" bash <<'EOF'
set -euo pipefail
REMOTE_SERVER_ROOT="$HOME/.warp-local/remote-server"
shopt -s nullglob
for pid_file in "$REMOTE_SERVER_ROOT"/*/server.pid; do
daemon_dir="$(dirname "$pid_file")"
pid="$(cat "$pid_file" 2>/dev/null || true)"
if [[ -z "$pid" || ! "$pid" =~ ^[0-9]+$ ]]; then
rm -f "$daemon_dir/server.sock" "$pid_file"
continue
fi
if [[ ! -r "/proc/$pid/cmdline" ]]; then
rm -f "$daemon_dir/server.sock" "$pid_file"
continue
fi
cmdline="$(tr '\0' ' ' <"/proc/$pid/cmdline" || true)"
if [[ "$cmdline" != *remote-server-daemon* ]]; then
continue
fi
echo " Stopping stale daemon pid=$pid dir=$daemon_dir"
kill "$pid" 2>/dev/null || true
for _ in {1..50}; do
if ! kill -0 "$pid" 2>/dev/null; then
break
fi
sleep 0.1
done
if kill -0 "$pid" 2>/dev/null; then
echo " Force-stopping stale daemon pid=$pid"
kill -9 "$pid" 2>/dev/null || true
fi
rm -f "$daemon_dir/server.sock" "$pid_file"
done
EOF
echo ""
echo "==> Done! Binary deployed to $HOST:$REMOTE_ABS_DIR/$BINARY_NAME"
echo " Bundled resources: $REMOTE_ABS_DIR/$BUNDLED_RESOURCES_DIR_NAME"
echo " (resolved from ~/$REMOTE_DIR on remote)"