mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-14 07:56:08 +00:00
511 lines
17 KiB
Bash
Executable File
511 lines
17 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Reset OpenClaw like Trimmy: kill running instances, rebuild, repackage, relaunch, verify.
|
|
|
|
set -euo pipefail
|
|
|
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
source "${ROOT_DIR}/scripts/lib/restart-mac-gateway.sh"
|
|
APP_BUNDLE="${OPENCLAW_APP_BUNDLE:-}"
|
|
APP_EXECUTABLE_RELATIVE_PATH="Contents/MacOS/OpenClaw"
|
|
DEBUG_PROCESS_PATTERN="${ROOT_DIR}/apps/macos/.build/debug/OpenClaw"
|
|
LOCAL_PROCESS_PATTERN="${ROOT_DIR}/apps/macos/.build-local/debug/OpenClaw"
|
|
RELEASE_PROCESS_PATTERN="${ROOT_DIR}/apps/macos/.build/release/OpenClaw"
|
|
LAUNCH_AGENT="${HOME}/Library/LaunchAgents/ai.openclaw.mac.plist"
|
|
LOCK_KEY="$(printf '%s' "${ROOT_DIR}" | shasum -a 256 | cut -c1-8)"
|
|
LOCK_DIR="${TMPDIR:-/tmp}/openclaw-restart-${LOCK_KEY}"
|
|
LOCK_PID_FILE="${LOCK_DIR}/pid"
|
|
LOCK_HELD=0
|
|
WAIT_FOR_LOCK=0
|
|
LOG_PATH="${OPENCLAW_RESTART_LOG:-${TMPDIR:-/tmp}/openclaw-restart-${LOCK_KEY}.log}"
|
|
NO_SIGN=0
|
|
SIGN=0
|
|
AUTO_DETECT_SIGNING=1
|
|
GATEWAY_WAIT_SECONDS="${OPENCLAW_GATEWAY_WAIT_SECONDS:-0}"
|
|
LAUNCHAGENT_DISABLE_MARKER="${HOME}/.openclaw/disable-launchagent"
|
|
ATTACH_ONLY=1
|
|
TARGET_ONLY=0
|
|
TARGET_APP_BUNDLE="${ROOT_DIR}/dist/OpenClaw.app"
|
|
TARGET_EXECUTABLE="${TARGET_APP_BUNDLE}/${APP_EXECUTABLE_RELATIVE_PATH}"
|
|
INSTALLED_EXECUTABLE="/Applications/OpenClaw.app/${APP_EXECUTABLE_RELATIVE_PATH}"
|
|
STAGED_APP_DIR="${ROOT_DIR}/dist/.openclaw-replacement-${LOCK_KEY}-$$"
|
|
STAGED_APP_BUNDLE="${STAGED_APP_DIR}/OpenClaw.app"
|
|
|
|
log() { printf '%s\n' "$*"; }
|
|
fail() { printf 'ERROR: %s\n' "$*" >&2; exit 1; }
|
|
|
|
# Ensure local node binaries (rolldown, pnpm) are discoverable for the steps below.
|
|
export PATH="${ROOT_DIR}/node_modules/.bin:${PATH}"
|
|
|
|
run_step() {
|
|
local label="$1"; shift
|
|
log "==> ${label}"
|
|
if ! "$@"; then
|
|
fail "${label} failed"
|
|
fi
|
|
}
|
|
|
|
cleanup() {
|
|
if [[ -n "${STAGED_APP_DIR:-}" ]]; then
|
|
rm -rf "${STAGED_APP_DIR}"
|
|
fi
|
|
if [[ "${LOCK_HELD}" != "1" || ! -d "${LOCK_DIR}" ]]; then
|
|
return 0
|
|
fi
|
|
local owner_pid=""
|
|
if [[ -f "${LOCK_PID_FILE}" ]]; then
|
|
owner_pid="$(cat "${LOCK_PID_FILE}" 2>/dev/null || true)"
|
|
fi
|
|
if [[ -z "${owner_pid}" || "${owner_pid}" == "$$" ]]; then
|
|
rm -rf "${LOCK_DIR}"
|
|
fi
|
|
}
|
|
|
|
acquire_lock() {
|
|
while true; do
|
|
if mkdir "${LOCK_DIR}" 2>/dev/null; then
|
|
LOCK_HELD=1
|
|
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 restart is running (pid ${existing_pid}); waiting..."
|
|
while kill -0 "${existing_pid}" 2>/dev/null; do
|
|
sleep 1
|
|
done
|
|
continue
|
|
fi
|
|
log "==> Another restart is running (pid ${existing_pid}); re-run with --wait."
|
|
exit 0
|
|
fi
|
|
|
|
rm -rf "${LOCK_DIR}"
|
|
done
|
|
}
|
|
|
|
check_signing_keys() {
|
|
security find-identity -p codesigning -v 2>/dev/null \
|
|
| grep -Eq '(Developer ID Application|Apple Distribution|Apple Development)'
|
|
}
|
|
|
|
canonicalize_app_bundle() {
|
|
if [[ -z "${APP_BUNDLE}" ]]; then
|
|
return 0
|
|
fi
|
|
if [[ ! -d "${APP_BUNDLE}" ]]; then
|
|
fail "OPENCLAW_APP_BUNDLE does not exist: ${APP_BUNDLE}"
|
|
fi
|
|
APP_BUNDLE="$(cd "${APP_BUNDLE}" && pwd -P)"
|
|
}
|
|
|
|
trap cleanup EXIT INT TERM
|
|
|
|
for arg in "$@"; do
|
|
case "${arg}" in
|
|
--wait|-w) WAIT_FOR_LOCK=1 ;;
|
|
--no-sign) NO_SIGN=1; AUTO_DETECT_SIGNING=0 ;;
|
|
--sign) SIGN=1; AUTO_DETECT_SIGNING=0 ;;
|
|
--attach-only) ATTACH_ONLY=1 ;;
|
|
--no-attach-only) ATTACH_ONLY=0 ;;
|
|
--target-only) TARGET_ONLY=1 ;;
|
|
--help|-h)
|
|
log "Usage: $(basename "$0") [--wait] [--no-sign] [--sign] [--attach-only|--no-attach-only] [--target-only]"
|
|
log " --wait Wait for other restart to complete instead of exiting"
|
|
log " --no-sign Force no code signing (fastest for development)"
|
|
log " --sign Force code signing (will fail if no signing key available)"
|
|
log " --attach-only Launch app with --attach-only (skip launchd install)"
|
|
log " --no-attach-only Launch app without attach-only override"
|
|
log " --target-only Restart only this checkout's dist app; fail if another OpenClaw app is active"
|
|
log ""
|
|
log "Env:"
|
|
log " OPENCLAW_GATEWAY_WAIT_SECONDS=0 Wait time before gateway port check (unsigned only)"
|
|
log ""
|
|
log "Unsigned recovery:"
|
|
log " node openclaw.mjs daemon install --force --runtime node"
|
|
log " node openclaw.mjs daemon restart"
|
|
log ""
|
|
log "Reset unsigned overrides:"
|
|
log " rm ~/.openclaw/disable-launchagent"
|
|
log ""
|
|
log "Default behavior: Auto-detect signing keys, fallback to --no-sign if none found"
|
|
exit 0
|
|
;;
|
|
--) ;;
|
|
*) fail "Unknown restart option: ${arg}" ;;
|
|
esac
|
|
done
|
|
|
|
if [[ "$NO_SIGN" -eq 1 && "$SIGN" -eq 1 ]]; then
|
|
fail "Cannot use --sign and --no-sign together"
|
|
fi
|
|
if [[ "$TARGET_ONLY" -eq 1 && "$ATTACH_ONLY" -ne 1 ]]; then
|
|
fail "--target-only requires --attach-only"
|
|
fi
|
|
if [[ "$TARGET_ONLY" -eq 1 && -n "$APP_BUNDLE" ]]; then
|
|
fail "--target-only does not accept OPENCLAW_APP_BUNDLE"
|
|
fi
|
|
canonicalize_app_bundle
|
|
|
|
mkdir -p "$(dirname "$LOG_PATH")"
|
|
rm -f "$LOG_PATH"
|
|
exec > >(tee "$LOG_PATH") 2>&1
|
|
log "==> Log: ${LOG_PATH}"
|
|
if [[ "$NO_SIGN" -eq 1 ]]; then
|
|
log "==> Using --no-sign (unsigned flow enabled)"
|
|
fi
|
|
if [[ "$ATTACH_ONLY" -eq 1 ]]; then
|
|
log "==> Using --attach-only (skip launchd install)"
|
|
fi
|
|
|
|
acquire_lock
|
|
|
|
kill_all_openclaw() {
|
|
for _ in {1..10}; do
|
|
local pids=""
|
|
pids="$(openclaw_process_pids)"
|
|
if [[ -z "${pids}" ]]; then
|
|
return 0
|
|
fi
|
|
while IFS= read -r pid; do
|
|
kill "${pid}" 2>/dev/null || true
|
|
done <<< "${pids}"
|
|
sleep 0.3
|
|
done
|
|
[[ -z "$(openclaw_process_pids)" ]]
|
|
}
|
|
|
|
known_openclaw_executables() {
|
|
if [[ -n "${APP_BUNDLE}" ]]; then
|
|
printf '%s\n' "${APP_BUNDLE}/${APP_EXECUTABLE_RELATIVE_PATH}"
|
|
fi
|
|
printf '%s\n' \
|
|
"${ROOT_DIR}/dist/OpenClaw.app/${APP_EXECUTABLE_RELATIVE_PATH}" \
|
|
"/Applications/OpenClaw.app/${APP_EXECUTABLE_RELATIVE_PATH}" \
|
|
"${DEBUG_PROCESS_PATTERN}" \
|
|
"${LOCAL_PROCESS_PATTERN}" \
|
|
"${RELEASE_PROCESS_PATTERN}"
|
|
}
|
|
|
|
openclaw_process_pids() {
|
|
local pattern=""
|
|
while IFS= read -r pattern; do
|
|
[[ -n "${pattern}" ]] || continue
|
|
process_pids_matching "${pattern}"
|
|
done < <(known_openclaw_executables) | sort -u
|
|
}
|
|
|
|
process_pids_matching() {
|
|
local pattern="$1"
|
|
ps axww -o pid=,command= 2>/dev/null \
|
|
| while read -r pid command_line; do
|
|
[[ "${pid}" =~ ^[0-9]+$ ]] || continue
|
|
[[ "${pid}" != "$$" ]] || continue
|
|
[[ "${command_line}" == *"${pattern}"* ]] || continue
|
|
printf '%s\n' "${pid}"
|
|
done
|
|
}
|
|
|
|
foreign_openclaw_process_pids() {
|
|
ps axww -o pid=,command= 2>/dev/null \
|
|
| while read -r pid command_line; do
|
|
[[ "${pid}" =~ ^[0-9]+$ ]] || continue
|
|
[[ "${pid}" != "$$" ]] || continue
|
|
local executable="${command_line%% *}"
|
|
[[ "${executable}" == "${TARGET_EXECUTABLE}" ]] && continue
|
|
[[ "${executable}" == "${INSTALLED_EXECUTABLE}" ]] && continue
|
|
if [[ "${executable}" == *"/OpenClaw.app/${APP_EXECUTABLE_RELATIVE_PATH}" \
|
|
|| "${executable}" == *"/apps/macos/.build/debug/OpenClaw" \
|
|
|| "${executable}" == *"/apps/macos/.build-local/debug/OpenClaw" \
|
|
|| "${executable}" == *"/apps/macos/.build/release/OpenClaw" ]]; then
|
|
printf '%s\n' "${pid}"
|
|
fi
|
|
done
|
|
}
|
|
|
|
process_pids_for_executable() {
|
|
local executable="$1"
|
|
ps axww -o pid=,command= 2>/dev/null \
|
|
| while read -r pid command_line; do
|
|
[[ "${pid}" =~ ^[0-9]+$ ]] || continue
|
|
[[ "${pid}" != "$$" ]] || continue
|
|
[[ "${command_line}" == "${executable}" || "${command_line}" == "${executable} "* ]] || continue
|
|
printf '%s\n' "${pid}"
|
|
done
|
|
}
|
|
|
|
managed_openclaw_process_pids() {
|
|
{
|
|
process_pids_for_executable "${TARGET_EXECUTABLE}"
|
|
process_pids_for_executable "${INSTALLED_EXECUTABLE}"
|
|
} | sort -u
|
|
}
|
|
|
|
launch_domain_jobs() {
|
|
local domain="$1"
|
|
local snapshot=""
|
|
if ! snapshot="$(/bin/launchctl print "${domain}" 2>/dev/null)"; then
|
|
return 1
|
|
fi
|
|
printf '%s\n' "${snapshot}" | /usr/bin/awk -v domain="${domain}" '
|
|
/^[[:space:]]*services = \{$/ { services = 1; next }
|
|
services && /^[[:space:]]*\}$/ { services = 0; next }
|
|
services && NF >= 3 { print domain "|" $NF }
|
|
'
|
|
}
|
|
|
|
loaded_launch_jobs() {
|
|
local uid=""
|
|
uid="$(/usr/bin/id -u)"
|
|
local domain=""
|
|
for domain in "gui/${uid}" "user/${uid}" system; do
|
|
launch_domain_jobs "${domain}" || return 1
|
|
done
|
|
}
|
|
|
|
launch_job_snapshot() {
|
|
local domain="$1"
|
|
local label="$2"
|
|
/bin/launchctl print "${domain}/${label}" 2>/dev/null
|
|
}
|
|
|
|
# A launchd-owned app will immediately respawn after target-only cleanup. Find
|
|
# exact managed executables before the expensive Swift build so the operator
|
|
# can stop the owning job instead of waiting for an inevitable switch failure.
|
|
print_managed_openclaw_supervisor_label() {
|
|
local domain="$1"
|
|
local label="$2"
|
|
local job=""
|
|
if ! job="$(launch_job_snapshot "${domain}" "${label}")"; then
|
|
return 0
|
|
fi
|
|
local executable=""
|
|
executable="$(/usr/bin/awk -F ' = ' '/^[[:space:]]*program = / { print $2; exit }' <<<"${job}")"
|
|
local properties=""
|
|
properties="$(/usr/bin/awk -F ' = ' '/^[[:space:]]*properties = / { print $2; exit }' <<<"${job}")"
|
|
local is_managed_executable=0
|
|
if [[ "${executable}" == "${TARGET_EXECUTABLE}" || "${executable}" == "${INSTALLED_EXECUTABLE}" ]]; then
|
|
is_managed_executable=1
|
|
fi
|
|
if [[ "${is_managed_executable}" -eq 1 && " ${properties} " == *" keepalive "* ]]; then
|
|
printf '%s\n' "${label}"
|
|
fi
|
|
}
|
|
|
|
managed_openclaw_supervisor_labels() {
|
|
local jobs=""
|
|
if ! jobs="$(loaded_launch_jobs)"; then
|
|
return 1
|
|
fi
|
|
local batch_size=0
|
|
local domain=""
|
|
local label=""
|
|
while IFS='|' read -r domain label; do
|
|
[[ -n "${domain}" && -n "${label}" ]] || continue
|
|
print_managed_openclaw_supervisor_label "${domain}" "${label}" &
|
|
batch_size=$((batch_size + 1))
|
|
if [[ "${batch_size}" -ge 16 ]]; then
|
|
wait
|
|
batch_size=0
|
|
fi
|
|
done <<< "${jobs}"
|
|
wait
|
|
}
|
|
|
|
kill_managed_openclaw() {
|
|
for _ in {1..10}; do
|
|
local pids=""
|
|
pids="$(managed_openclaw_process_pids)"
|
|
if [[ -z "${pids}" ]]; then
|
|
return 0
|
|
fi
|
|
while IFS= read -r pid; do
|
|
kill "${pid}" 2>/dev/null || true
|
|
done <<< "${pids}"
|
|
sleep 0.3
|
|
done
|
|
# The app can keep handling SIGTERM while shutting down. Escalate only for
|
|
# the two exact executables target-only mode has already classified as safe.
|
|
local remaining_pids=""
|
|
remaining_pids="$(managed_openclaw_process_pids)"
|
|
while IFS= read -r pid; do
|
|
[[ -n "${pid}" ]] || continue
|
|
kill -KILL "${pid}" 2>/dev/null || true
|
|
done <<< "${remaining_pids}"
|
|
sleep 0.3
|
|
[[ -z "$(managed_openclaw_process_pids)" ]]
|
|
}
|
|
|
|
stop_launch_agent() {
|
|
launchctl bootout gui/"$UID"/ai.openclaw.mac 2>/dev/null || true
|
|
}
|
|
|
|
# 1) Validate the process set selected by the requested mode. Target-only keeps
|
|
# the current managed app alive while the replacement builds and signs.
|
|
if [[ "$TARGET_ONLY" -eq 1 ]]; then
|
|
if ! managed_supervisors="$(managed_openclaw_supervisor_labels | sort -u | /usr/bin/paste -sd, -)"; then
|
|
fail "Unable to inspect loaded launchd jobs before target-only restart"
|
|
fi
|
|
if [[ -n "${managed_supervisors}" ]]; then
|
|
fail "Managed OpenClaw app is supervised by launchd job(s): ${managed_supervisors}; stop those jobs before a target-only restart"
|
|
fi
|
|
if [[ -n "$(foreign_openclaw_process_pids)" ]]; then
|
|
fail "Another OpenClaw app or test process is active; target-only restart deferred"
|
|
fi
|
|
log "==> Keeping managed OpenClaw running while the replacement builds"
|
|
else
|
|
stop_launch_agent
|
|
log "==> Killing existing OpenClaw instances"
|
|
if ! kill_all_openclaw; then
|
|
fail "OpenClaw instances did not exit after cleanup attempts"
|
|
fi
|
|
fi
|
|
|
|
# Bundle Gateway-hosted plugin assets.
|
|
run_step "bundle plugin assets" bash -c "cd '${ROOT_DIR}' && pnpm plugins:assets:build"
|
|
|
|
if [ "$AUTO_DETECT_SIGNING" -eq 1 ]; then
|
|
if check_signing_keys; then
|
|
log "==> Signing keys detected, will code sign"
|
|
SIGN=1
|
|
else
|
|
log "==> No signing keys found, will skip code signing (--no-sign)"
|
|
NO_SIGN=1
|
|
fi
|
|
fi
|
|
|
|
if [ "$NO_SIGN" -eq 1 ]; then
|
|
export ALLOW_ADHOC_SIGNING=1
|
|
export SIGN_IDENTITY="-"
|
|
mkdir -p "${HOME}/.openclaw"
|
|
run_step "disable launchagent writes" /usr/bin/touch "${LAUNCHAGENT_DISABLE_MARKER}"
|
|
elif [ "$SIGN" -eq 1 ]; then
|
|
if ! check_signing_keys; then
|
|
fail "No signing identity found. Use --no-sign or install a signing key."
|
|
fi
|
|
unset ALLOW_ADHOC_SIGNING
|
|
unset SIGN_IDENTITY
|
|
fi
|
|
|
|
# 3) Package and sign outside the live bundle. A failed package/sign operation
|
|
# must leave the currently running and on-disk app untouched.
|
|
run_step "package app" env \
|
|
SKIP_TSC="${SKIP_TSC:-1}" \
|
|
OPENCLAW_PACKAGE_APP_ROOT="${STAGED_APP_BUNDLE}" \
|
|
"${ROOT_DIR}/scripts/package-mac-app.sh"
|
|
run_step "verify packaged app" /usr/bin/codesign --verify --deep --strict "${STAGED_APP_BUNDLE}"
|
|
|
|
install_staged_app() {
|
|
local previous="${ROOT_DIR}/dist/.OpenClaw.app.previous-$$"
|
|
rm -rf "${previous}"
|
|
if [[ -d "${TARGET_APP_BUNDLE}" ]]; then
|
|
mv "${TARGET_APP_BUNDLE}" "${previous}"
|
|
fi
|
|
if ! mv "${STAGED_APP_BUNDLE}" "${TARGET_APP_BUNDLE}"; then
|
|
if [[ -d "${previous}" && ! -d "${TARGET_APP_BUNDLE}" ]]; then
|
|
mv "${previous}" "${TARGET_APP_BUNDLE}"
|
|
fi
|
|
return 1
|
|
fi
|
|
rm -rf "${previous}" "${STAGED_APP_DIR}"
|
|
}
|
|
|
|
choose_app_bundle() {
|
|
if [[ -n "${APP_BUNDLE}" ]]; then
|
|
canonicalize_app_bundle
|
|
return 0
|
|
fi
|
|
|
|
if [[ -d "${ROOT_DIR}/dist/OpenClaw.app" ]]; then
|
|
APP_BUNDLE="$(cd "${ROOT_DIR}/dist/OpenClaw.app" && pwd -P)"
|
|
if [[ ! -d "${APP_BUNDLE}/Contents/Frameworks/Sparkle.framework" ]]; then
|
|
fail "dist/OpenClaw.app missing Sparkle after packaging"
|
|
fi
|
|
return 0
|
|
fi
|
|
|
|
if [[ -d "/Applications/OpenClaw.app" ]]; then
|
|
APP_BUNDLE="$(cd "/Applications/OpenClaw.app" && pwd -P)"
|
|
return 0
|
|
fi
|
|
|
|
fail "App bundle not found. Set OPENCLAW_APP_BUNDLE to your installed OpenClaw.app"
|
|
}
|
|
|
|
# When signed, clear any previous launchagent override marker.
|
|
if [[ "$NO_SIGN" -ne 1 && "$ATTACH_ONLY" -ne 1 && -f "${LAUNCHAGENT_DISABLE_MARKER}" ]]; then
|
|
run_step "clear launchagent disable marker" /bin/rm -f "${LAUNCHAGENT_DISABLE_MARKER}"
|
|
fi
|
|
|
|
# When unsigned, ensure the gateway LaunchAgent targets the repo CLI (before the app launches).
|
|
# This reduces noisy "could not connect" errors during app startup.
|
|
if [ "$NO_SIGN" -eq 1 ] && [ "$ATTACH_ONLY" -ne 1 ]; then
|
|
run_step "install gateway launch agent (unsigned)" bash -c "cd '${ROOT_DIR}' && node openclaw.mjs daemon install --force --runtime node"
|
|
run_step "restart gateway daemon (unsigned)" bash -c "cd '${ROOT_DIR}' && node openclaw.mjs daemon restart"
|
|
if [[ "${GATEWAY_WAIT_SECONDS}" -gt 0 ]]; then
|
|
run_step "wait for gateway (unsigned)" sleep "${GATEWAY_WAIT_SECONDS}"
|
|
fi
|
|
GATEWAY_PORT="$(
|
|
node -e '
|
|
const fs = require("node:fs");
|
|
const path = require("node:path");
|
|
try {
|
|
const raw = fs.readFileSync(path.join(process.env.HOME, ".openclaw", "openclaw.json"), "utf8");
|
|
const cfg = JSON.parse(raw);
|
|
const port = cfg && cfg.gateway && typeof cfg.gateway.port === "number" ? cfg.gateway.port : 18789;
|
|
process.stdout.write(String(port));
|
|
} catch {
|
|
process.stdout.write("18789");
|
|
}
|
|
'
|
|
)"
|
|
run_step "verify gateway port ${GATEWAY_PORT} (unsigned)" verify_gateway_port_listening "${GATEWAY_PORT}"
|
|
fi
|
|
|
|
ATTACH_ONLY_ARGS=()
|
|
if [[ "$ATTACH_ONLY" -eq 1 ]]; then
|
|
ATTACH_ONLY_ARGS+=(--args --attach-only)
|
|
fi
|
|
|
|
if [[ "$TARGET_ONLY" -eq 1 ]]; then
|
|
if [[ -n "$(foreign_openclaw_process_pids)" ]]; then
|
|
fail "Another OpenClaw app or test process appeared during build; target-only restart deferred"
|
|
fi
|
|
log "==> Switching managed installed and exact target OpenClaw instances"
|
|
if ! kill_managed_openclaw; then
|
|
fail "Managed OpenClaw instances did not exit after cleanup attempts"
|
|
fi
|
|
fi
|
|
|
|
run_step "install packaged app" install_staged_app
|
|
choose_app_bundle
|
|
|
|
# 4) Launch the installed app in the foreground so the menu bar extra appears.
|
|
# LaunchServices can inherit a huge environment from this shell (secrets, prompt vars, etc.).
|
|
# That can cause launchd spawn failures and is undesirable for a GUI app anyway.
|
|
run_step "launch app" env -i \
|
|
HOME="${HOME}" \
|
|
USER="${USER:-$(id -un)}" \
|
|
LOGNAME="${LOGNAME:-$(id -un)}" \
|
|
TMPDIR="${TMPDIR:-/tmp}" \
|
|
PATH="/usr/bin:/bin:/usr/sbin:/sbin" \
|
|
LANG="${LANG:-en_US.UTF-8}" \
|
|
/usr/bin/open -n "${APP_BUNDLE}" ${ATTACH_ONLY_ARGS[@]:+"${ATTACH_ONLY_ARGS[@]}"}
|
|
|
|
# 5) Verify the app is alive.
|
|
sleep 1.5
|
|
if [[ -n "$(process_pids_matching "${APP_BUNDLE}/${APP_EXECUTABLE_RELATIVE_PATH}")" ]]; then
|
|
log "OK: OpenClaw is running."
|
|
else
|
|
fail "App exited immediately. Check ${LOG_PATH} or Console.app (User Reports)."
|
|
fi
|
|
|
|
if [ "$NO_SIGN" -eq 1 ] && [ "$ATTACH_ONLY" -ne 1 ]; then
|
|
run_step "show gateway launch agent args (unsigned)" bash -c "/usr/bin/plutil -p '${HOME}/Library/LaunchAgents/ai.openclaw.gateway.plist' | head -n 40 || true"
|
|
fi
|