#!/usr/bin/env bash set -euo pipefail script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" repository_root="$(cd "${script_dir}/.." && pwd)" local_replay_env_file="${repository_root}/.env.local-replay" if [[ -f "${local_replay_env_file}" ]]; then set -a # shellcheck disable=SC1090 source "${local_replay_env_file}" set +a echo "Loaded local replay configuration." fi server_dir="${repository_root}/server" client_dir="${repository_root}/client" server_port="${SERVER_PORT:-8082}" backend_url="http://127.0.0.1:${server_port}" frontend_port="${CLIENT_PORT:-5178}" frontend_url="http://127.0.0.1:${frontend_port}" replay_hotel_id="${VITE_RESERVATION_HOTEL_ID:-${AUTH_BOOTSTRAP_DEFAULT_HOTEL_ID:-HOTEL-LOCAL-REPLAY}}" backend_pid="" frontend_pid="" # Spring profile reads SERVER_PORT from the process environment. Keep the # value used by the health check and the value used by Spring Boot identical. export SERVER_PORT="${server_port}" cleanup() { if [[ -n "${frontend_pid}" ]] && kill -0 "${frontend_pid}" 2>/dev/null; then kill "${frontend_pid}" 2>/dev/null || true fi if [[ -n "${backend_pid}" ]] && kill -0 "${backend_pid}" 2>/dev/null; then kill "${backend_pid}" 2>/dev/null || true fi } trap cleanup EXIT INT TERM is_health_endpoint_ready() { local base_url="$1" local response response="$(curl --silent --show-error --fail "${base_url}/api/health" 2>/dev/null)" || return 1 [[ "${response}" == *'"status":"UP"'* ]] } is_replay_capabilities_endpoint_ready() { local response response="$(curl --silent --show-error --fail \ "${frontend_url}/api/system/debug/agentbus-eml-replays/capabilities?hotel_id=${replay_hotel_id}" \ 2>/dev/null)" || return 1 [[ "${response}" == *'"replay_enabled":'* ]] \ && [[ "${response}" == *'"strict_execution_ready":'* ]] } if [[ "${START_BOOKING_POSTGRES:-false}" == "true" ]]; then compose_file="${BOOKING_POSTGRES_COMPOSE_FILE:-${repository_root}/docker-compose.booking-postgres.yml}" if [[ ! -f "${compose_file}" ]]; then echo "Booking PostgreSQL compose file not found: ${compose_file}" >&2 exit 66 fi docker compose -f "${compose_file}" up -d fi if [[ "${TH_HOTEL_BOOKING_PG_ENABLED:-false}" == "true" ]]; then echo "Booking PostgreSQL is enabled; this process will not invent credentials." else echo "Booking PostgreSQL is disabled. Strict real-agent execute remains fail-closed until configured." fi ( cd "${server_dir}" exec ./mvnw spring-boot:run -Dspring-boot.run.profiles=local,local-replay ) & backend_pid=$! ( cd "${client_dir}" export VITE_API_PROXY_TARGET="${VITE_API_PROXY_TARGET:-${backend_url}}" export VITE_ENABLE_BR00_PROTOTYPE="${VITE_ENABLE_BR00_PROTOTYPE:-true}" export VITE_RESERVATION_HOTEL_ID="${VITE_RESERVATION_HOTEL_ID:-${AUTH_BOOTSTRAP_DEFAULT_HOTEL_ID:-HOTEL-LOCAL-REPLAY}}" exec pnpm exec vite --host 127.0.0.1 --port "${frontend_port}" --strictPort ) & frontend_pid=$! for attempt in $(seq 1 120); do if ! kill -0 "${backend_pid}" 2>/dev/null; then echo "Backend exited before /api/health became ready." >&2 exit 1 fi if ! kill -0 "${frontend_pid}" 2>/dev/null; then echo "Frontend exited before ${frontend_url} became ready; the requested UI port may already be in use." >&2 exit 1 fi if is_health_endpoint_ready "${backend_url}" \ && is_replay_capabilities_endpoint_ready; then echo "Local replay backend ready: ${backend_url}" echo "Local replay UI proxy ready: ${frontend_url} -> ${backend_url}" echo "Fixed identity: LOCAL_TEST_REPLAY_OPERATOR (no saved token; local-replay profile only)" break fi if [[ "${attempt}" == "120" ]]; then echo "Backend did not become ready within 120 seconds." >&2 exit 1 fi sleep 1 done wait "${backend_pid}" "${frontend_pid}"