#!/usr/bin/env bash # Local development server: start|stop|status|restart|logs|tunnel-check # # ./scripts/dev-server.sh restart # reads .env.dev if present, runs on DASH_PORT # # The server keeps running after this shell exits (nohup + pidfile) so you can browse it # while an agent works. Logs: data/logs/dev.log set -uo pipefail ROOT="$(cd "$(dirname "$0")/.." && pwd)" cd "$ROOT" PIDFILE="$ROOT/data/dev-server.pid" LOG="$ROOT/data/logs/dev.log" mkdir -p "$ROOT/data/logs" running() { [ -f "$PIDFILE" ] && kill -0 "$(cat "$PIDFILE")" 2>/dev/null; } start() { if running; then echo "already running (pid $(cat "$PIDFILE")) — http://127.0.0.1:${DASH_PORT:-8090}" return 0 fi if [ -f "$ROOT/.env.dev" ]; then set -a; . "$ROOT/.env.dev"; set +a echo "loaded .env.dev" fi PORT="${DASH_PORT:-8090}" nohup .venv/bin/dashboards >"$LOG" 2>&1 & echo $! >"$PIDFILE" sleep 3 if running && curl -sf -o /dev/null "http://127.0.0.1:${PORT}/api/health"; then echo "started pid $(cat "$PIDFILE") — http://127.0.0.1:${PORT}" else echo "FAILED to start; last lines of $LOG:" tail -20 "$LOG" return 1 fi } stop() { if running; then local pid pid="$(cat "$PIDFILE")" kill "$pid" 2>/dev/null # Wait for the process to actually go away before handing the pidfile back. A bare # `sleep 1` was not enough: uvicorn's graceful shutdown waits for in-flight requests, # so `restart` with a browser open started the new server while the old one still # held the SQLite file — the dying process then logged `database is locked` for a poll # it had already fetched, which the app correctly turned into a red store-error event. # Two servers on one DB is the one way this script can fabricate an incident, so the # wait is bounded rather than optimistic, and SIGKILL is the last resort. local i=0 while kill -0 "$pid" 2>/dev/null; do i=$((i + 1)) if [ "$i" -gt 60 ]; then echo "pid $pid ignored SIGTERM after 20s; sending SIGKILL" kill -9 "$pid" 2>/dev/null break fi sleep 0.25 done echo "stopped" else echo "not running" fi rm -f "$PIDFILE" } case "${1:-start}" in start) start ;; stop) stop ;; restart) stop; start ;; status) if running; then echo "running pid $(cat "$PIDFILE")" curl -s "http://127.0.0.1:${DASH_PORT:-8090}/api/health" \ | .venv/bin/python -c "import json,sys; d=json.load(sys.stdin); print('status:', d['status']); [print(' ', s['key'], 'ok' if s['ok'] else 'DOWN', (s['error'] or '')[:90]) for s in d['sources']]" else echo "not running" fi ;; logs) tail -n 40 "$LOG" ;; *) echo "usage: $0 start|stop|restart|status|logs"; exit 2 ;; esac