#!/usr/bin/env bash # # Builds the Integration-channel Oz CLI for Linux x86_64 (musl) and uploads it # to the SSH integration testing VM so that remote-server integration tests run # against the binary built from the current branch. # # This script is intended to be run once by CI before the integration test # suite, not by individual test functions. The binary is placed at the # versioned Integration channel path on the remote host, which is where the # Integration channel's `check_binary` looks. # # Prerequisites (same as script/deploy_remote_server): # On Linux, install curl, sha256sum, tar, and sshpass. # rustup target add x86_64-unknown-linux-musl # gcloud CLI authenticated for the warp-ssh-integration-testing project # # Usage: # script/deploy_remote_server_to_test_vm set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" WORKSPACE_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" TARGET="x86_64-unknown-linux-musl" CARGO_PROFILE="dev-remote" FEATURES="crash_reporting,standalone,agent_mode_debug,remote_codebase_indexing" WARP_BIN="integration" # The integration test runner uses Channel::Integration, which maps to # cli_command_name() = "oz-integration", remote_server_dir() = "~/.warp-dev/remote-server", # and remote_server_binary() = "oz-integration-${pinned_version}". # See crates/warp_core/src/channel/mod.rs, crates/remote_server/src/setup.rs, # and crates/integration/src/bin/integration.rs. # # We intentionally build app/src/bin/integration.rs instead of the normal # Local-channel app binary. The Local-channel release bundle requires # warp-channel-config to generate local_config.json at compile time, but this CI # path should not depend on private channel config. The Integration-channel # binary embeds a static non-secret config instead. REMOTE_DIR=".warp-dev/remote-server" # Dedicated remote-server test VM, configured similarly to the SSH integration # test VM but isolated from the general SSH integration suite. REMOTE_USERS=(bash zsh) REMOTE_HOST="ssh-remote-server-testing" REMOTE_PORT="22" PROXY_COMMAND="gcloud compute start-iap-tunnel ssh-remote-server-testing ${REMOTE_PORT} --listen-on-stdin --project=warp-ssh-integration-testing --zone=us-east4-b" app_version() { if [[ -n "${GIT_RELEASE_TAG:-}" ]]; then echo "$GIT_RELEASE_TAG" return fi cargo metadata --no-deps --format-version 1 \ | python3 -c 'import json, sys; print(next(p["version"] for p in json.load(sys.stdin)["packages"] if p["name"] == "remote_server" and p["manifest_path"].endswith("/crates/remote_server/Cargo.toml")))' } # ── Preflight checks ────────────────────────────────────────────── source "$SCRIPT_DIR/linux/configure_musl_toolchain" "$TARGET" if ! rustup target list --installed 2>/dev/null | grep -q "$TARGET"; then echo "Error: $TARGET target not installed." >&2 echo "Install it with: rustup target add $TARGET" >&2 exit 1 fi BUNDLE_VERSION="$(app_version)" BUNDLE_BINARY_NAME="oz-integration" BINARY_NAME="$BUNDLE_BINARY_NAME-$BUNDLE_VERSION" # ── Build ───────────────────────────────────────────────────────── CARGO_TARGET_DIR="${CARGO_TARGET_DIR:-$WORKSPACE_ROOT/target}" OUTPUT_DIR="$CARGO_TARGET_DIR/$TARGET/$CARGO_PROFILE" BUILT_BINARY="$OUTPUT_DIR/$WARP_BIN" LOCAL_BUNDLE_DIR="$OUTPUT_DIR/remote-server-integration-bundle" LOCAL_BUNDLE_TARBALL="$OUTPUT_DIR/remote-server-integration-bundle.tar.gz" echo "==> Building Integration-channel Oz CLI for $TARGET (profile=$CARGO_PROFILE)" 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 "==> Build complete ($BINARY_SIZE)" rm -rf "$LOCAL_BUNDLE_DIR" mkdir -p "$LOCAL_BUNDLE_DIR" cp "$BUILT_BINARY" "$LOCAL_BUNDLE_DIR/$BUNDLE_BINARY_NAME" "$WORKSPACE_ROOT/script/prepare_bundled_resources" \ "$LOCAL_BUNDLE_DIR/resources" \ integration \ "$CARGO_PROFILE" tar -czf "$LOCAL_BUNDLE_TARBALL" -C "$LOCAL_BUNDLE_DIR" . # ── Upload via SCP through GCP IAP tunnel ───────────────────────── # The test VM uses password auth (same as the SSH integration tests). # sshpass provides the password non-interactively for SSH/SCP. SSH_PASSWORD="password" SSH_OPTS=(-p "$REMOTE_PORT" -o "ProxyCommand=$PROXY_COMMAND" -o PreferredAuthentications=password -o PubkeyAuthentication=no -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null) if command -v sshpass &>/dev/null; then export SSHPASS="$SSH_PASSWORD" SSH_CMD=(sshpass -e ssh) SCP_CMD=(sshpass -e scp) else echo "Warning: sshpass not found, SSH/SCP will prompt for password interactively." >&2 if [[ "$(uname -s)" == "Darwin" ]]; then echo "Install it with: brew install hudochenkov/sshpass/sshpass" >&2 elif [[ "$(uname -s)" == "Linux" ]]; then echo "Install it with: sudo apt-get install sshpass" >&2 fi SSH_CMD=(ssh) SCP_CMD=(scp) fi for REMOTE_USER in "${REMOTE_USERS[@]}"; do echo "==> Ensuring remote directory exists for ${REMOTE_USER}" "${SSH_CMD[@]}" "${SSH_OPTS[@]}" "${REMOTE_USER}@${REMOTE_HOST}" "mkdir -p ~/$REMOTE_DIR" echo "==> Stopping stale remote server daemons for ${REMOTE_USER}" "${SSH_CMD[@]}" "${SSH_OPTS[@]}" "${REMOTE_USER}@${REMOTE_HOST}" "bash -s -- '$REMOTE_DIR'" <<'REMOTE_SCRIPT' set -euo pipefail REMOTE_ROOT="$HOME/$1" mkdir -p "$REMOTE_ROOT" shopt -s nullglob for PID_FILE in "$REMOTE_ROOT"/*/server.pid; do DAEMON_DIR="$(dirname "$PID_FILE")" PID="$(cat "$PID_FILE" 2>/dev/null || true)" if [[ "$PID" =~ ^[0-9]+$ ]] && [[ -d "/proc/$PID" ]]; then CMDLINE="$(tr '\0' ' ' < "/proc/$PID/cmdline" 2>/dev/null || true)" if [[ "$CMDLINE" == *remote-server-daemon* ]]; then echo "Stopping stale remote-server daemon pid $PID" 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 remote-server daemon pid $PID" kill -9 "$PID" 2>/dev/null || true fi fi fi rm -f "$DAEMON_DIR/server.sock" "$DAEMON_DIR/server.pid" done rm -f "$REMOTE_ROOT"/remote-server-integration-bundle-*.tar.gz REMOTE_SCRIPT TMP_BUNDLE_NAME="remote-server-integration-bundle-${GITHUB_RUN_ID:-local}-$$-${REMOTE_USER}.tar.gz" echo "==> Uploading bundle to ${REMOTE_USER}@${REMOTE_HOST}:~/$REMOTE_DIR/$TMP_BUNDLE_NAME" "${SCP_CMD[@]}" -P "$REMOTE_PORT" \ -o "ProxyCommand=$PROXY_COMMAND" \ -o PreferredAuthentications=password \ -o PubkeyAuthentication=no \ -o StrictHostKeyChecking=no \ -o UserKnownHostsFile=/dev/null \ "$LOCAL_BUNDLE_TARBALL" \ "${REMOTE_USER}@${REMOTE_HOST}:~/$REMOTE_DIR/$TMP_BUNDLE_NAME" echo "==> Installing binary and resources for ${REMOTE_USER}" # Args: . # The tarball ships the unversioned binary beside its resources/ tree; the # installed binary carries the version suffix the Integration channel # launches. The resources tree lands at `bundled_resources`, the global # version-independent location the daemon reads (must match # BUNDLED_RESOURCES_DIR_NAME in crates/remote_server/src/setup.rs). "${SSH_CMD[@]}" "${SSH_OPTS[@]}" "${REMOTE_USER}@${REMOTE_HOST}" \ "bash -s -- '$REMOTE_DIR' '$TMP_BUNDLE_NAME' '$BUNDLE_BINARY_NAME' '$BINARY_NAME'" <<'REMOTE_SCRIPT' set -euo pipefail cd "$HOME/$1" tar -xzf "$2" rm -f "$2" rm -rf bundled_resources mv resources bundled_resources chmod 755 "$3" mv -f "$3" "$4" "./$4" --version REMOTE_SCRIPT done echo "" echo "==> Done! Binary deployed to ${REMOTE_HOST}:~/$REMOTE_DIR/$BINARY_NAME for users: ${REMOTE_USERS[*]}" echo " Bundled resources: ~/$REMOTE_DIR/bundled_resources"