#!/usr/bin/env bash # Build huskies, install (codesign-heal wrapper + underlying binary), and if a # gateway is running on this host, hot-restart it detached from the current shell # so SSH disconnect — e.g. when redeploying from a phone — doesn't kill it. # # Skips the restart silently if no gateway is running. Errors loudly if more # than one matches, so we don't restart the wrong one. # # Pass --skip-check to bypass `script/check` (useful for docs / build-script # changes you've already verified). # # On relaunch failure the previous binary is restored from # ~/bin/huskies-bin.prev and re-launched, so a bad deploy doesn't leave the # host without a working gateway. # # After a `cp` or download the binary loses its ad-hoc signature and macOS # SIGKILLs it silently on Apple Silicon. The wrapper at ~/bin/huskies re-signs # the underlying binary at ~/bin/huskies-bin whenever codesign validation # fails, then execs it. Normal launches (already signed) are zero-overhead. set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" LOG_DIR="${HUSKIES_LOG_DIR:-$PROJECT_ROOT/logs}" GATEWAY_PATTERN='huskies .*--gateway' BIN_DIR="${HOME}/bin" UNDERLYING="${BIN_DIR}/huskies-bin" WRAPPER="${BIN_DIR}/huskies" PREV_BIN="${BIN_DIR}/huskies-bin.prev" NEW_BIN="${PROJECT_ROOT}/target/release/huskies" SKIP_CHECK=0 for arg in "$@"; do case "$arg" in --skip-check) SKIP_CHECK=1 ;; -h|--help) sed -n '2,17p' "$0"; exit 0 ;; *) echo "Unknown arg: $arg (use --help)" >&2; exit 2 ;; esac done if [ "$SKIP_CHECK" -eq 0 ] && [ -x "$SCRIPT_DIR/check" ]; then echo "=== Running script/check ===" "$SCRIPT_DIR/check" fi echo "=== Building release binary ===" cd "$PROJECT_ROOT" cargo build --release --bin huskies mkdir -p "$BIN_DIR" # Snapshot current binary so we can roll back if the relaunch fails. PREV_VERSION="" if [ -x "$UNDERLYING" ]; then PREV_VERSION="$("$UNDERLYING" --version 2>/dev/null || echo unknown)" cp "$UNDERLYING" "$PREV_BIN" fi cp "$NEW_BIN" "$UNDERLYING" chmod +x "$UNDERLYING" codesign -s - -f "$UNDERLYING" 2>/dev/null NEW_VERSION="$("$UNDERLYING" --version 2>/dev/null || echo unknown)" echo "==> Installed binary: ${UNDERLYING}" if [ -n "$PREV_VERSION" ]; then echo " version: $PREV_VERSION → $NEW_VERSION" else echo " version: $NEW_VERSION (no prior install)" fi cat > "${WRAPPER}" << 'WRAPPER_EOF' #!/usr/bin/env bash # Codesign-heal wrapper — re-signs ~/bin/huskies-bin if the signature is # missing or invalid, then execs the binary. Logs only when it re-signs. BIN="${HOME}/bin/huskies-bin" if ! codesign --verify --quiet "${BIN}" 2>/dev/null; then codesign -s - "${BIN}" echo "[codesign-heal] re-signed ~/bin/huskies-bin" >&2 fi exec "${BIN}" "$@" WRAPPER_EOF chmod +x "${WRAPPER}" echo "==> Installed wrapper: ${WRAPPER}" # ── Hot-restart gateway if one is running ───────────────────────────── collect_descendants() { local pid="$1" kid for kid in $(pgrep -P "$pid" 2>/dev/null); do collect_descendants "$kid" printf '%s\n' "$kid" done } GATEWAY_PIDS="$(pgrep -f "$GATEWAY_PATTERN" || true)" if [ -z "$GATEWAY_PIDS" ]; then echo "==> No running gateway found; install complete." exit 0 fi if [ "$(echo "$GATEWAY_PIDS" | wc -l)" -gt 1 ]; then echo "Error: multiple gateway processes match '${GATEWAY_PATTERN}':" >&2 ps -p $GATEWAY_PIDS -o pid,args >&2 || true echo "Refusing to guess which to restart." >&2 exit 3 fi GATEWAY_PID="$GATEWAY_PIDS" GATEWAY_ARGS="$(ps -p "$GATEWAY_PID" -o args= | sed -E 's@^[^ ]*huskies[^ ]* @@')" GATEWAY_CWD="$(lsof -p "$GATEWAY_PID" 2>/dev/null | awk '$4=="cwd"{print $9; exit}')" if [ -z "$GATEWAY_CWD" ]; then GATEWAY_CWD="$PWD"; fi LOG_FILE="$LOG_DIR/gateway-$(date +%Y%m%d-%H%M%S).log" mkdir -p "$LOG_DIR" DESCENDANTS="$(collect_descendants "$GATEWAY_PID" | tr '\n' ' ')" echo "==> Stopping gateway tree (pids: $GATEWAY_PID $DESCENDANTS)" # Kill descendants depth-first so PTY children die before the gateway, then the gateway. for pid in $DESCENDANTS $GATEWAY_PID; do kill "$pid" 2>/dev/null || true done sleep 2 echo "==> Restarting gateway" echo " log: $LOG_FILE" ( cd "$GATEWAY_CWD" nohup "$WRAPPER" $GATEWAY_ARGS >> "$LOG_FILE" 2>&1 < /dev/null & disown ) # Wait up to 10s for the new gateway to appear AND be a different PID. NEW_PID="" for _ in 1 2 3 4 5 6 7 8 9 10; do sleep 1 candidate="$(pgrep -f "$GATEWAY_PATTERN" 2>/dev/null || true)" if [ -n "$candidate" ] && [ "$candidate" != "$GATEWAY_PID" ]; then NEW_PID="$candidate" break fi done if [ -n "$NEW_PID" ]; then echo "==> Gateway restarted as pid $NEW_PID" exit 0 fi # ── Rollback ────────────────────────────────────────────────────────── echo "Error: new gateway failed to come up within 10s; rolling back" >&2 if [ -x "$PREV_BIN" ]; then cp "$PREV_BIN" "$UNDERLYING" chmod +x "$UNDERLYING" codesign -s - -f "$UNDERLYING" 2>/dev/null echo "==> Restored previous binary" ( cd "$GATEWAY_CWD" nohup "$WRAPPER" $GATEWAY_ARGS >> "$LOG_FILE" 2>&1 < /dev/null & disown ) sleep 2 if pgrep -f "$GATEWAY_PATTERN" >/dev/null 2>&1; then echo "==> Gateway restored to previous version" exit 1 fi fi echo "Error: rollback failed; gateway is DOWN. Inspect $LOG_FILE." >&2 exit 1