mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-09-10 02:25:56 +00:00
Supervise launchd dev runtime through hot-dev-bg
This commit is contained in:
@@ -37,6 +37,9 @@ server-side update execution surfaces.
|
||||
15. `scripts/dev-check.sh`
|
||||
16. `scripts/toggle-mock.sh`
|
||||
17. `scripts/clean-mock-alerts.sh`
|
||||
18. `scripts/dev-launchd-setup.sh`
|
||||
19. `scripts/dev-launchd-wrapper.sh`
|
||||
20. `scripts/com.pulse.hot-dev.plist.template`
|
||||
|
||||
## Shared Boundaries
|
||||
|
||||
@@ -51,7 +54,7 @@ server-side update execution surfaces.
|
||||
2. Add or change release-build metadata injection, release artifact assembly, or governed promotion metadata resolution through `scripts/build-release.sh`, `scripts/release_ldflags.sh`, and `scripts/release_control/resolve_release_promotion.py`, plus the container Dockerfile and governed release workflows that consume those same contracts
|
||||
3. Add or change shell installer, Windows installer, container-agent installer, or auto-update script behavior through `scripts/install.sh`, `scripts/install.ps1`, `scripts/install-container-agent.sh`, and `scripts/pulse-auto-update.sh`
|
||||
4. Add or change server update transport through `internal/api/updates.go` and `frontend-modern/src/api/updates.ts`
|
||||
5. Add or change local dev-runtime orchestration, managed ownership, browser-runtime proof wiring, frontend/backend coherence diagnostics, canonical developer entry wrappers, or dev-runtime helper control surfaces through `scripts/hot-dev.sh`, `scripts/hot-dev-bg.sh`, `tests/integration/scripts/managed-dev-runtime.mjs`, `package.json`, `frontend-modern/package.json`, `scripts/dev-check.sh`, `scripts/toggle-mock.sh`, and `scripts/clean-mock-alerts.sh`
|
||||
5. Add or change local dev-runtime orchestration, managed ownership, browser-runtime proof wiring, frontend/backend coherence diagnostics, canonical developer entry wrappers, or dev-runtime helper control surfaces through `scripts/hot-dev.sh`, `scripts/hot-dev-bg.sh`, `tests/integration/scripts/managed-dev-runtime.mjs`, `package.json`, `frontend-modern/package.json`, `scripts/dev-check.sh`, `scripts/toggle-mock.sh`, `scripts/clean-mock-alerts.sh`, `scripts/dev-launchd-setup.sh`, `scripts/dev-launchd-wrapper.sh`, and `scripts/com.pulse.hot-dev.plist.template`
|
||||
|
||||
## Forbidden Paths
|
||||
|
||||
@@ -196,6 +199,12 @@ Makefile targets, `scripts/toggle-mock.sh`, and `scripts/clean-mock-alerts.sh` m
|
||||
route through the managed runtime control plane when they are operating on the
|
||||
local dev stack, instead of resurrecting lane-local `hot-dev.sh` or raw Vite
|
||||
process management through separate shell folklore.
|
||||
That same rule now extends to the macOS auto-start surface. The launchd helper
|
||||
may not boot a separate legacy foreground runtime beside the managed dev stack:
|
||||
`scripts/dev-launchd-wrapper.sh`, `scripts/dev-launchd-setup.sh`, and the
|
||||
generated `com.pulse.hot-dev` LaunchAgent template must supervise the same
|
||||
managed `hot-dev-bg` control plane, so login-time auto-start, crash restart,
|
||||
and takeover diagnostics all operate on one runtime model.
|
||||
That shared `scripts/install.sh` boundary must also keep one canonical service
|
||||
argument builder for the runtime flags it persists. Token-bearing install
|
||||
paths, token-file systemd paths, wrapper-script launches, and later service
|
||||
|
||||
@@ -2136,7 +2136,10 @@
|
||||
"package.json",
|
||||
"scripts/build-release.sh",
|
||||
"scripts/clean-mock-alerts.sh",
|
||||
"scripts/com.pulse.hot-dev.plist.template",
|
||||
"scripts/dev-check.sh",
|
||||
"scripts/dev-launchd-setup.sh",
|
||||
"scripts/dev-launchd-wrapper.sh",
|
||||
"scripts/hot-dev-bg.sh",
|
||||
"scripts/hot-dev.sh",
|
||||
"scripts/install-container-agent.sh",
|
||||
@@ -2241,7 +2244,10 @@
|
||||
"Makefile",
|
||||
"package.json",
|
||||
"scripts/clean-mock-alerts.sh",
|
||||
"scripts/com.pulse.hot-dev.plist.template",
|
||||
"scripts/dev-check.sh",
|
||||
"scripts/dev-launchd-setup.sh",
|
||||
"scripts/dev-launchd-wrapper.sh",
|
||||
"scripts/hot-dev-bg.sh",
|
||||
"scripts/hot-dev.sh",
|
||||
"scripts/toggle-mock.sh",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
# dev-launchd-setup.sh - Install or uninstall the Pulse dev Launch Agent
|
||||
# dev-launchd-setup.sh - Install or uninstall the Pulse managed dev LaunchAgent
|
||||
#
|
||||
# Usage:
|
||||
# ./scripts/dev-launchd-setup.sh # Install and start
|
||||
@@ -89,15 +89,16 @@ install() {
|
||||
fi
|
||||
|
||||
echo ""
|
||||
log_info "=== Pulse Dev Launch Agent Installed ==="
|
||||
log_info "=== Pulse Dev LaunchAgent Installed ==="
|
||||
log_info ""
|
||||
log_info "The dev environment will now auto-start on login and auto-restart on crash."
|
||||
log_info "The managed dev runtime will now auto-start on login and auto-restart on crash."
|
||||
log_info ""
|
||||
log_info "Useful commands:"
|
||||
log_info " Restart: launchctl kickstart -k ${GUI_DOMAIN}/${LABEL}"
|
||||
log_info " Stop: launchctl kill SIGTERM ${GUI_DOMAIN}/${LABEL}"
|
||||
log_info " Status: launchctl print ${GUI_DOMAIN}/${LABEL}"
|
||||
log_info " Logs: tail -f ${LOG_DIR}/hot-dev.stderr.log"
|
||||
log_info " Runtime: cd ${ROOT_DIR} && ./scripts/hot-dev-bg.sh status"
|
||||
log_info " Disable: launchctl bootout ${GUI_DOMAIN}/${LABEL}"
|
||||
log_info " Remove: $0 uninstall"
|
||||
}
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
#!/usr/bin/env bash
|
||||
# dev-launchd-wrapper.sh - Wrapper for running hot-dev.sh under launchd
|
||||
# dev-launchd-wrapper.sh - Wrapper for running hot-dev-bg launchd-session
|
||||
#
|
||||
# launchd doesn't source login profiles, so PATH won't include go, npm,
|
||||
# node, fswatch, etc. Hand off to an interactive login zsh so the user's
|
||||
# normal shell environment is loaded before exec'ing hot-dev.sh.
|
||||
# normal shell environment is loaded before exec'ing the managed launchd
|
||||
# supervisor path.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)"
|
||||
export PULSE_DEV_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd -P)"
|
||||
|
||||
exec /bin/zsh -ilc 'cd "$PULSE_DEV_ROOT" && exec bash scripts/hot-dev.sh'
|
||||
exec /bin/zsh -ilc 'cd "$PULSE_DEV_ROOT" && exec bash scripts/hot-dev-bg.sh launchd-session --takeover'
|
||||
|
||||
+54
-1
@@ -7,6 +7,7 @@
|
||||
# ./scripts/hot-dev-bg.sh stop
|
||||
# ./scripts/hot-dev-bg.sh restart
|
||||
# ./scripts/hot-dev-bg.sh backend-restart
|
||||
# ./scripts/hot-dev-bg.sh launchd-session [--takeover]
|
||||
# ./scripts/hot-dev-bg.sh verify [--takeover]
|
||||
# ./scripts/hot-dev-bg.sh status
|
||||
# ./scripts/hot-dev-bg.sh logs
|
||||
@@ -669,6 +670,56 @@ verify_bg() {
|
||||
run_verify_proof_command
|
||||
}
|
||||
|
||||
launchd_session_bg() {
|
||||
local takeover="${1:-false}"
|
||||
local managed_pid poll_interval
|
||||
|
||||
require_python
|
||||
|
||||
stop_and_exit() {
|
||||
log "Launchd supervisor received termination signal; stopping managed runtime..."
|
||||
stop_bg
|
||||
exit 0
|
||||
}
|
||||
|
||||
trap stop_and_exit TERM INT HUP
|
||||
|
||||
if ! is_running; then
|
||||
log "Managed runtime is not running. Starting supervised session..."
|
||||
start_bg "${takeover}"
|
||||
else
|
||||
managed_pid="$(managed_session_pid)"
|
||||
if [[ -n "${managed_pid}" ]] && has_unmanaged_listeners "${managed_pid}"; then
|
||||
if [[ "${takeover}" != "true" ]]; then
|
||||
fail "Managed runtime has split ownership. Rerun with: ./scripts/hot-dev-bg.sh launchd-session --takeover"
|
||||
fi
|
||||
log "Managed runtime has split ownership. Reclaiming ports before launchd supervision..."
|
||||
stop_bg
|
||||
start_bg "${takeover}"
|
||||
elif ! runtime_healthy; then
|
||||
log "Managed runtime is unhealthy. Restarting it before launchd supervision..."
|
||||
stop_bg
|
||||
start_bg "${takeover}"
|
||||
fi
|
||||
fi
|
||||
|
||||
managed_pid="$(managed_session_pid)"
|
||||
[[ -n "${managed_pid}" ]] || fail "Managed runtime did not produce a session pid"
|
||||
|
||||
poll_interval="${HOT_DEV_BG_SUPERVISOR_POLL_INTERVAL:-1}"
|
||||
log "Supervising managed runtime for launchd (pid: ${managed_pid})"
|
||||
|
||||
while is_running; do
|
||||
sleep "${poll_interval}"
|
||||
done
|
||||
|
||||
rm -f "${PID_FILE}"
|
||||
log "Managed runtime exited unexpectedly under launchd supervision"
|
||||
touch "${LOG_FILE}"
|
||||
tail -n 80 "${LOG_FILE}" || true
|
||||
return 1
|
||||
}
|
||||
|
||||
usage() {
|
||||
cat <<EOF
|
||||
Usage: $(basename "$0") <command>
|
||||
@@ -678,6 +729,7 @@ Commands:
|
||||
stop
|
||||
restart
|
||||
backend-restart
|
||||
launchd-session [--takeover]
|
||||
verify [--takeover]
|
||||
status
|
||||
logs
|
||||
@@ -689,7 +741,7 @@ parse_takeover_flag() {
|
||||
local flag="${2:-}"
|
||||
|
||||
case "${command}" in
|
||||
start|restart|verify)
|
||||
start|restart|verify|launchd-session)
|
||||
if [[ -z "${flag}" ]]; then
|
||||
printf "false\n"
|
||||
return 0
|
||||
@@ -731,6 +783,7 @@ main() {
|
||||
start_bg "${takeover}"
|
||||
;;
|
||||
backend-restart) restart_backend_bg ;;
|
||||
launchd-session) launchd_session_bg "${takeover}" ;;
|
||||
verify) verify_bg "${takeover}" ;;
|
||||
status) status_bg ;;
|
||||
logs) logs_bg ;;
|
||||
|
||||
@@ -8,6 +8,7 @@ ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
HOT_DEV_BG="${ROOT_DIR}/scripts/hot-dev-bg.sh"
|
||||
PACKAGE_JSON="${ROOT_DIR}/package.json"
|
||||
FRONTEND_PACKAGE_JSON="${ROOT_DIR}/frontend-modern/package.json"
|
||||
DEV_LAUNCHD_WRAPPER="${ROOT_DIR}/scripts/dev-launchd-wrapper.sh"
|
||||
|
||||
if [[ ! -x "${HOT_DEV_BG}" ]]; then
|
||||
echo "hot-dev-bg.sh not found or not executable at ${HOT_DEV_BG}" >&2
|
||||
@@ -106,6 +107,7 @@ test_cli_parses_takeover_flag() {
|
||||
printf "start=%s\n" "$(parse_takeover_flag start --takeover)"
|
||||
printf "restart=%s\n" "$(parse_takeover_flag restart --takeover)"
|
||||
printf "verify=%s\n" "$(parse_takeover_flag verify --takeover)"
|
||||
printf "launchd=%s\n" "$(parse_takeover_flag launchd-session --takeover)"
|
||||
printf "backend_restart=%s\n" "$(parse_takeover_flag backend-restart)"
|
||||
printf "plain=%s\n" "$(parse_takeover_flag start)"
|
||||
if parse_takeover_flag status --takeover >/tmp/hot-dev-bg.invalid 2>&1; then
|
||||
@@ -119,6 +121,7 @@ test_cli_parses_takeover_flag() {
|
||||
assert_contains "takeover parsing enables start" "${output}" "start=true"
|
||||
assert_contains "takeover parsing enables restart" "${output}" "restart=true"
|
||||
assert_contains "takeover parsing enables verify" "${output}" "verify=true"
|
||||
assert_contains "takeover parsing enables launchd-session" "${output}" "launchd=true"
|
||||
assert_contains "backend restart remains flagless" "${output}" "backend_restart=false"
|
||||
assert_contains "start without flag stays false" "${output}" "plain=false"
|
||||
assert_contains "unexpected status flag is rejected" "${output}" "invalid=rejected"
|
||||
@@ -145,6 +148,37 @@ PY"
|
||||
assert_contains "verify proof defaults password" "${output}" "password=admin"
|
||||
}
|
||||
|
||||
test_launchd_session_supervises_managed_runtime() {
|
||||
local output
|
||||
output="$(
|
||||
HOT_DEV_BG_PATH="${HOT_DEV_BG}" \
|
||||
bash -lc '
|
||||
source "${HOT_DEV_BG_PATH}"
|
||||
set +e
|
||||
test_dir="$(mktemp -d)"
|
||||
trap "rm -rf \"$test_dir\"" EXIT
|
||||
PID_FILE="${test_dir}/hot-dev-bg.pid"
|
||||
LOG_FILE="${test_dir}/hot-dev-bg.log"
|
||||
require_python(){ :; }
|
||||
start_bg(){ printf "%s\n" "999999" > "${PID_FILE}"; }
|
||||
launchd_session_bg true
|
||||
status=$?
|
||||
printf "status=%s\n" "${status}"
|
||||
'
|
||||
)"
|
||||
|
||||
assert_contains "launchd session starts supervised runtime" "${output}" "Managed runtime is not running. Starting supervised session..."
|
||||
assert_contains "launchd session announces supervision" "${output}" "Supervising managed runtime for launchd"
|
||||
assert_contains "launchd session exits nonzero on child crash" "${output}" "status=1"
|
||||
}
|
||||
|
||||
test_launchd_wrapper_uses_managed_supervisor() {
|
||||
local output
|
||||
output="$(sed -n '1,80p' "${DEV_LAUNCHD_WRAPPER}")"
|
||||
|
||||
assert_contains "launchd wrapper uses managed launchd-session" "${output}" "scripts/hot-dev-bg.sh launchd-session --takeover"
|
||||
}
|
||||
|
||||
test_root_package_exposes_managed_runtime_entrypoints() {
|
||||
local output
|
||||
output="$(
|
||||
@@ -292,6 +326,8 @@ test_detects_unmanaged_listeners() {
|
||||
main() {
|
||||
test_cli_parses_takeover_flag
|
||||
test_verify_command_injects_managed_runtime_env
|
||||
test_launchd_session_supervises_managed_runtime
|
||||
test_launchd_wrapper_uses_managed_supervisor
|
||||
test_root_package_exposes_managed_runtime_entrypoints
|
||||
test_frontend_package_exposes_managed_runtime_entrypoints
|
||||
test_backend_restart_requires_managed_runtime
|
||||
|
||||
Reference in New Issue
Block a user