cyberhybridhub/startup.sh
2026-05-31 11:17:12 -05:00

120 lines
3.1 KiB
Bash
Executable File

#!/usr/bin/env bash
# Start the API (port 3000) and Flutter web dev server (port 8080) with merged logs.
# Usage: ./startup.sh
set -euo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
API_PORT=3000
WEB_PORT=8080
if [ -f "$ROOT/scripts/flutter-env.sh" ]; then
# shellcheck source=/dev/null
source "$ROOT/scripts/flutter-env.sh"
fi
log() { printf '%s\n' "$*"; }
kill_port() {
local port=$1
if command -v fuser >/dev/null 2>&1; then
if fuser "${port}/tcp" >/dev/null 2>&1; then
log "Stopping process(es) on port ${port}..."
fuser -k "${port}/tcp" >/dev/null 2>&1 || true
sleep 0.5
fi
return
fi
if ! command -v lsof >/dev/null 2>&1; then
warn "Cannot free port ${port}: install fuser or lsof"
return
fi
local pids
pids="$(lsof -t -iTCP:"${port}" -sTCP:LISTEN 2>/dev/null || true)"
if [ -n "$pids" ]; then
log "Stopping process(es) on port ${port}: ${pids}"
# shellcheck disable=SC2086
kill ${pids} 2>/dev/null || true
sleep 0.5
# shellcheck disable=SC2086
kill -9 ${pids} 2>/dev/null || true
fi
}
warn() { printf 'WARN: %s\n' "$*" >&2; }
prefix_lines() {
local tag=$1
while IFS= read -r line || [ -n "${line:-}" ]; do
printf '[%s] %s\n' "$tag" "$line"
done
}
cleanup() {
trap - INT TERM EXIT
log "Shutting down..."
if [ -n "${API_PID:-}" ]; then kill "$API_PID" 2>/dev/null || true; fi
if [ -n "${WEB_PID:-}" ]; then kill "$WEB_PID" 2>/dev/null || true; fi
kill_port "$API_PORT"
kill_port "$WEB_PORT"
wait 2>/dev/null || true
}
trap cleanup INT TERM EXIT
log "Freeing ports ${API_PORT} (API) and ${WEB_PORT} (web)..."
kill_port "$API_PORT"
kill_port "$WEB_PORT"
if [ ! -f "$ROOT/server/.env" ]; then
warn "Missing server/.env — copy server/.env.example and configure it first."
fi
log "Starting API on http://localhost:${API_PORT} ..."
(
cd "$ROOT/server"
dart run bin/server.dart 2>&1 | prefix_lines api
) &
API_PID=$!
# Build a static debug web bundle and serve it via a plain HTTP server.
# This avoids `flutter run -d web-server`'s DWDS session pinning, which causes
# blank pages when the browser is closed and reopened.
#
# Set SKIP_WEB_BUILD=1 to skip the build (useful when iterating on server-only
# changes and the existing build/web is fine to keep serving).
if [ "${SKIP_WEB_BUILD:-0}" != "1" ]; then
log "Building Flutter web (debug)..."
(
cd "$ROOT"
flutter build web --debug --source-maps \
--dart-define=API_BASE_URL="http://localhost:${API_PORT}" \
2>&1 | prefix_lines web-build
)
if [ -x "$ROOT/scripts/copy_flutter_js_source_map.sh" ]; then
"$ROOT/scripts/copy_flutter_js_source_map.sh" "$ROOT/build/web" \
2>&1 | prefix_lines web-build
fi
else
log "SKIP_WEB_BUILD=1 — reusing existing build/web bundle."
fi
log "Starting web app on http://localhost:${WEB_PORT} ..."
(
cd "$ROOT"
dart run scripts/web_static_server.dart build/web "${WEB_PORT}" \
2>&1 | prefix_lines web
) &
WEB_PID=$!
log "Both services running. Press Ctrl+C to stop."
log " API: http://localhost:${API_PORT}"
log " Web: http://localhost:${WEB_PORT}"
log ""
wait -n 2>/dev/null || wait || true
exit_code=$?
cleanup
exit "$exit_code"