326 lines · 9.9 KB · Shell
RawDownload
#!/usr/bin/env bash# Reset CodexBar: kill running instances, build, package, relaunch, verify. set -euo pipefail ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"APP_BUNDLE="${ROOT_DIR}/CodexBar.app"APP_PROCESS_PATTERN="CodexBar.app/Contents/MacOS/CodexBar"DEBUG_PROCESS_PATTERN="${ROOT_DIR}/.build/debug/CodexBar"RELEASE_PROCESS_PATTERN="${ROOT_DIR}/.build/release/CodexBar"LOCK_KEY="$(printf '%s' "${ROOT_DIR}" | shasum -a 256 | cut -c1-8)"LOCK_DIR="${TMPDIR:-/tmp}/codexbar-compile-and-run-${LOCK_KEY}"LOCK_PID_FILE="${LOCK_DIR}/pid"WAIT_FOR_LOCK=0RUN_TESTS=0DEBUG_LLDB=0RELEASE_ARCHES=""SIGNING_MODE="${CODEXBAR_SIGNING:-}"CLEAR_ADHOC_KEYCHAIN=0 log()  { printf '%s\n' "$*"; }fail() { printf 'ERROR: %s\n' "$*" >&2; exit 1; } delete_keychain_service_items() {  local service="$1"  security delete-generic-password -s "${service}" >/dev/null 2>&1 || true  while security delete-generic-password -s "${service}" >/dev/null 2>&1; do    :  done} # Ensure Swift >= 5.5 (required for --arch flag in swift build)ensure_swift_version() {  local swift_output  local swift_ver  swift_output=$(swift --version 2>&1 || true)  if [[ "$swift_output" =~ (Apple[[:space:]]+)?Swift[[:space:]]+version[[:space:]]+([0-9]+)\.([0-9]+)(\.[0-9]+)? ]]; then    swift_ver="${BASH_REMATCH[2]}.${BASH_REMATCH[3]}${BASH_REMATCH[4]}"  else    fail "Swift >= 5.5 required (found ${swift_output:-none}). Install Xcode or update swiftly."  fi  local major minor  major=$(echo "$swift_ver" | cut -d. -f1)  minor=$(echo "$swift_ver" | cut -d. -f2)  if [[ "${major:-0}" -ge 6 ]] || { [[ "${major:-0}" -eq 5 ]] && [[ "${minor:-0}" -ge 5 ]]; }; then    return 0  fi  # Try Xcode toolchain  local xcrun_swift  xcrun_swift=$(xcrun --find swift 2>/dev/null || true)  if [[ -n "$xcrun_swift" && -x "$xcrun_swift" ]]; then    log "WARN: PATH swift is v${swift_ver}; switching to Xcode toolchain at $(dirname "$xcrun_swift")"    export PATH="$(dirname "$xcrun_swift"):$PATH"    return 0  fi  fail "Swift >= 5.5 required (found ${swift_ver:-none}). Install Xcode or update swiftly."} has_signing_identity() {  local identity="${1:-}"  if [[ -z "${identity}" ]]; then    return 1  fi  security find-identity -p codesigning -v 2>/dev/null | grep -F "${identity}" >/dev/null 2>&1} detect_codesigning_identity() {  local preferred_prefixes=(    "Developer ID Application:"    "Apple Development:"    "Apple Distribution:"  )  local prefix  local identities  identities="$(security find-identity -p codesigning -v 2>/dev/null || true)"  for prefix in "${preferred_prefixes[@]}"; do    awk -v prefix="${prefix}" '      index($0, "\"" prefix) {        sub(/^[^\"]*\"/, "")        sub(/\".*$/, "")        print        exit      }    ' <<<"${identities}"  done | sed -n '1p'} export_team_id_from_identity() {  local identity="${1:-}"  if [[ -n "${APP_TEAM_ID:-}" || -z "${identity}" ]]; then    return  fi  local subject  subject="$(security find-certificate -c "${identity}" -p 2>/dev/null \    | openssl x509 -noout -subject -nameopt RFC2253 2>/dev/null || true)"  if [[ "${subject}" =~ (^|,)OU=([A-Z0-9]{10})(,|$) ]]; then    APP_TEAM_ID="${BASH_REMATCH[2]}"    export APP_TEAM_ID    return  fi  if [[ "${identity}" =~ \(([A-Z0-9]{10})\)$ ]]; then    APP_TEAM_ID="${BASH_REMATCH[1]}"    export APP_TEAM_ID  fi} resolve_signing_mode() {  if [[ -n "${SIGNING_MODE}" ]]; then    export_team_id_from_identity "${APP_IDENTITY:-}"    return  fi   if [[ -n "${APP_IDENTITY:-}" ]]; then    if has_signing_identity "${APP_IDENTITY}"; then      export_team_id_from_identity "${APP_IDENTITY}"      SIGNING_MODE="identity"      return    fi    log "WARN: APP_IDENTITY not found in Keychain; falling back to adhoc signing."    SIGNING_MODE="adhoc"    return  fi   local candidate=""  for candidate in \    "Developer ID Application: Peter Steinberger (Y5PE65HELJ)" \    "CodexBar Development"  do    if has_signing_identity "${candidate}"; then      APP_IDENTITY="${candidate}"      export APP_IDENTITY      export_team_id_from_identity "${APP_IDENTITY}"      SIGNING_MODE="identity"      return    fi  done   candidate="$(detect_codesigning_identity)"  if [[ -n "${candidate}" ]]; then    APP_IDENTITY="${candidate}"    export APP_IDENTITY    export_team_id_from_identity "${APP_IDENTITY}"    SIGNING_MODE="identity"    return  fi   SIGNING_MODE="adhoc"} run_step() {  local label="$1"; shift  log "==> ${label}"  if ! "$@"; then    fail "${label} failed"  fi} cleanup() {  if [[ -d "${LOCK_DIR}" ]]; then    rm -rf "${LOCK_DIR}"  fi} acquire_lock() {  while true; do    if mkdir "${LOCK_DIR}" 2>/dev/null; then      echo "$$" > "${LOCK_PID_FILE}"      return 0    fi     local existing_pid=""    if [[ -f "${LOCK_PID_FILE}" ]]; then      existing_pid="$(cat "${LOCK_PID_FILE}" 2>/dev/null || true)"    fi     if [[ -n "${existing_pid}" ]] && kill -0 "${existing_pid}" 2>/dev/null; then      if [[ "${WAIT_FOR_LOCK}" == "1" ]]; then        log "==> Another agent is compiling (pid ${existing_pid}); waiting..."        while kill -0 "${existing_pid}" 2>/dev/null; do          sleep 1        done        continue      fi      log "==> Another agent is compiling (pid ${existing_pid}); re-run with --wait."      exit 0    fi     rm -rf "${LOCK_DIR}"  done} trap cleanup EXIT INT TERM kill_claude_probes() {  # CodexBar spawns `claude /usage` + `/status` in a PTY; if we kill the app mid-probe we can orphan them.  pkill -f "claude (/status|/usage) --allowed-tools" 2>/dev/null || true  sleep 0.2  pkill -9 -f "claude (/status|/usage) --allowed-tools" 2>/dev/null || true} kill_all_codexbar() {  is_running() {    pgrep -f "${APP_PROCESS_PATTERN}" >/dev/null 2>&1 \      || pgrep -f "${DEBUG_PROCESS_PATTERN}" >/dev/null 2>&1 \      || pgrep -f "${RELEASE_PROCESS_PATTERN}" >/dev/null 2>&1 \      || pgrep -x "CodexBar" >/dev/null 2>&1  }   # Phase 1: request termination (give the app time to exit cleanly).  for _ in {1..25}; do    pkill -f "${APP_PROCESS_PATTERN}" 2>/dev/null || true    pkill -f "${DEBUG_PROCESS_PATTERN}" 2>/dev/null || true    pkill -f "${RELEASE_PROCESS_PATTERN}" 2>/dev/null || true    pkill -x "CodexBar" 2>/dev/null || true    if ! is_running; then      return 0    fi    sleep 0.2  done   # Phase 2: force kill any stragglers (avoids `open -n` creating multiple instances).  pkill -9 -f "${APP_PROCESS_PATTERN}" 2>/dev/null || true  pkill -9 -f "${DEBUG_PROCESS_PATTERN}" 2>/dev/null || true  pkill -9 -f "${RELEASE_PROCESS_PATTERN}" 2>/dev/null || true  pkill -9 -x "CodexBar" 2>/dev/null || true   for _ in {1..25}; do    if ! is_running; then      return 0    fi    sleep 0.2  done   fail "Failed to kill all CodexBar instances."} # 1) Ensure a single runner instance.for arg in "$@"; do  case "${arg}" in    --wait|-w) WAIT_FOR_LOCK=1 ;;    --test|-t) RUN_TESTS=1 ;;    --debug-lldb) DEBUG_LLDB=1 ;;    --clear-adhoc-keychain) CLEAR_ADHOC_KEYCHAIN=1 ;;    --release-universal) RELEASE_ARCHES="arm64 x86_64" ;;    --release-arches=*) RELEASE_ARCHES="${arg#*=}" ;;    --help|-h)      log "Usage: $(basename "$0") [--wait] [--test] [--debug-lldb] [--clear-adhoc-keychain] [--release-universal] [--release-arches=\"arm64 x86_64\"]"      exit 0      ;;    *)      ;;  esacdone ensure_swift_versionresolve_signing_modeif [[ "${CLEAR_ADHOC_KEYCHAIN}" == "1" && "${SIGNING_MODE}" != "adhoc" ]]; then  fail "--clear-adhoc-keychain is only supported when using adhoc signing."fiif [[ "${SIGNING_MODE}" == "adhoc" ]]; then  log "==> Signing: adhoc (set APP_IDENTITY or install a dev cert to avoid keychain prompts)"else  log "==> Signing: ${APP_IDENTITY:-Developer ID Application}"fi acquire_lock # 2) Kill all running CodexBar instances (debug, release, bundled).log "==> Killing existing CodexBar instances"kill_all_codexbarkill_claude_probes # 2.5) Optionally delete keychain entries to avoid permission prompts with adhoc signing# (adhoc signature changes on every build, making old keychain entries inaccessible)if [[ "${SIGNING_MODE:-adhoc}" == "adhoc" && "${CLEAR_ADHOC_KEYCHAIN}" == "1" ]]; then  log "==> Clearing CodexBar keychain entries (adhoc signing)"  # Clear both the legacy keychain store and the current cache service when developers explicitly want a clean reset  # of CodexBar-owned keychain state for ad-hoc builds.  delete_keychain_service_items "com.steipete.CodexBar"  delete_keychain_service_items "com.steipete.codexbar.cache"elif [[ "${SIGNING_MODE:-adhoc}" == "adhoc" ]]; then  log "==> Preserving CodexBar keychain entries (pass --clear-adhoc-keychain to reset adhoc keychain state)"fi # 3) Package (release build happens inside package_app.sh).if [[ "${RUN_TESTS}" == "1" ]]; then  run_step "sharded swift tests" "${ROOT_DIR}/Scripts/test.sh"fiif [[ "${DEBUG_LLDB}" == "1" && -n "${RELEASE_ARCHES}" ]]; then  fail "--release-arches is only supported for release packaging"fiHOST_ARCH="$(uname -m)"ARCHES_VALUE="${HOST_ARCH}"if [[ -n "${RELEASE_ARCHES}" ]]; then  ARCHES_VALUE="${RELEASE_ARCHES}"fiPACKAGE_ENV=(  ARCHES="${ARCHES_VALUE}")if [[ "${DEBUG_LLDB}" == "1" ]]; then  run_step "package app" env CODEXBAR_ALLOW_LLDB=1 "${PACKAGE_ENV[@]}" "${ROOT_DIR}/Scripts/package_app.sh" debugelse  if [[ -n "${SIGNING_MODE}" ]]; then    run_step "package app" env CODEXBAR_SIGNING="${SIGNING_MODE}" "${PACKAGE_ENV[@]}" "${ROOT_DIR}/Scripts/package_app.sh"  else    run_step "package app" env "${PACKAGE_ENV[@]}" "${ROOT_DIR}/Scripts/package_app.sh"  fifi # 4) Launch the packaged app.log "==> launch app"if ! open "${APP_BUNDLE}"; then  log "WARN: launch app returned non-zero; falling back to direct binary launch."  "${APP_BUNDLE}/Contents/MacOS/CodexBar" >/dev/null 2>&1 &  disownfi # 5) Verify the app stays up for at least a moment (launch can be >1s on some systems).for _ in {1..10}; do  if pgrep -f "${APP_PROCESS_PATTERN}" >/dev/null 2>&1; then    log "OK: CodexBar is running."    exit 0  fi  sleep 0.4donefail "App exited immediately. Check crash logs in Console.app (User Reports)."