#!/usr/bin/env bash # scripts/manage_sidecar.sh — ops helper for the BizPortal Python sidecar. # # Auto-loaded at login via com.ysterbaard.bizportal-sidecar.plist. # # Subcommands: # status — print whether the sidecar is running and the /health JSON. # start — load + boot the LaunchAgent (safe to re-run when already up). # stop — kickstart -k to terminate; the agent will NOT be reloaded. # restart — kickstart -k to terminate + re-launch. # logs — tail the stdout/stderr logs. # doctor — show what's wired up: plist path, working dir, /health, # who owns the port, and whether the Next.js sidecar URL env # is set. # # Used by: # - launchd (auto-start at login) # - humans debugging "bizportal search is stuck again" # - cron smoke checks set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROFILE_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)" PLIST_PATH="${HOME}/Library/LaunchAgents/com.ysterbaard.bizportal-sidecar.plist" LABEL="com.ysterbaard.bizportal-sidecar" SIDECAR_PORT="${BIZPORTAL_SERVICE_PORT:-7001}" cmd="${1:-status}" case "$cmd" in status) echo "=== bizportal sidecar — status ===" if curl -s --max-time 3 "http://127.0.0.1:${SIDECAR_PORT}/health" | head -c 500; then echo else echo "NOT REACHABLE at http://127.0.0.1:${SIDECAR_PORT}" exit 1 fi echo echo "LaunchAgent label: $LABEL" launchctl list 2>/dev/null | grep -F "$LABEL" || echo "(not loaded into launchd)" ;; start) if [ ! -f "$PLIST_PATH" ]; then echo "ERROR: plist not found at $PLIST_PATH" >&2 exit 2 fi UID_VAL=$(id -u) launchctl bootstrap "gui/${UID_VAL}" "$PLIST_PATH" 2>&1 || true launchctl enable "gui/${UID_VAL}/${LABEL}" 2>&1 || true launchctl kickstart -k "gui/${UID_VAL}/${LABEL}" 2>&1 echo "started — waiting for /health…" for i in 1 2 3 4 5 6 7 8; do sleep 1 if curl -s --max-time 1 "http://127.0.0.1:${SIDECAR_PORT}/health" >/dev/null 2>&1; then echo "OK after ${i}s" exit 0 fi done echo "ERROR: sidecar didn't come up within 8s — check logs" >&2 tail -n 40 /tmp/bizportal-sidecar.err.log 2>/dev/null || true exit 3 ;; stop) UID_VAL=$(id -u) launchctl kickstart -k "gui/${UID_VAL}/${LABEL}" 2>&1 || true echo "stopped (pl remains loaded; will not autostart next login)" ;; restart) UID_VAL=$(id -u) launchctl kickstart -k "gui/${UID_VAL}/${LABEL}" 2>&1 || true sleep 2 launchctl kickstart -k "gui/${UID_VAL}/${LABEL}" 2>&1 || true echo "restarted" ;; logs) echo "=== stdout ===" tail -n 200 /tmp/bizportal-sidecar.out.log 2>/dev/null || echo "(no stdout yet)" echo echo "=== stderr ===" tail -n 200 /tmp/bizportal-sidecar.err.log 2>/dev/null || echo "(no stderr yet)" ;; doctor) echo "=== bizportal sidecar — doctor ===" echo "Project: $PROFILE_DIR" echo "Plist: $PLIST_PATH" if [ -f "$PLIST_PATH" ]; then echo " exists ✓" plutil -lint "$PLIST_PATH" 2>/dev/null && echo " syntax OK ✓" else echo " MISSING ✗" fi echo "Logs: /tmp/bizportal-sidecar.{out,err}.log" echo "Venv: ${PROFILE_DIR}/scripts/.venv" echo "Health:" curl -s --max-time 3 "http://127.0.0.1:${SIDECAR_PORT}/health" | head -c 500 || echo " not reachable" echo echo "Port: ${SIDECAR_PORT}" lsof -nP -iTCP:"${SIDECAR_PORT}" 2>/dev/null | head -5 || echo " free" echo echo "LaunchAgent load status:" launchctl list 2>/dev/null | grep -F "$LABEL" || echo " not loaded (run '$0 start' or relaunch to autostart)" ;; *) echo "Usage: $0 {status|start|stop|restart|logs|doctor}" >&2 exit 1 ;; esac