cleanup
This commit is contained in:
@@ -2,20 +2,21 @@
|
|||||||
# No `-e` (we tolerate benign non-zero returns); keep -Euo
|
# No `-e` (we tolerate benign non-zero returns); keep -Euo
|
||||||
set -Euo pipefail
|
set -Euo pipefail
|
||||||
|
|
||||||
# ---- logging to a writable place (DMG is read-only) ----
|
|
||||||
LOG_DIR="/tmp"
|
LOG_DIR="/tmp"
|
||||||
LOG_FILE="${LOG_DIR}/Install-$(date +%Y%m%d-%H%M%S).log"
|
LOG_FILE="${LOG_DIR}/Install-$(date +%Y%m%d-%H%M%S).log"
|
||||||
exec > >(tee -a "$LOG_FILE") 2>&1
|
exec > >(tee -a "$LOG_FILE") 2>&1
|
||||||
echo "Logging to: $LOG_FILE"
|
echo "Logging to: $LOG_FILE"
|
||||||
|
|
||||||
TARGET_DIR="/Applications"
|
TARGET_DIR="/Applications"
|
||||||
APP_NAME="repertory.app" # final install path: /Applications/repertory.app
|
APP_NAME="repertory.app"
|
||||||
|
|
||||||
# Embedded at pack time (from CFBundleIdentifier prefix)
|
|
||||||
LABEL_PREFIX="__LABEL_PREFIX__"
|
LABEL_PREFIX="__LABEL_PREFIX__"
|
||||||
|
UI_LABEL="${LABEL_PREFIX}.ui"
|
||||||
|
|
||||||
# make staged visible to trap cleanup
|
|
||||||
staged=""
|
staged=""
|
||||||
|
backup=""
|
||||||
|
snapfile=""
|
||||||
|
skip_ui_launch=0
|
||||||
|
|
||||||
log() { printf "[%(%H:%M:%S)T] %s\n" -1 "$*"; }
|
log() { printf "[%(%H:%M:%S)T] %s\n" -1 "$*"; }
|
||||||
warn() { log "WARN: $*"; }
|
warn() { log "WARN: $*"; }
|
||||||
@@ -26,14 +27,13 @@ die() {
|
|||||||
|
|
||||||
here="$(cd "$(dirname "$0")" && pwd)"
|
here="$(cd "$(dirname "$0")" && pwd)"
|
||||||
|
|
||||||
# Find the source app anywhere under the DMG root (supports hidden .payload/)
|
|
||||||
src_app="$(/usr/bin/find "$here" -type d -name "$APP_NAME" -print -quit 2>/dev/null || true)"
|
src_app="$(/usr/bin/find "$here" -type d -name "$APP_NAME" -print -quit 2>/dev/null || true)"
|
||||||
if [[ -z "${src_app:-}" ]]; then
|
if [[ -z "${src_app:-}" ]]; then
|
||||||
src_app="$(/usr/bin/find "$here" -type d -name "*.app" -print -quit 2>/dev/null || true)"
|
src_app="$(/usr/bin/find "$here" -type d -name "*.app" -print -quit 2>/dev/null || true)"
|
||||||
fi
|
fi
|
||||||
[[ -z "${src_app:-}" ]] && die "No .app found on this disk image."
|
[[ -z "${src_app:-}" ]] && die "No .app found on this disk image."
|
||||||
|
|
||||||
app_basename="$(basename "$src_app")" # repertory.app
|
app_basename="$(basename "$src_app")"
|
||||||
dest_app="${TARGET_DIR}/${APP_NAME}"
|
dest_app="${TARGET_DIR}/${APP_NAME}"
|
||||||
|
|
||||||
bundle_id_of() { /usr/bin/defaults read "$1/Contents/Info" CFBundleIdentifier 2>/dev/null || true; }
|
bundle_id_of() { /usr/bin/defaults read "$1/Contents/Info" CFBundleIdentifier 2>/dev/null || true; }
|
||||||
@@ -47,7 +47,6 @@ bundle_build_of() {
|
|||||||
/usr/bin/defaults read "$1/Contents/Info" CFBundleVersion 2>/dev/null || echo "(unknown)"
|
/usr/bin/defaults read "$1/Contents/Info" CFBundleVersion 2>/dev/null || echo "(unknown)"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Require /Applications; prompt for sudo if needed; abort if cannot elevate
|
|
||||||
USE_SUDO=0
|
USE_SUDO=0
|
||||||
SUDO=""
|
SUDO=""
|
||||||
ensure_target_writable() {
|
ensure_target_writable() {
|
||||||
@@ -67,7 +66,6 @@ ensure_target_writable() {
|
|||||||
die "Cannot write to ${TARGET_DIR} and sudo is unavailable."
|
die "Cannot write to ${TARGET_DIR} and sudo is unavailable."
|
||||||
}
|
}
|
||||||
|
|
||||||
# ----- STRICT LABEL PREFIX GATE (fail if invalid) -----
|
|
||||||
_is_valid_label_prefix() {
|
_is_valid_label_prefix() {
|
||||||
local p="${1:-}"
|
local p="${1:-}"
|
||||||
[[ -n "$p" ]] && [[ "$p" != "__LABEL_PREFIX__" ]] && [[ "$p" =~ ^[A-Za-z0-9._-]+$ ]] && [[ "$p" == *.* ]]
|
[[ -n "$p" ]] && [[ "$p" != "__LABEL_PREFIX__" ]] && [[ "$p" =~ ^[A-Za-z0-9._-]+$ ]] && [[ "$p" == *.* ]]
|
||||||
@@ -76,7 +74,6 @@ if ! _is_valid_label_prefix "${LABEL_PREFIX:-}"; then
|
|||||||
die "Invalid LABEL_PREFIX in installer (value: \"${LABEL_PREFIX:-}\"). Rebuild the DMG so the installer contains a valid reverse-DNS prefix."
|
die "Invalid LABEL_PREFIX in installer (value: \"${LABEL_PREFIX:-}\"). Rebuild the DMG so the installer contains a valid reverse-DNS prefix."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ----- LaunchServices helpers -----
|
|
||||||
ls_prune_bundle_id() {
|
ls_prune_bundle_id() {
|
||||||
local bundle_id="$1" keep_path="$2"
|
local bundle_id="$1" keep_path="$2"
|
||||||
[[ -z "$bundle_id" ]] && return 0
|
[[ -z "$bundle_id" ]] && return 0
|
||||||
@@ -100,7 +97,6 @@ ls_register_exact() {
|
|||||||
/System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister -f "$app_path" >/dev/null 2>&1 || true
|
/System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister -f "$app_path" >/dev/null 2>&1 || true
|
||||||
}
|
}
|
||||||
|
|
||||||
# ----- FUSE unmount (no process killing here) -----
|
|
||||||
is_mounted() { /sbin/mount | /usr/bin/awk '{print $3}' | /usr/bin/grep -Fx "${1:-}" >/dev/null 2>&1; }
|
is_mounted() { /sbin/mount | /usr/bin/awk '{print $3}' | /usr/bin/grep -Fx "${1:-}" >/dev/null 2>&1; }
|
||||||
_list_repertory_fuse_mounts() { /sbin/mount | /usr/bin/grep -Ei 'macfuse|osxfuse' | /usr/bin/awk '{print $3}' | /usr/bin/grep -i "repertory" || true; }
|
_list_repertory_fuse_mounts() { /sbin/mount | /usr/bin/grep -Ei 'macfuse|osxfuse' | /usr/bin/awk '{print $3}' | /usr/bin/grep -i "repertory" || true; }
|
||||||
_unmount_one() {
|
_unmount_one() {
|
||||||
@@ -134,8 +130,25 @@ unmount_existing_repertory_volumes_first() {
|
|||||||
return $failed
|
return $failed
|
||||||
}
|
}
|
||||||
|
|
||||||
# ----- user LaunchAgents (by LABEL_PREFIX only) -----
|
get_plist_label() {
|
||||||
get_plist_label() { /usr/bin/defaults read "$1" Label 2>/dev/null || /usr/libexec/PlistBuddy -c "Print :Label" "$1" 2>/dev/null || basename "$1" .plist; }
|
/usr/bin/defaults read "$1" Label 2>/dev/null || /usr/libexec/PlistBuddy -c "Print :Label" "$1" 2>/dev/null || basename "$1" .plist
|
||||||
|
}
|
||||||
|
|
||||||
|
snapshot_launchagents_user() {
|
||||||
|
local user_agents="$HOME/Library/LaunchAgents"
|
||||||
|
snapfile="$(/usr/bin/mktemp "/tmp/repertory_launchagents.XXXXXX")"
|
||||||
|
if [[ -d "$user_agents" ]]; then
|
||||||
|
/usr/bin/find "$user_agents" -maxdepth 1 -type f -name "${LABEL_PREFIX}"'*.plist' -print 2>/dev/null |
|
||||||
|
while IFS= read -r plist; do
|
||||||
|
[[ -z "$plist" ]] && continue
|
||||||
|
local label
|
||||||
|
label="$(get_plist_label "$plist")"
|
||||||
|
[[ -n "$label" && "$label" == "${LABEL_PREFIX}"* ]] || continue
|
||||||
|
printf "%s\t%s\n" "$plist" "$label" >>"$snapfile"
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
unload_launchd_helpers_user() {
|
unload_launchd_helpers_user() {
|
||||||
local uid
|
local uid
|
||||||
uid="$(id -u)"
|
uid="$(id -u)"
|
||||||
@@ -154,6 +167,7 @@ unload_launchd_helpers_user() {
|
|||||||
/bin/launchctl bootout "gui/${uid}" "$label" 2>/dev/null ||
|
/bin/launchctl bootout "gui/${uid}" "$label" 2>/dev/null ||
|
||||||
/bin/launchctl remove "$label" 2>/dev/null || true
|
/bin/launchctl remove "$label" 2>/dev/null || true
|
||||||
done < <(/usr/bin/find "$user_agents" -maxdepth 1 -type f -name "${LABEL_PREFIX}"'*.plist' -print 2>/dev/null)
|
done < <(/usr/bin/find "$user_agents" -maxdepth 1 -type f -name "${LABEL_PREFIX}"'*.plist' -print 2>/dev/null)
|
||||||
|
|
||||||
/bin/launchctl list 2>/dev/null | /usr/bin/awk -v pre="$LABEL_PREFIX" 'NF>=3 && index($3, pre)==1 {print $3}' |
|
/bin/launchctl list 2>/dev/null | /usr/bin/awk -v pre="$LABEL_PREFIX" 'NF>=3 && index($3, pre)==1 {print $3}' |
|
||||||
while read -r lbl; do
|
while read -r lbl; do
|
||||||
[[ -z "$lbl" ]] && continue
|
[[ -z "$lbl" ]] && continue
|
||||||
@@ -161,20 +175,33 @@ unload_launchd_helpers_user() {
|
|||||||
/bin/launchctl bootout "gui/${uid}" "$lbl" 2>/dev/null || /bin/launchctl remove "$lbl" 2>/dev/null || true
|
/bin/launchctl bootout "gui/${uid}" "$lbl" 2>/dev/null || /bin/launchctl remove "$lbl" 2>/dev/null || true
|
||||||
done
|
done
|
||||||
}
|
}
|
||||||
remove_launchd_plists_user() {
|
|
||||||
local user_agents="$HOME/Library/LaunchAgents"
|
restart_launchagents_from_snapshot() {
|
||||||
[[ -d "$user_agents" ]] || return 0
|
[[ -n "${snapfile:-}" && -f "${snapfile}" ]] || return 0
|
||||||
while IFS= read -r plist; do
|
local uid
|
||||||
[[ -z "$plist" ]] && continue
|
uid="$(id -u)"
|
||||||
local base
|
local count=0
|
||||||
base="$(basename "$plist")"
|
local ui_seen=0
|
||||||
[[ "$base" == "${LABEL_PREFIX}"* ]] || continue
|
while IFS=$'\t' read -r plist label; do
|
||||||
log "Removing LaunchAgent plist: $plist"
|
[[ -z "$plist" || -z "$label" ]] && continue
|
||||||
/bin/rm -f "$plist" 2>/dev/null || warn "Failed to remove $plist"
|
[[ -f "$plist" ]] || continue
|
||||||
done < <(/usr/bin/find "$user_agents" -maxdepth 1 -type f -name "${LABEL_PREFIX}"'*.plist' -print 2>/dev/null)
|
log "Re-starting LaunchAgent: ${label}"
|
||||||
|
/bin/launchctl bootstrap "gui/${uid}" "$plist" 2>/dev/null || true
|
||||||
|
/bin/launchctl kickstart -k "gui/${uid}/${label}" 2>/dev/null || true
|
||||||
|
((count++)) || true
|
||||||
|
if [[ "$label" == "$UI_LABEL" ]]; then ui_seen=1; fi
|
||||||
|
done <"$snapfile"
|
||||||
|
log "Re-started ${count} LaunchAgent(s) with prefix ${LABEL_PREFIX}." || true
|
||||||
|
|
||||||
|
if ((ui_seen)); then
|
||||||
|
sleep 0.3
|
||||||
|
if /bin/launchctl list | /usr/bin/awk '{print $3}' | /usr/bin/grep -Fxq "$UI_LABEL"; then
|
||||||
|
log "UI LaunchAgent (${UI_LABEL}) active after restart; skipping manual UI launch."
|
||||||
|
skip_ui_launch=1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# ----- quarantine helper -----
|
|
||||||
remove_quarantine() {
|
remove_quarantine() {
|
||||||
local path="${1:-}"
|
local path="${1:-}"
|
||||||
if [[ "${USE_SUDO:-0}" == "1" ]]; then
|
if [[ "${USE_SUDO:-0}" == "1" ]]; then
|
||||||
@@ -184,7 +211,6 @@ remove_quarantine() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# ----- optional diagnostics -----
|
|
||||||
dump_quick_diagnostics() {
|
dump_quick_diagnostics() {
|
||||||
local dest_app="$1" exec_name="$2"
|
local dest_app="$1" exec_name="$2"
|
||||||
warn "codesign verify:"
|
warn "codesign verify:"
|
||||||
@@ -202,18 +228,12 @@ dump_quick_diagnostics() {
|
|||||||
/usr/bin/log show --style syslog --last 2m --predicate "process == \"${exec_name}\"" 2>/dev/null | /usr/bin/tail -n 120 || true
|
/usr/bin/log show --style syslog --last 2m --predicate "process == \"${exec_name}\"" 2>/dev/null | /usr/bin/tail -n 120 || true
|
||||||
}
|
}
|
||||||
|
|
||||||
# ----- process helpers -----
|
_current_user() { id -un; }
|
||||||
_current_user() { id -un; } # username (ps prints 'user' as a name)
|
|
||||||
|
|
||||||
# Faster/more robust "alive" check than parsing `ps`.
|
|
||||||
ps_alive() {
|
ps_alive() {
|
||||||
local p="${1:-}"
|
local p="${1:-}"
|
||||||
[[ -n "$p" ]] && /bin/kill -0 "$p" 2>/dev/null
|
[[ -n "$p" ]] && /bin/kill -0 "$p" 2>/dev/null
|
||||||
}
|
}
|
||||||
|
|
||||||
# Newest PID for current user/exec, started >= since_epoch, where command line is:
|
|
||||||
# - exactly the binary (no args), OR
|
|
||||||
# - contains -ui or --ui (possibly with other args)
|
|
||||||
find_latest_repertory_pid() {
|
find_latest_repertory_pid() {
|
||||||
local since_epoch="$1" exec_name="$2" user="$(_current_user)"
|
local since_epoch="$1" exec_name="$2" user="$(_current_user)"
|
||||||
/bin/ps ax -o pid=,lstart=,user=,command= |
|
/bin/ps ax -o pid=,lstart=,user=,command= |
|
||||||
@@ -222,15 +242,12 @@ find_latest_repertory_pid() {
|
|||||||
pid=$1; lstart=$2" "$3" "$4" "$5" "$6; usr=$7;
|
pid=$1; lstart=$2" "$3" "$4" "$5" "$6; usr=$7;
|
||||||
cmd=""; for (i=8;i<=NF;i++) { if (i>8) cmd=cmd" "; cmd=cmd $i }
|
cmd=""; for (i=8;i<=NF;i++) { if (i>8) cmd=cmd" "; cmd=cmd $i }
|
||||||
if (usr != u) next;
|
if (usr != u) next;
|
||||||
|
|
||||||
n=split(cmd, tok, /[[:space:]]+/); if (n < 1) next;
|
n=split(cmd, tok, /[[:space:]]+/); if (n < 1) next;
|
||||||
argv0 = tok[1]; m=split(argv0, parts, "/"); base = parts[m];
|
argv0 = tok[1]; m=split(argv0, parts, "/"); base = parts[m];
|
||||||
if (base != en) next;
|
if (base != en) next;
|
||||||
|
|
||||||
has_ui = (cmd ~ /(^|[[:space:]])-{1,2}ui([[:space:]]|$)/) ? 1 : 0;
|
has_ui = (cmd ~ /(^|[[:space:]])-{1,2}ui([[:space:]]|$)/) ? 1 : 0;
|
||||||
no_args = (n == 1) ? 1 : 0;
|
no_args = (n == 1) ? 1 : 0;
|
||||||
if (!has_ui && !no_args) next;
|
if (!has_ui && !no_args) next;
|
||||||
|
|
||||||
printf "%s\t%s\n", pid, lstart
|
printf "%s\t%s\n", pid, lstart
|
||||||
}' |
|
}' |
|
||||||
while IFS=$'\t' read -r pid lstart; do
|
while IFS=$'\t' read -r pid lstart; do
|
||||||
@@ -252,7 +269,6 @@ kill_repertory_processes() {
|
|||||||
/usr/bin/pkill -KILL -x "$exec_name" >/dev/null 2>&1 || true
|
/usr/bin/pkill -KILL -x "$exec_name" >/dev/null 2>&1 || true
|
||||||
}
|
}
|
||||||
|
|
||||||
# ----- visibility helper -----
|
|
||||||
unhide_path() {
|
unhide_path() {
|
||||||
local path="$1"
|
local path="$1"
|
||||||
/usr/bin/chflags -R nohidden "$path" 2>/dev/null || true
|
/usr/bin/chflags -R nohidden "$path" 2>/dev/null || true
|
||||||
@@ -261,43 +277,27 @@ unhide_path() {
|
|||||||
|
|
||||||
now_epoch() { /bin/date +%s; }
|
now_epoch() { /bin/date +%s; }
|
||||||
|
|
||||||
# --- main ---
|
stage_new_app() {
|
||||||
main() {
|
|
||||||
ensure_target_writable
|
|
||||||
|
|
||||||
local exec_name
|
|
||||||
exec_name="$(bundle_exec_of "$src_app")"
|
|
||||||
|
|
||||||
# 1) Unmount FUSE mounts first
|
|
||||||
unmount_existing_repertory_volumes_first || warn "One or more FUSE mounts resisted unmount; continuing."
|
|
||||||
|
|
||||||
# 2) Stop/remove user LaunchAgents
|
|
||||||
unload_launchd_helpers_user
|
|
||||||
remove_launchd_plists_user
|
|
||||||
|
|
||||||
# 3) Kill any remaining repertory processes
|
|
||||||
kill_repertory_processes "$exec_name"
|
|
||||||
|
|
||||||
# ---- Stage → atomic swap (keep backup until success confirmed) ----
|
|
||||||
staged="${dest_app}.new-$$"
|
staged="${dest_app}.new-$$"
|
||||||
log "Staging new app → $staged"
|
log "Staging new app → $staged"
|
||||||
$SUDO /usr/bin/ditto "$src_app" "$staged" || die "ditto to stage failed"
|
$SUDO /usr/bin/ditto "$src_app" "$staged" || die "ditto to stage failed"
|
||||||
remove_quarantine "$staged"
|
remove_quarantine "$staged"
|
||||||
|
}
|
||||||
|
|
||||||
# Sanity
|
validate_staged_app() {
|
||||||
if [[ ! -f "$staged/Contents/Info.plist" ]]; then
|
[[ -f "$staged/Contents/Info.plist" ]] || {
|
||||||
$SUDO /bin/rm -rf "$staged"
|
$SUDO /bin/rm -rf "$staged"
|
||||||
die "staged app missing Info.plist"
|
die "staged app missing Info.plist"
|
||||||
fi
|
}
|
||||||
local exe_name_staged
|
local exe_name_staged
|
||||||
exe_name_staged="$(/usr/bin/defaults read "$staged/Contents/Info" CFBundleExecutable 2>/dev/null || echo "${app_basename%.app}")"
|
exe_name_staged="$(/usr/bin/defaults read "$staged/Contents/Info" CFBundleExecutable 2>/dev/null || echo "${app_basename%.app}")"
|
||||||
if [[ ! -x "$staged/Contents/MacOS/$exe_name_staged" ]]; then
|
[[ -x "$staged/Contents/MacOS/$exe_name_staged" ]] || {
|
||||||
$SUDO /bin/rm -rf "$staged"
|
$SUDO /bin/rm -rf "$staged"
|
||||||
die "staged app missing main executable"
|
die "staged app missing main executable"
|
||||||
fi
|
}
|
||||||
|
}
|
||||||
|
|
||||||
# Activate (keep backup until success)
|
activate_staged_app() {
|
||||||
local backup=""
|
|
||||||
if [[ -d "$dest_app" ]]; then
|
if [[ -d "$dest_app" ]]; then
|
||||||
backup="${dest_app}.$(date +%Y%m%d%H%M%S).bak"
|
backup="${dest_app}.$(date +%Y%m%d%H%M%S).bak"
|
||||||
log "Moving existing app to backup: $backup"
|
log "Moving existing app to backup: $backup"
|
||||||
@@ -313,13 +313,14 @@ main() {
|
|||||||
$SUDO /bin/rm -rf "$staged" || true
|
$SUDO /bin/rm -rf "$staged" || true
|
||||||
die "install activation failed"
|
die "install activation failed"
|
||||||
fi
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
post_activate_housekeeping() {
|
||||||
log "Clearing quarantine on installed app…"
|
log "Clearing quarantine on installed app…"
|
||||||
remove_quarantine "$dest_app"
|
remove_quarantine "$dest_app"
|
||||||
log "Clearing hidden flags on installed app…"
|
log "Clearing hidden flags on installed app…"
|
||||||
unhide_path "$dest_app"
|
unhide_path "$dest_app"
|
||||||
|
|
||||||
# LS refresh/prune
|
|
||||||
local LSREG="/System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister"
|
local LSREG="/System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister"
|
||||||
[[ -x "$LSREG" ]] && "$LSREG" -f "$dest_app" >/dev/null 2>&1 || true
|
[[ -x "$LSREG" ]] && "$LSREG" -f "$dest_app" >/dev/null 2>&1 || true
|
||||||
local BID
|
local BID
|
||||||
@@ -328,23 +329,23 @@ main() {
|
|||||||
ls_register_exact "$dest_app"
|
ls_register_exact "$dest_app"
|
||||||
|
|
||||||
log "Installed ${app_basename}: version=$(bundle_version_of "$dest_app") build=$(bundle_build_of "$dest_app")"
|
log "Installed ${app_basename}: version=$(bundle_version_of "$dest_app") build=$(bundle_build_of "$dest_app")"
|
||||||
|
}
|
||||||
|
|
||||||
|
launch_ui() {
|
||||||
|
/usr/bin/open -n "$dest_app" || warn "open -n by path failed; not falling back to -b to avoid launching a stale copy."
|
||||||
|
}
|
||||||
|
|
||||||
|
wait_for_ui_stability() {
|
||||||
|
local exec_name_now="$1"
|
||||||
|
|
||||||
|
local PID_DETECT_TIMEOUT_SEC="${PID_DETECT_TIMEOUT_SEC:-2}"
|
||||||
|
local POLL_INTERVAL="${POLL_INTERVAL:-0.10}"
|
||||||
|
local STABILITY_WINDOW_SEC="${STABILITY_WINDOW_SEC:-8}"
|
||||||
|
local GAP_TOLERANCE_SEC="${GAP_TOLERANCE_SEC:-1}"
|
||||||
|
|
||||||
# ---- Launch ONLY by full path (no -b fallback) ----
|
|
||||||
log "Launching the new app…"
|
|
||||||
local launch_epoch
|
local launch_epoch
|
||||||
launch_epoch="$(now_epoch)"
|
launch_epoch="$(now_epoch)"
|
||||||
local exec_name_now
|
|
||||||
exec_name_now="$(/usr/bin/defaults read "$dest_app/Contents/Info" CFBundleExecutable 2>/dev/null || echo "${app_basename%.app}")"
|
|
||||||
|
|
||||||
# Tunables
|
|
||||||
local PID_DETECT_TIMEOUT_SEC="${PID_DETECT_TIMEOUT_SEC:-2}"
|
|
||||||
local POLL_INTERVAL="${POLL_INTERVAL:-0.10}" # seconds; fractional OK
|
|
||||||
local STABILITY_WINDOW_SEC="${STABILITY_WINDOW_SEC:-8}"
|
|
||||||
local GAP_TOLERANCE_SEC="${GAP_TOLERANCE_SEC:-1}" # allow brief handoff gaps
|
|
||||||
|
|
||||||
/usr/bin/open -n "$dest_app" || warn "open -n by path failed; not falling back to -b to avoid launching a stale copy."
|
|
||||||
|
|
||||||
# Detect initial UI pid (don't fail if we miss the very first parent)
|
|
||||||
local cur_pid=""
|
local cur_pid=""
|
||||||
for ((i = 0; i < PID_DETECT_TIMEOUT_SEC * 10; i++)); do
|
for ((i = 0; i < PID_DETECT_TIMEOUT_SEC * 10; i++)); do
|
||||||
cur_pid="$(find_latest_repertory_pid "$launch_epoch" "$exec_name_now" || true)"
|
cur_pid="$(find_latest_repertory_pid "$launch_epoch" "$exec_name_now" || true)"
|
||||||
@@ -354,36 +355,23 @@ main() {
|
|||||||
if [[ -z "$cur_pid" ]]; then
|
if [[ -z "$cur_pid" ]]; then
|
||||||
warn "No UI process observed for ${exec_name_now} after launch."
|
warn "No UI process observed for ${exec_name_now} after launch."
|
||||||
dump_quick_diagnostics "$dest_app" "$exec_name_now" || true
|
dump_quick_diagnostics "$dest_app" "$exec_name_now" || true
|
||||||
if [[ -n "$backup" && -d "$backup" ]]; then
|
return 1
|
||||||
warn "Rolling back to previous app…"
|
|
||||||
$SUDO /bin/rm -rf "$dest_app" || true
|
|
||||||
$SUDO /bin/mv "$backup" "$dest_app" || true
|
|
||||||
/usr/bin/open -n "$dest_app" || true
|
|
||||||
fi
|
|
||||||
log "Done."
|
|
||||||
return 0
|
|
||||||
fi
|
fi
|
||||||
log "Tracking UI pid $cur_pid."
|
log "Tracking UI pid $cur_pid."
|
||||||
|
ps_alive "$cur_pid" || cur_pid=""
|
||||||
|
|
||||||
# === Stability shepherd: succeed if *any* qualifying UI pid is present
|
|
||||||
# for the entire window, with small tolerance for handoffs.
|
|
||||||
local start_ts
|
local start_ts
|
||||||
start_ts="$(now_epoch)"
|
start_ts="$(now_epoch)"
|
||||||
local last_seen_ts="$start_ts"
|
local last_seen_ts="$start_ts"
|
||||||
local last_logged_pid="$cur_pid"
|
local last_logged_pid="$cur_pid"
|
||||||
|
|
||||||
# Ensure the first observed PID is alive; if not, we still allow a quick adopt.
|
|
||||||
ps_alive "$cur_pid" || cur_pid=""
|
|
||||||
|
|
||||||
while :; do
|
while :; do
|
||||||
local newest
|
local newest
|
||||||
newest="$(find_latest_repertory_pid "$launch_epoch" "$exec_name_now" || true)"
|
newest="$(find_latest_repertory_pid "$launch_epoch" "$exec_name_now" || true)"
|
||||||
|
|
||||||
if [[ -n "$newest" ]] && ps_alive "$newest"; then
|
if [[ -n "$newest" ]] && ps_alive "$newest"; then
|
||||||
# update last-seen
|
|
||||||
last_seen_ts="$(now_epoch)"
|
last_seen_ts="$(now_epoch)"
|
||||||
if [[ "$newest" != "$cur_pid" ]]; then
|
if [[ "$newest" != "$cur_pid" ]]; then
|
||||||
# only log when PID actually changes; never print "(was )"
|
|
||||||
if [[ -n "$cur_pid" && "$newest" != "$last_logged_pid" ]]; then
|
if [[ -n "$cur_pid" && "$newest" != "$last_logged_pid" ]]; then
|
||||||
log "Adopting successor UI pid $newest (was $cur_pid)."
|
log "Adopting successor UI pid $newest (was $cur_pid)."
|
||||||
elif [[ -z "$cur_pid" ]]; then
|
elif [[ -z "$cur_pid" ]]; then
|
||||||
@@ -394,7 +382,6 @@ main() {
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# success if window elapsed and we've seen a live UI within tolerance
|
|
||||||
local now
|
local now
|
||||||
now="$(now_epoch)"
|
now="$(now_epoch)"
|
||||||
local elapsed=$((now - start_ts))
|
local elapsed=$((now - start_ts))
|
||||||
@@ -402,20 +389,64 @@ main() {
|
|||||||
if ((elapsed >= STABILITY_WINDOW_SEC)); then
|
if ((elapsed >= STABILITY_WINDOW_SEC)); then
|
||||||
if ((gap <= GAP_TOLERANCE_SEC)); then
|
if ((gap <= GAP_TOLERANCE_SEC)); then
|
||||||
log "UI verified alive across stability window (~${STABILITY_WINDOW_SEC}s)."
|
log "UI verified alive across stability window (~${STABILITY_WINDOW_SEC}s)."
|
||||||
if [[ -n "$backup" && -d "$backup" ]]; then
|
|
||||||
log "Removing backup: $backup"
|
|
||||||
$SUDO /bin/rm -rf "$backup" || warn "Could not remove backup (safe to delete manually): $backup"
|
|
||||||
fi
|
|
||||||
log "Done."
|
|
||||||
return 0
|
return 0
|
||||||
else
|
else
|
||||||
break
|
break
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
sleep "$POLL_INTERVAL"
|
sleep "$POLL_INTERVAL"
|
||||||
done
|
done
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
main() {
|
||||||
|
ensure_target_writable
|
||||||
|
|
||||||
|
local exec_name
|
||||||
|
exec_name="$(bundle_exec_of "$src_app")"
|
||||||
|
|
||||||
|
snapshot_launchagents_user
|
||||||
|
|
||||||
|
unmount_existing_repertory_volumes_first || warn "One or more FUSE mounts resisted unmount; continuing."
|
||||||
|
|
||||||
|
unload_launchd_helpers_user
|
||||||
|
|
||||||
|
kill_repertory_processes "$exec_name"
|
||||||
|
|
||||||
|
stage_new_app
|
||||||
|
|
||||||
|
validate_staged_app
|
||||||
|
|
||||||
|
activate_staged_app
|
||||||
|
|
||||||
|
post_activate_housekeeping
|
||||||
|
|
||||||
|
restart_launchagents_from_snapshot
|
||||||
|
|
||||||
|
if ((skip_ui_launch)); then
|
||||||
|
[[ -n "$backup" && -d "$backup" ]] && {
|
||||||
|
log "Removing backup: $backup"
|
||||||
|
$SUDO /bin/rm -rf "$backup" || warn "Could not remove backup (safe to delete manually): $backup"
|
||||||
|
}
|
||||||
|
log "Done."
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "Launching the new app…"
|
||||||
|
launch_ui
|
||||||
|
local exec_name_now
|
||||||
|
exec_name_now="$(/usr/bin/defaults read "$dest_app/Contents/Info" CFBundleExecutable 2>/dev/null || echo "${app_basename%.app}")"
|
||||||
|
|
||||||
|
if wait_for_ui_stability "$exec_name_now"; then
|
||||||
|
[[ -n "$backup" && -d "$backup" ]] && {
|
||||||
|
log "Removing backup: $backup"
|
||||||
|
$SUDO /bin/rm -rf "$backup" || warn "Could not remove backup (safe to delete manually): $backup"
|
||||||
|
}
|
||||||
|
log "Done."
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
warn "No surviving UI process for ${exec_name_now} by end of verification."
|
warn "No surviving UI process for ${exec_name_now} by end of verification."
|
||||||
dump_quick_diagnostics "$dest_app" "$exec_name_now" || true
|
dump_quick_diagnostics "$dest_app" "$exec_name_now" || true
|
||||||
if [[ -n "$backup" && -d "$backup" ]]; then
|
if [[ -n "$backup" && -d "$backup" ]]; then
|
||||||
@@ -436,6 +467,9 @@ cleanup_staged() {
|
|||||||
/bin/rm -rf "${staged}" 2>/dev/null || true
|
/bin/rm -rf "${staged}" 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
if [[ -n "${snapfile:-}" && -f "${snapfile}" ]]; then
|
||||||
|
/bin/rm -f "${snapfile}" 2>/dev/null || true
|
||||||
|
fi
|
||||||
}
|
}
|
||||||
trap 'rc=$?; cleanup_staged; if (( rc != 0 )); then echo "Installer failed with code $rc. See $LOG_FILE"; fi' EXIT
|
trap 'rc=$?; cleanup_staged; if (( rc != 0 )); then echo "Installer failed with code $rc. See $LOG_FILE"; fi' EXIT
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user