diff --git a/scripts/start-web-ui.sh b/scripts/start-web-ui.sh new file mode 100755 index 00000000..3b553e95 --- /dev/null +++ b/scripts/start-web-ui.sh @@ -0,0 +1,206 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +DESKTOP_DIR="${ROOT_DIR}/desktop" +HOST="${HOST:-127.0.0.1}" +SERVER_PORT_START="${SERVER_PORT:-3456}" +WEB_PORT_START="${WEB_PORT:-2024}" +MAX_PORT_SCAN="${MAX_PORT_SCAN:-100}" +RUN_ID="$(date +%s)-$RANDOM" +LOG_DIR="${LOG_DIR:-/tmp/cc-haha-web-ui-${RUN_ID}}" +SERVER_LOG="${LOG_DIR}/server.log" +WEB_LOG="${LOG_DIR}/web.log" + +SERVER_PID="" +WEB_PID="" + +usage() { + cat <<'EOF' +Usage: scripts/start-web-ui.sh + +Environment overrides: + SERVER_PORT=3456 preferred backend port + WEB_PORT=2024 preferred Web UI port + HOST=127.0.0.1 bind host for both processes + LOG_DIR=/tmp/... directory for server.log and web.log + +The script checks the preferred ports first and scans upward when a port is busy. +EOF +} + +if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then + usage + exit 0 +fi + +require_command() { + local command_name="$1" + if ! command -v "$command_name" >/dev/null 2>&1; then + echo "Missing required command: ${command_name}" >&2 + exit 1 + fi +} + +is_port_in_use() { + local port="$1" + + if command -v lsof >/dev/null 2>&1; then + lsof -nP -iTCP:"${port}" -sTCP:LISTEN >/dev/null 2>&1 + return $? + fi + + if command -v nc >/dev/null 2>&1; then + nc -z "${HOST}" "${port}" >/dev/null 2>&1 + return $? + fi + + echo "Missing lsof or nc; cannot check whether port ${port} is available." >&2 + exit 1 +} + +find_available_port() { + local start_port="$1" + local port="${start_port}" + local end_port=$((start_port + MAX_PORT_SCAN)) + + while (( port <= end_port )); do + if ! is_port_in_use "${port}"; then + printf '%s\n' "${port}" + return 0 + fi + port=$((port + 1)) + done + + echo "No available port found in range ${start_port}-${end_port}" >&2 + exit 1 +} + +urlencode() { + bun -e 'console.log(encodeURIComponent(process.argv[1]))' "$1" +} + +wait_for_http() { + local url="$1" + local log_file="$2" + + for _ in $(seq 1 120); do + if curl -fsS "${url}" >/dev/null 2>&1; then + return 0 + fi + + if [[ -n "${SERVER_PID}" ]] && ! kill -0 "${SERVER_PID}" >/dev/null 2>&1; then + echo "Server process exited before ${url} became ready. Recent log:" >&2 + tail -n 40 "${SERVER_LOG}" >&2 || true + exit 1 + fi + + if [[ -n "${WEB_PID}" ]] && ! kill -0 "${WEB_PID}" >/dev/null 2>&1; then + echo "Web UI process exited before ${url} became ready. Recent log:" >&2 + tail -n 40 "${WEB_LOG}" >&2 || true + exit 1 + fi + + sleep 1 + done + + echo "Timed out waiting for ${url}. Recent log:" >&2 + tail -n 40 "${log_file}" >&2 || true + exit 1 +} + +cleanup() { + local exit_code=$? + + if [[ -n "${WEB_PID}" ]]; then + kill "${WEB_PID}" >/dev/null 2>&1 || true + wait "${WEB_PID}" >/dev/null 2>&1 || true + fi + + if [[ -n "${SERVER_PID}" ]]; then + kill "${SERVER_PID}" >/dev/null 2>&1 || true + wait "${SERVER_PID}" >/dev/null 2>&1 || true + fi + + if [[ "${exit_code}" -ne 0 && "${exit_code}" -ne 130 && "${exit_code}" -ne 143 ]]; then + echo "Logs kept at ${LOG_DIR}" >&2 + fi +} + +trap cleanup EXIT +trap 'exit 130' INT +trap 'exit 143' TERM + +require_command bun +require_command curl + +if [[ ! -d "${DESKTOP_DIR}" ]]; then + echo "Desktop directory not found: ${DESKTOP_DIR}" >&2 + exit 1 +fi + +mkdir -p "${LOG_DIR}" + +SERVER_PORT_RESOLVED="$(find_available_port "${SERVER_PORT_START}")" +WEB_PORT_RESOLVED="$(find_available_port "${WEB_PORT_START}")" +if [[ "${WEB_PORT_RESOLVED}" == "${SERVER_PORT_RESOLVED}" ]]; then + WEB_PORT_RESOLVED="$(find_available_port "$((WEB_PORT_RESOLVED + 1))")" +fi + +SERVER_URL="http://${HOST}:${SERVER_PORT_RESOLVED}" +WEB_URL="http://${HOST}:${WEB_PORT_RESOLVED}/?serverUrl=$(urlencode "${SERVER_URL}")" + +if [[ "${SERVER_PORT_RESOLVED}" != "${SERVER_PORT_START}" ]]; then + echo "Server port ${SERVER_PORT_START} is busy; using ${SERVER_PORT_RESOLVED}." +fi + +if [[ "${WEB_PORT_RESOLVED}" != "${WEB_PORT_START}" ]]; then + echo "Web UI port ${WEB_PORT_START} is busy; using ${WEB_PORT_RESOLVED}." +fi + +echo "Starting server: ${SERVER_URL}" +( + cd "${ROOT_DIR}" + SERVER_PORT="${SERVER_PORT_RESOLVED}" bun run src/server/index.ts --host "${HOST}" --port "${SERVER_PORT_RESOLVED}" +) >"${SERVER_LOG}" 2>&1 & +SERVER_PID=$! + +wait_for_http "${SERVER_URL}/health" "${SERVER_LOG}" + +echo "Starting Web UI: http://${HOST}:${WEB_PORT_RESOLVED}" +( + cd "${DESKTOP_DIR}" + VITE_DESKTOP_SERVER_URL="${SERVER_URL}" bun run dev -- --host "${HOST}" --port "${WEB_PORT_RESOLVED}" --strictPort +) >"${WEB_LOG}" 2>&1 & +WEB_PID=$! + +wait_for_http "http://${HOST}:${WEB_PORT_RESOLVED}" "${WEB_LOG}" + +cat </dev/null 2>&1; then + wait "${SERVER_PID}" + exit $? + fi + + if ! kill -0 "${WEB_PID}" >/dev/null 2>&1; then + wait "${WEB_PID}" + exit $? + fi + + sleep 1 +done