Files
hapi/scripts/deploy-remote.sh
T
wushenghua 7221c8caed feat(deploy): add remote deploy over SSH with a shared signing step
- extract the pinned-identity signing into scripts/sign-build.sh so local and
  remote deploys ship the same stable signature (macOS TCC grants key on the
  signing identity, not the path)
- add scripts/deploy-remote.sh: signs locally, copies the binary to a new
  versioned file on the remote host, swaps the ~/.hapi/bin/hapi symlink,
  kicks the launchd job (default com.hapi.runner, override via
  HAPI_REMOTE_LAUNCHD_LABEL), and verifies the runner executes the new file;
  running sessions survive
- document the remote flow in AGENTS.md and docs/local-deployment.md
2026-09-16 18:29:42 +08:00

111 lines
3.5 KiB
Bash
Executable File

#!/bin/bash
# Deploy the built-and-signed all-in-one binary to a remote Mac over SSH.
# Usage: scripts/deploy-remote.sh <ssh-target> [tag]
# e.g. scripts/deploy-remote.sh k2lab stable-signing
#
# The remote host must use this repo's deployment layout: a versioned binary
# under ~/.hapi/bin/ with the stable `hapi` symlink, supervised by a launchd
# job (default com.hapi.runner; override with HAPI_REMOTE_LAUNCHD_LABEL).
# Running sessions survive the restart; new sessions use the new binary.
set -euo pipefail
cd "$(dirname "$0")/.."
target=${1:-}
tag=${2:-}
if [ -z "$target" ]; then
echo "usage: scripts/deploy-remote.sh <ssh-target> [tag]" >&2
exit 1
fi
label=${HAPI_REMOTE_LAUNCHD_LABEL:-com.hapi.runner}
build=cli/dist-exe/bun-darwin-arm64/hapi
stamp=$(date +%Y%m%d-%H%M%S)
release="hapi.$stamp${tag:+-$tag}"
ssh_opts=(-o BatchMode=yes -o ConnectTimeout=10)
if [ ! -x "$build" ]; then
echo "error: build missing at $build; run 'bun run build:single-exe' first" >&2
exit 1
fi
# Sign locally with the pinned identity: the remote TCC database keys grants
# to the signing identity, so shipping an ad-hoc build there re-triggers
# permission prompts after every deploy.
bash scripts/sign-build.sh "$build"
local_arch=$(uname -m)
remote_arch=$(ssh "${ssh_opts[@]}" "$target" 'uname -m')
if [ "$local_arch" != "$remote_arch" ]; then
echo "error: remote arch $remote_arch does not match local build arch $local_arch" >&2
exit 1
fi
remote_home=$(ssh "${ssh_opts[@]}" "$target" 'printf %s "$HOME"')
echo "target: $target ($remote_arch)"
echo "release: $release"
# Copy to a NEW versioned filename: macOS caches Mach-O signatures by path,
# so never overwrite an existing executable path.
ssh "${ssh_opts[@]}" "$target" "mkdir -p '$remote_home/.hapi/bin'"
scp -q -C "${ssh_opts[@]}" "$build" "$target:$remote_home/.hapi/bin/$release"
ssh "${ssh_opts[@]}" "$target" "STAMP='$stamp' TAG='$tag' LABEL='$label' bash -s" <<'REMOTE'
set -euo pipefail
bin_dir="$HOME/.hapi/bin"
stable="$bin_dir/hapi"
new_name="hapi.$STAMP${TAG:+-$TAG}"
new="$bin_dir/$new_name"
if [ ! -x "$new" ]; then
echo "error: uploaded binary missing at $new" >&2
exit 1
fi
codesign --verify --deep --strict "$new"
"$new" --version
if ! launchctl print "gui/$(id -u)/$LABEL" >/dev/null 2>&1; then
echo "error: launchd job $LABEL is not loaded on this host" >&2
exit 1
fi
old_target=""
if [ -L "$stable" ]; then
old_target=$(readlink "$stable")
fi
ln -sfn "$new_name" "$stable"
launchctl kickstart -k "gui/$(id -u)/$LABEL"
runner_pid=""
runner_exe=""
for _ in $(seq 1 15); do
sleep 1
if [ -f "$HOME/.hapi/runner.state.json" ]; then
runner_pid=$(sed -n 's/.*"pid": *\([0-9][0-9]*\).*/\1/p' "$HOME/.hapi/runner.state.json" | head -n 1 || true)
fi
if [ -n "$runner_pid" ] && kill -0 "$runner_pid" 2>/dev/null; then
runner_exe=$(lsof -p "$runner_pid" 2>/dev/null | awk '$4=="txt"{print $NF}' | head -n 1)
break
fi
runner_pid=""
done
echo "link: $(readlink "$stable")"
echo "rollback target: ${old_target:-none}"
if [ -z "$runner_pid" ]; then
echo "warning: runner not up yet; check 'launchctl print gui/$(id -u)/$LABEL'" >&2
exit 1
fi
echo "runner pid: $runner_pid"
echo "runner exe: $runner_exe"
case "$runner_exe" in
*"$new_name"*) echo "remote deploy ok" ;;
*) echo "warning: runner executable is not $new_name; it will switch on the next restart" >&2 ;;
esac
REMOTE
echo "rollback: ssh $target \"ln -sfn <old target> ~/.hapi/bin/hapi && launchctl kickstart -k gui/\\\$(id -u)/$label\""