A specific --host=ADDR binds only that address, so localhost health-check and window-open failed. Derive REACH_HOST from the bind (localhost for unset/0.0.0.0, else the bound IP) and use it for web_up + PANEL_URL. Verified: gate works with a real passcode (401 unauth, 200 with cookie). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
152 lines
5.7 KiB
Bash
Executable file
152 lines
5.7 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
# Launch the prospector operator app locally and open it as a Chrome app window
|
|
# (the containerless PWA experience). Starts the NestJS API + the vite preview
|
|
# front door (serves the built web/ assets and injects the bearer token), waits
|
|
# for health, then opens Chrome --app at the panel URL.
|
|
#
|
|
# scripts/app.sh launch in the foreground (Ctrl-C stops it)
|
|
# scripts/app.sh --build rebuild backend + web first
|
|
# scripts/app.sh --detach start in the background and return (tray/menubar)
|
|
# ./run app (preferred entrypoint)
|
|
#
|
|
# If the app is already running, just opens the window. Foreground mode stops
|
|
# both processes on Ctrl-C; detached mode leaves them running (stop with
|
|
# './run stop'). Only the PIDs we started are killed — never a blanket node kill.
|
|
set -euo pipefail
|
|
|
|
. "$(dirname "${BASH_SOURCE[0]}")/lib.sh"
|
|
load_env
|
|
|
|
API_PORT="${PROSPECTOR_API_PORT:-3210}"
|
|
WEB_PORT="${PROSPECTOR_WEB_PORT:-4173}"
|
|
HEALTH_URL="http://127.0.0.1:${API_PORT}/health"
|
|
RUN_DIR="$REPO_ROOT/.run"
|
|
PIDFILE="$RUN_DIR/app.pids"
|
|
|
|
BUILD=0 DETACH=0
|
|
WEB_HOST="${PROSPECTOR_WEB_HOST:-}" # empty = localhost only (default)
|
|
for a in "$@"; do
|
|
case "$a" in
|
|
--build) BUILD=1 ;;
|
|
--detach) DETACH=1 ;;
|
|
--host) WEB_HOST="0.0.0.0" ;;
|
|
--host=*) WEB_HOST="${a#--host=}" ;;
|
|
*) die "unknown flag: $a (use --build / --detach / --host[=addr])" ;;
|
|
esac
|
|
done
|
|
|
|
# Binding the panel beyond localhost exposes an app with no login of its own —
|
|
# require a passcode (the vite preview gate) before doing so.
|
|
HOST_ARGS=()
|
|
if [[ -n "$WEB_HOST" ]]; then
|
|
HOST_ARGS=(--host "$WEB_HOST")
|
|
if [[ -z "$(grep -sE '^PROSPECTOR_PANEL_PASSCODE=.+' "$REPO_ROOT/web/.env.local")" ]]; then
|
|
warn "exposing the panel on $WEB_HOST with NO passcode — anyone who can reach :$WEB_PORT gets full operator control."
|
|
warn "set PROSPECTOR_PANEL_PASSCODE in web/.env.local first (then it prompts for a passcode)."
|
|
fi
|
|
fi
|
|
|
|
# Host used to reach the panel from this machine (health-check + opening the
|
|
# window). A specific --host binds only that address; unset/0.0.0.0 still answer
|
|
# on localhost.
|
|
if [[ -z "$WEB_HOST" || "$WEB_HOST" == "0.0.0.0" || "$WEB_HOST" == "::" ]]; then
|
|
REACH_HOST="localhost"
|
|
else
|
|
REACH_HOST="$WEB_HOST"
|
|
fi
|
|
PANEL_URL="http://${REACH_HOST}:${WEB_PORT}/#/markets"
|
|
|
|
api_up() { curl -fsS "$HEALTH_URL" >/dev/null 2>&1; }
|
|
# Any HTTP response = up (the passcode gate answers 401 to non-HTML requests, so
|
|
# don't use -f here — a 401 still means the preview server is listening).
|
|
web_up() { curl -sS -o /dev/null "http://${REACH_HOST}:${WEB_PORT}" 2>/dev/null; }
|
|
|
|
open_window() {
|
|
info "opening $PANEL_URL"
|
|
if [[ -d "/Applications/Google Chrome.app" ]]; then
|
|
open -na "Google Chrome" --args --app="$PANEL_URL" || open "$PANEL_URL"
|
|
else
|
|
warn "Google Chrome not found — opening in your default browser"
|
|
open "$PANEL_URL" 2>/dev/null || true
|
|
fi
|
|
}
|
|
|
|
if [[ "$BUILD" == "1" ]]; then
|
|
info "building backend"; ( cd "$REPO_ROOT" && npm run build )
|
|
info "building web"; ( cd "$REPO_ROOT/web" && npm run build )
|
|
fi
|
|
|
|
[[ -f "$REPO_ROOT/dist/main.js" ]] || die "backend not built — run 'scripts/app.sh --build' or './run install'"
|
|
[[ -d "$REPO_ROOT/web/dist" ]] || die "web not built — run 'scripts/app.sh --build' or './run install'"
|
|
|
|
mkdir -p "$RUN_DIR"
|
|
BACK_PID="" WEB_PID=""
|
|
|
|
# Reuse whatever is already up (idempotent); only start what's down. This avoids
|
|
# colliding with an existing instance on the same ports.
|
|
if api_up; then
|
|
ok "API already running on :$API_PORT — reusing"
|
|
else
|
|
info "starting API on :$API_PORT${DETACH:+ (detached)}"
|
|
if [[ "$DETACH" == "1" ]]; then
|
|
( cd "$REPO_ROOT" && exec node dist/main.js ) >"$RUN_DIR/api.log" 2>&1 &
|
|
else
|
|
( cd "$REPO_ROOT" && exec node dist/main.js ) &
|
|
fi
|
|
BACK_PID=$!
|
|
info "waiting for API health"
|
|
for _ in $(seq 1 40); do
|
|
kill -0 "$BACK_PID" 2>/dev/null || die "API exited during startup — port :$API_PORT in use or DB unreachable? (see $RUN_DIR/api.log)"
|
|
api_up && { ok "API healthy"; break; }
|
|
sleep 0.5
|
|
done
|
|
api_up || die "API did not become healthy at $HEALTH_URL"
|
|
fi
|
|
|
|
if web_up; then
|
|
ok "web front door already running on :$WEB_PORT — reusing"
|
|
else
|
|
info "starting web front door on :$WEB_PORT${DETACH:+ (detached)}"
|
|
if [[ "$DETACH" == "1" ]]; then
|
|
( cd "$REPO_ROOT/web" && exec npx vite preview ${HOST_ARGS[@]+"${HOST_ARGS[@]}"} --port "$WEB_PORT" --strictPort ) >"$RUN_DIR/web.log" 2>&1 &
|
|
else
|
|
( cd "$REPO_ROOT/web" && exec npx vite preview ${HOST_ARGS[@]+"${HOST_ARGS[@]}"} --port "$WEB_PORT" --strictPort ) &
|
|
fi
|
|
WEB_PID=$!
|
|
for _ in $(seq 1 40); do
|
|
kill -0 "$WEB_PID" 2>/dev/null || die "web preview exited during startup — port :$WEB_PORT in use? (see $RUN_DIR/web.log)"
|
|
web_up && break
|
|
sleep 0.5
|
|
done
|
|
web_up || die "web preview did not come up on :$WEB_PORT"
|
|
fi
|
|
|
|
# Record only the PIDs we started, so 'stop' touches nothing it didn't launch.
|
|
: > "$PIDFILE"
|
|
[[ -n "$BACK_PID" ]] && echo "$BACK_PID" >> "$PIDFILE"
|
|
[[ -n "$WEB_PID" ]] && echo "$WEB_PID" >> "$PIDFILE"
|
|
|
|
open_window
|
|
|
|
if [[ "$DETACH" == "1" ]]; then
|
|
ok "prospector running. API :$API_PORT · panel :$WEB_PORT · stop with './run stop'"
|
|
exit 0
|
|
fi
|
|
|
|
# Foreground: clean up only what we started; if we reused both, just exit.
|
|
if [[ -z "$BACK_PID" && -z "$WEB_PID" ]]; then
|
|
ok "prospector already running — opened window. API :$API_PORT · panel :$WEB_PORT"
|
|
exit 0
|
|
fi
|
|
|
|
cleanup() {
|
|
[[ -n "$WEB_PID" ]] && kill "$WEB_PID" 2>/dev/null || true
|
|
[[ -n "$BACK_PID" ]] && kill "$BACK_PID" 2>/dev/null || true
|
|
rm -f "$PIDFILE"
|
|
}
|
|
trap cleanup EXIT INT TERM
|
|
|
|
ok "prospector running. API :$API_PORT · panel :$WEB_PORT"
|
|
echo " Tip: in the Chrome window, ⋮ → Save and Share → Install Prospector for a dock icon."
|
|
echo " Press Ctrl-C to stop."
|
|
wait "${BACK_PID:-$WEB_PID}"
|