From 59a9c56330f6e721d991f5f8ccb0cd8ccb7ad729 Mon Sep 17 00:00:00 2001 From: cryptooda Date: Wed, 31 Dec 2025 22:08:55 +0700 Subject: [PATCH] Fix script to run aspire --- scripts/vibe-kanban/vibe-dev-server.sh | 735 ++++++++++++++++-- .../Properties/launchSettings.json.backup | 29 + .../launchSettings.json.task-TASK-4485 | 17 + 3 files changed, 713 insertions(+), 68 deletions(-) create mode 100644 src/Managing.AppHost/Properties/launchSettings.json.backup create mode 100644 src/Managing.AppHost/Properties/launchSettings.json.task-TASK-4485 diff --git a/scripts/vibe-kanban/vibe-dev-server.sh b/scripts/vibe-kanban/vibe-dev-server.sh index 266b4c8e..beb75452 100755 --- a/scripts/vibe-kanban/vibe-dev-server.sh +++ b/scripts/vibe-kanban/vibe-dev-server.sh @@ -2,6 +2,12 @@ # scripts/vibe-kanban/vibe-dev-server.sh # Simplified script for Vibe Kanban - starts API and Workers using Aspire # Assumes database setup is already done by vibe-setup.sh +# +# PORT CONSISTENCY: +# - Ports are calculated from PORT_OFFSET, which is stored in .vibe-setup.env +# - The same TASK_ID always uses the same PORT_OFFSET (set by vibe-setup.sh) +# - This ensures ports are consistent across runs for the same task +# - Port calculation: API=5000+OFFSET, Dashboard=15000+OFFSET, OTLP=19000+OFFSET, Resource=20000+OFFSET # Detect worktree root WORKTREE_ROOT="$(pwd)" @@ -106,6 +112,311 @@ echo " Task ID: $TASK_ID" echo " Port offset: $PORT_OFFSET" echo " Task Slot: $TASK_SLOT" +# Restore launchSettings.json function +restore_launch_settings() { + # Only restore if variables are set (they're set later in the script) + if [ -z "$LAUNCH_SETTINGS" ]; then + return 0 + fi + if [ -n "$LAUNCH_SETTINGS_BACKUP" ] && [ -f "$LAUNCH_SETTINGS_BACKUP" ]; then + cp "$LAUNCH_SETTINGS_BACKUP" "$LAUNCH_SETTINGS" 2>/dev/null || true + fi + if [ -n "$LAUNCH_SETTINGS_TEMP" ]; then + rm -f "$LAUNCH_SETTINGS_TEMP" 2>/dev/null || true + fi +} + +# Cleanup function to stop Aspire and related processes +cleanup_aspire() { + echo "" + echo "๐Ÿงน Cleaning up Aspire processes for task $TASK_ID..." + + # Kill processes using task-specific ports (if ports are set) + if [ -n "$API_PORT" ]; then + echo " Cleaning up port $API_PORT..." + lsof -ti :${API_PORT} | xargs kill -9 2>/dev/null || true + fi + if [ -n "$ASPIRE_DASHBOARD_PORT" ]; then + echo " Cleaning up port $ASPIRE_DASHBOARD_PORT..." + lsof -ti :${ASPIRE_DASHBOARD_PORT} | xargs kill -9 2>/dev/null || true + fi + if [ -n "$ASPIRE_OTLP_PORT" ]; then + echo " Cleaning up port $ASPIRE_OTLP_PORT..." + lsof -ti :${ASPIRE_OTLP_PORT} | xargs kill -9 2>/dev/null || true + fi + if [ -n "$ASPIRE_RESOURCE_SERVICE_PORT" ]; then + echo " Cleaning up port $ASPIRE_RESOURCE_SERVICE_PORT..." + lsof -ti :${ASPIRE_RESOURCE_SERVICE_PORT} | xargs kill -9 2>/dev/null || true + fi + + # Kill Aspire process if PID file exists + ASPIRE_PID_FILE="$WORKTREE_PROJECT_ROOT/.task-pids/aspire-${TASK_ID}.pid" + if [ -f "$ASPIRE_PID_FILE" ]; then + ASPIRE_PID=$(cat "$ASPIRE_PID_FILE" 2>/dev/null) + if [ -n "$ASPIRE_PID" ] && ps -p "$ASPIRE_PID" > /dev/null 2>&1; then + echo " Stopping Aspire process (PID: $ASPIRE_PID)..." + # Kill all child processes first (they might be holding ports) + pkill -P "$ASPIRE_PID" 2>/dev/null || true + sleep 1 + # Kill the main process + kill -TERM "$ASPIRE_PID" 2>/dev/null || true + sleep 2 + # Force kill if still running + if ps -p "$ASPIRE_PID" > /dev/null 2>&1; then + kill -KILL "$ASPIRE_PID" 2>/dev/null || true + fi + # Kill any remaining child processes + pkill -P "$ASPIRE_PID" 2>/dev/null || true + fi + rm -f "$ASPIRE_PID_FILE" + fi + + # Also kill any processes that might be children of previous Aspire runs + # Find all dotnet processes and check if they're related to our task ports + ps aux | grep "dotnet" | grep -v grep | while read line; do + PID=$(echo "$line" | awk '{print $2}') + # Check if this process is using any of our task ports + if lsof -p "$PID" 2>/dev/null | grep -E ":(15005|19005|20005|5005)" > /dev/null 2>&1; then + echo " Killing dotnet process $PID (using task ports)..." + # Kill the process and its children + pkill -P "$PID" 2>/dev/null || true + kill -9 "$PID" 2>/dev/null || true + fi + done + + # Kill dotnet processes related to AppHost + # Kill processes that match AppHost patterns + pkill -9 -f "dotnet.*AppHost" 2>/dev/null || true + pkill -9 -f "dotnet run.*AppHost" 2>/dev/null || true + + # Kill processes running from the AppHost directory specifically + # This catches processes that are running from that directory even if command doesn't show it + if [ -n "$MAIN_REPO" ]; then + APPHOST_DIR="$MAIN_REPO/src/Managing.AppHost" + # Use pwdx or lsof to find processes in this directory + ps aux | grep -E "dotnet.*run" | grep -v grep | while read line; do + PID=$(echo "$line" | awk '{print $2}') + # Check if this process has files open in AppHost directory or is using our ports + if lsof -p "$PID" 2>/dev/null | grep -q "$APPHOST_DIR"; then + echo " Killing dotnet process $PID (running from AppHost directory)..." + kill -9 "$PID" 2>/dev/null || true + elif lsof -p "$PID" 2>/dev/null | grep -E ":(15005|19005|20005|5005)" > /dev/null 2>&1; then + echo " Killing dotnet process $PID (using task ports)..." + kill -9 "$PID" 2>/dev/null || true + fi + done + fi + + # Kill any Aspire dashboard processes and orchestration processes + # These processes can hold onto ports even after the main process is killed + # Kill by process name patterns + pkill -9 -f "Aspire.Dashboard" 2>/dev/null || true + pkill -9 -f "dcpctrl" 2>/dev/null || true + pkill -9 -f "dcp start-apiserver" 2>/dev/null || true + pkill -9 -f "dcpproc" 2>/dev/null || true + pkill -9 -f "AspireWorker" 2>/dev/null || true + + # Also kill by executable name (Aspire dashboard runs as a separate process) + pkill -9 -f "Aspire.Dashboard.dll" 2>/dev/null || true + + # Kill all Managing.* processes (AppHost, Api, Workers) - these can hold ports + # These are the actual executables that Aspire spawns + echo " Killing all Managing.* processes..." + ps aux | grep -E "Managing\.(AppHost|Api|Workers)" | grep -v grep | while read line; do + PID=$(echo "$line" | awk '{print $2}') + if [ -n "$PID" ]; then + echo " Killing Managing.* process $PID..." + pkill -P "$PID" 2>/dev/null || true + kill -9 "$PID" 2>/dev/null || true + fi + done + # Also kill by pattern (more aggressive) + pkill -9 -f "Managing.AppHost" 2>/dev/null || true + pkill -9 -f "Managing.Api" 2>/dev/null || true + pkill -9 -f "Managing.Workers" 2>/dev/null || true + + # Kill any dotnet processes that might be running Aspire dashboard + # Find processes using our ports and kill them + for port in ${API_PORT} ${ASPIRE_DASHBOARD_PORT} ${ASPIRE_OTLP_PORT} ${ASPIRE_RESOURCE_SERVICE_PORT}; do + if [ -n "$port" ]; then + lsof -ti :${port} 2>/dev/null | xargs kill -9 2>/dev/null || true + fi + done + + # Kill any tail processes that might be following the log file + TAIL_PID_FILE="$WORKTREE_PROJECT_ROOT/.task-pids/tail-${TASK_ID}.pid" + if [ -f "$TAIL_PID_FILE" ]; then + TAIL_PID=$(cat "$TAIL_PID_FILE" 2>/dev/null) + if [ -n "$TAIL_PID" ] && ps -p "$TAIL_PID" > /dev/null 2>&1; then + echo " Killing log tailing process (PID: $TAIL_PID)..." + kill -9 "$TAIL_PID" 2>/dev/null || true + fi + rm -f "$TAIL_PID_FILE" + fi + + # Also kill any tail processes that might be following the log file (fallback) + if [ -n "$ASPIRE_LOG" ]; then + echo " Killing any remaining log tailing processes..." + pkill -f "tail.*aspire.*${TASK_ID}" 2>/dev/null || true + pkill -f "tail -f.*${ASPIRE_LOG}" 2>/dev/null || true + # Also kill any tail processes that have the log file open + if [ -d "$(dirname "$ASPIRE_LOG")" ]; then + ps aux | grep "tail" | grep -v grep | while read line; do + PID=$(echo "$line" | awk '{print $2}') + if lsof -p "$PID" 2>/dev/null | grep -q "$ASPIRE_LOG"; then + echo " Killing tail process $PID..." + kill -9 "$PID" 2>/dev/null || true + fi + done + fi + fi + + # Wait a moment for processes to fully terminate + sleep 2 + + # Restore launchSettings.json + restore_launch_settings + + echo "โœ… Cleanup complete" +} + +# Function to find an available port +find_available_port() { + local start_port=$1 + local end_port=$((start_port + 100)) # Search in a range of 100 ports + + for port in $(seq $start_port $end_port); do + if ! lsof -ti :${port} > /dev/null 2>&1; then + echo $port + return 0 + fi + done + + # If no port found in range, return a random high port + echo $((20000 + RANDOM % 10000)) +} + +# Ensure API_PORT is set (should be from config, but fallback if needed) +if [ -z "$API_PORT" ]; then + API_PORT=$((5000 + PORT_OFFSET)) +fi + +# DYNAMIC PORT ALLOCATION: Find available ports each time instead of using fixed offsets +# This completely eliminates port conflict race conditions +echo "๐Ÿ” Finding available ports for Aspire..." +ASPIRE_DASHBOARD_PORT=$(find_available_port 15000) +ASPIRE_OTLP_PORT=$(find_available_port 19000) +ASPIRE_RESOURCE_SERVICE_PORT=$(find_available_port 20000) + +echo " Dashboard will use port: $ASPIRE_DASHBOARD_PORT" +echo " OTLP will use port: $ASPIRE_OTLP_PORT" +echo " Resource Service will use port: $ASPIRE_RESOURCE_SERVICE_PORT" + +# Function to verify and free a port +verify_and_free_port() { + local port=$1 + local port_name=$2 + local max_attempts=5 + local attempt=0 + + while [ $attempt -lt $max_attempts ]; do + attempt=$((attempt + 1)) + + # Check if port is in use + PIDS_USING_PORT=$(lsof -ti :${port} 2>/dev/null) + + if [ -z "$PIDS_USING_PORT" ]; then + echo " โœ… Port $port ($port_name) is free" + return 0 + fi + + # Port is in use, show what's using it + echo " โš ๏ธ Port $port ($port_name) is in use by PIDs: $PIDS_USING_PORT" + + # Show process details + for pid in $PIDS_USING_PORT; do + if ps -p "$pid" > /dev/null 2>&1; then + PROCESS_INFO=$(ps -p "$pid" -o command= 2>/dev/null | head -1) + echo " PID $pid: $PROCESS_INFO" + fi + done + + # Kill processes using this port + echo " ๐Ÿ”ช Killing processes using port $port..." + for pid in $PIDS_USING_PORT; do + # Kill children first + pkill -P "$pid" 2>/dev/null || true + # Kill the process + kill -9 "$pid" 2>/dev/null || true + done + + # Also kill by process name if it's Aspire-related + if echo "$PIDS_USING_PORT" | xargs ps -p 2>/dev/null | grep -qiE "(Aspire|AppHost|dcp)"; then + pkill -9 -f "Aspire.Dashboard" 2>/dev/null || true + pkill -9 -f "dcpctrl" 2>/dev/null || true + pkill -9 -f "dcp" 2>/dev/null || true + fi + + # Wait for port to be released + sleep 2 + + # Verify port is now free + if ! lsof -ti :${port} > /dev/null 2>&1; then + echo " โœ… Port $port ($port_name) is now free" + return 0 + fi + done + + # Port still in use after max attempts + echo " โŒ Port $port ($port_name) is still in use after $max_attempts attempts" + return 1 +} + +# Set up signal handlers for cleanup on exit +trap cleanup_aspire EXIT INT TERM + +# Clean up any existing processes for this task before starting +echo "" +echo "๐Ÿงน Cleaning up any existing processes for task $TASK_ID..." +cleanup_aspire + +# Wait for ports to be released (TIME_WAIT state can take a few seconds) +echo "โณ Waiting for ports to be released..." +for i in {1..10}; do + PORTS_IN_USE=0 + if [ -n "$API_PORT" ] && lsof -ti :${API_PORT} > /dev/null 2>&1; then + PORTS_IN_USE=$((PORTS_IN_USE + 1)) + fi + if [ -n "$ASPIRE_DASHBOARD_PORT" ] && lsof -ti :${ASPIRE_DASHBOARD_PORT} > /dev/null 2>&1; then + PORTS_IN_USE=$((PORTS_IN_USE + 1)) + fi + if [ -n "$ASPIRE_OTLP_PORT" ] && lsof -ti :${ASPIRE_OTLP_PORT} > /dev/null 2>&1; then + PORTS_IN_USE=$((PORTS_IN_USE + 1)) + fi + if [ -n "$ASPIRE_RESOURCE_SERVICE_PORT" ] && lsof -ti :${ASPIRE_RESOURCE_SERVICE_PORT} > /dev/null 2>&1; then + PORTS_IN_USE=$((PORTS_IN_USE + 1)) + fi + + if [ $PORTS_IN_USE -eq 0 ]; then + echo "โœ… All ports are free" + break + else + if [ $i -lt 10 ]; then + echo " Ports still in use, waiting... (${i}/10)" + sleep 1 + else + echo "โš ๏ธ Some ports are still in use after cleanup" + echo " Attempting to force kill processes on ports..." + # Force kill one more time + if [ -n "$API_PORT" ]; then lsof -ti :${API_PORT} | xargs kill -9 2>/dev/null || true; fi + if [ -n "$ASPIRE_DASHBOARD_PORT" ]; then lsof -ti :${ASPIRE_DASHBOARD_PORT} | xargs kill -9 2>/dev/null || true; fi + if [ -n "$ASPIRE_OTLP_PORT" ]; then lsof -ti :${ASPIRE_OTLP_PORT} | xargs kill -9 2>/dev/null || true; fi + if [ -n "$ASPIRE_RESOURCE_SERVICE_PORT" ]; then lsof -ti :${ASPIRE_RESOURCE_SERVICE_PORT} | xargs kill -9 2>/dev/null || true; fi + sleep 2 + fi + fi +done + # Verify database is ready if [ -n "$POSTGRES_PORT" ]; then echo "๐Ÿ” Verifying database is ready on port $POSTGRES_PORT..." @@ -117,10 +428,9 @@ if [ -n "$POSTGRES_PORT" ]; then echo "โœ… Database is ready" fi -# Ensure API_PORT is set (should be from config, but fallback if needed) -if [ -z "$API_PORT" ]; then - API_PORT=$((5000 + PORT_OFFSET)) -fi +echo "๐Ÿ“Š Aspire Dashboard Port: $ASPIRE_DASHBOARD_PORT" +echo "๐Ÿ“Š Aspire OTLP Port: $ASPIRE_OTLP_PORT" +echo "๐Ÿ“Š Aspire Resource Service Port: $ASPIRE_RESOURCE_SERVICE_PORT" # Set environment variables for Aspire export TASK_ID="$TASK_ID" @@ -138,14 +448,18 @@ if ! dotnet dev-certs https --check > /dev/null 2>&1; then fi # Configure Aspire to use HTTP only (avoid certificate issues) -# Use the "http" launch profile which is configured for HTTP-only -export ASPNETCORE_URLS="http://localhost:15242" -export DOTNET_DASHBOARD_OTLP_ENDPOINT_URL="http://localhost:19204" -export DOTNET_RESOURCE_SERVICE_ENDPOINT_URL="http://localhost:20284" -export DOTNET_DASHBOARD_OTLP_HTTP_ENDPOINT_URL="http://localhost:19204" +# IMPORTANT: We MUST set OTLP endpoint (Aspire requires it), but we only set the HTTP one (not both) +# Setting both DOTNET_DASHBOARD_OTLP_ENDPOINT_URL and DOTNET_DASHBOARD_OTLP_HTTP_ENDPOINT_URL +# can cause double-binding issues +export ASPIRE_ALLOW_UNSECURED_TRANSPORT="true" +export ASPNETCORE_URLS="http://localhost:${ASPIRE_DASHBOARD_PORT}" +export DOTNET_DASHBOARD_OTLP_HTTP_ENDPOINT_URL="http://localhost:${ASPIRE_OTLP_PORT}" export ASPNETCORE_ENVIRONMENT="Development" export DOTNET_ENVIRONMENT="Development" +# NOTE: We do NOT set DOTNET_RESOURCE_SERVICE_ENDPOINT_URL - let Aspire choose its own port +# We also do NOT set DOTNET_DASHBOARD_OTLP_ENDPOINT_URL (only HTTP version) + # Restore packages in the worktree first to ensure all dependencies are available # This is important because Aspire will build projects that may reference worktree paths echo "" @@ -179,9 +493,194 @@ fi cd "$MAIN_REPO/src/Managing.AppHost" echo "" +# Create a temporary launchSettings.json with task-specific port +# This ensures Aspire uses the correct port for this task +LAUNCH_SETTINGS="$MAIN_REPO/src/Managing.AppHost/Properties/launchSettings.json" +LAUNCH_SETTINGS_BACKUP="$MAIN_REPO/src/Managing.AppHost/Properties/launchSettings.json.backup" +LAUNCH_SETTINGS_TEMP="$MAIN_REPO/src/Managing.AppHost/Properties/launchSettings.json.task-${TASK_ID}" + +# Backup original launchSettings.json if not already backed up +if [ ! -f "$LAUNCH_SETTINGS_BACKUP" ]; then + cp "$LAUNCH_SETTINGS" "$LAUNCH_SETTINGS_BACKUP" 2>/dev/null || true +fi + +# Create task-specific launchSettings.json with custom port +# NOTE: Only set DOTNET_DASHBOARD_OTLP_HTTP_ENDPOINT_URL (not both HTTP and non-HTTP versions) +cat > "$LAUNCH_SETTINGS_TEMP" </dev/null || true +pkill -9 -f "dcpctrl" 2>/dev/null || true +pkill -9 -f "dcp start-apiserver" 2>/dev/null || true +pkill -9 -f "dcpproc" 2>/dev/null || true +pkill -9 -f "Managing.AppHost" 2>/dev/null || true +pkill -9 -f "Managing.Workers" 2>/dev/null || true +pkill -9 -f "Managing.Api" 2>/dev/null || true +sleep 2 + +# Verify each port individually +echo "" +echo "๐Ÿ” Step 2: Verifying each port is free..." +ALL_PORTS_FREE=true + +if ! verify_and_free_port "$ASPIRE_DASHBOARD_PORT" "Aspire Dashboard"; then + ALL_PORTS_FREE=false +fi + +if ! verify_and_free_port "$ASPIRE_OTLP_PORT" "Aspire OTLP"; then + ALL_PORTS_FREE=false +fi + +if ! verify_and_free_port "$ASPIRE_RESOURCE_SERVICE_PORT" "Aspire Resource Service"; then + ALL_PORTS_FREE=false +fi + +if ! verify_and_free_port "$API_PORT" "API"; then + ALL_PORTS_FREE=false +fi + +# Final verification - check all ports one more time +echo "" +echo "๐Ÿ” Step 3: Final verification - all ports must be free..." +FINAL_CHECK_FAILED=false +for port in "$ASPIRE_DASHBOARD_PORT" "$ASPIRE_OTLP_PORT" "$ASPIRE_RESOURCE_SERVICE_PORT" "$API_PORT"; do + if lsof -ti :${port} > /dev/null 2>&1; then + echo " โŒ Port $port is still in use!" + FINAL_CHECK_FAILED=true + fi +done + +if [ "$FINAL_CHECK_FAILED" = true ] || [ "$ALL_PORTS_FREE" = false ]; then + echo "" + echo "โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”" + echo "โŒ ERROR: Cannot start Aspire - ports are still in use" + echo "โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”" + echo "๐Ÿ’ก This usually means:" + echo " 1. Another instance of Aspire is running" + echo " 2. A previous instance didn't shut down properly" + echo " 3. Another application is using these ports" + echo "" + echo "๐Ÿ’ก Try running the cleanup script:" + echo " bash scripts/vibe-kanban/cleanup-api-workers.sh $TASK_ID" + echo "" + echo "๐Ÿ’ก Or manually kill processes using these ports:" + for port in "$ASPIRE_DASHBOARD_PORT" "$ASPIRE_OTLP_PORT" "$ASPIRE_RESOURCE_SERVICE_PORT" "$API_PORT"; do + PIDS=$(lsof -ti :${port} 2>/dev/null) + if [ -n "$PIDS" ]; then + echo " Port $port: kill -9 $PIDS" + fi + done + exit 1 +fi + +echo "" +echo "โœ… All ports are verified and free!" +echo "โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”" +echo "" + +# One final aggressive port check right before starting (race condition prevention) +echo "๐Ÿ” Final port check (race condition prevention)..." +# Kill any existing Aspire processes that might have started +echo " Killing any existing Aspire orchestration processes..." +pkill -9 -f "dcpctrl" 2>/dev/null || true +pkill -9 -f "dcpproc" 2>/dev/null || true +pkill -9 -f "dcp start-apiserver" 2>/dev/null || true +pkill -9 -f "dotnet run.*http" 2>/dev/null || true +pkill -9 -f "Managing.AppHost" 2>/dev/null || true +pkill -9 -f "Managing.Api" 2>/dev/null || true +pkill -9 -f "Managing.Workers" 2>/dev/null || true + +# Kill any processes using our specific ports (most important) +echo " Checking and killing processes using task ports..." +for port in "$ASPIRE_DASHBOARD_PORT" "$ASPIRE_OTLP_PORT" "$ASPIRE_RESOURCE_SERVICE_PORT" "$API_PORT"; do + PIDS=$(lsof -ti :${port} 2>/dev/null) + if [ -n "$PIDS" ]; then + echo " โš ๏ธ Port $port is in use by PIDs: $PIDS - killing..." + for pid in $PIDS; do + # Kill children first + pkill -P "$pid" 2>/dev/null || true + # Kill the process + kill -9 "$pid" 2>/dev/null || true + done + sleep 1 + fi +done + +# Wait longer for ports to be fully released (OS might hold them in TIME_WAIT) +echo " Waiting for OS to fully release ports (TIME_WAIT state)..." +sleep 5 + +# One more pre-emptive cleanup to catch any new processes +echo " Pre-emptive cleanup of any new processes..." +pkill -9 -f "Aspire.Dashboard" 2>/dev/null || true +pkill -9 -f "dcpctrl" 2>/dev/null || true +pkill -9 -f "dcp" 2>/dev/null || true +for port in "$ASPIRE_DASHBOARD_PORT" "$ASPIRE_OTLP_PORT" "$ASPIRE_RESOURCE_SERVICE_PORT" "$API_PORT"; do + lsof -ti :${port} 2>/dev/null | xargs kill -9 2>/dev/null || true +done +sleep 2 + +# Final verification - all ports must be free +echo " Verifying all ports are free..." +PORTS_STILL_IN_USE=0 +for port in "$ASPIRE_DASHBOARD_PORT" "$ASPIRE_OTLP_PORT" "$ASPIRE_RESOURCE_SERVICE_PORT" "$API_PORT"; do + if lsof -ti :${port} > /dev/null 2>&1; then + echo " โŒ Port $port is still in use!" + PORTS_STILL_IN_USE=$((PORTS_STILL_IN_USE + 1)) + fi +done + +if [ $PORTS_STILL_IN_USE -gt 0 ]; then + echo " โš ๏ธ Some ports are still in use. Attempting final aggressive cleanup..." + # Final aggressive kill + for port in "$ASPIRE_DASHBOARD_PORT" "$ASPIRE_OTLP_PORT" "$ASPIRE_RESOURCE_SERVICE_PORT" "$API_PORT"; do + lsof -ti :${port} 2>/dev/null | xargs kill -9 2>/dev/null || true + done + pkill -9 -f "Aspire" 2>/dev/null || true + pkill -9 -f "dcp" 2>/dev/null || true + sleep 3 +fi + +echo "โœ… Final port check complete" +echo "" + # Run Aspire (this will start the API and Workers) echo "โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”" -echo "๐Ÿš€ Starting Aspire..." +echo "๐Ÿš€ Starting Aspire on port $ASPIRE_DASHBOARD_PORT..." echo "โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”" echo "" @@ -189,7 +688,44 @@ echo "" ASPIRE_LOG="$WORKTREE_PROJECT_ROOT/.task-pids/aspire-${TASK_ID}.log" mkdir -p "$(dirname "$ASPIRE_LOG")" -# Start Aspire using the "http" launch profile (HTTP only, no HTTPS) +# CRITICAL: Kill any DCP processes that might interfere +# DCP (Distributed Control Plane) is Aspire's orchestrator and can hold ports +echo "๐Ÿ”ง Ensuring no DCP processes are running..." +pkill -9 -f "dcpctrl" 2>/dev/null || true +pkill -9 -f "dcpproc" 2>/dev/null || true +pkill -9 -f "dcp start-apiserver" 2>/dev/null || true +pkill -9 -f "Aspire.Hosting.Orchestration" 2>/dev/null || true +sleep 1 + +# Final port verification right before starting (within 1 second of starting Aspire) +for port in "$ASPIRE_DASHBOARD_PORT" "$ASPIRE_OTLP_PORT" "$ASPIRE_RESOURCE_SERVICE_PORT" "$API_PORT"; do + lsof -ti :${port} 2>/dev/null | xargs kill -9 2>/dev/null || true +done + +# CRITICAL: Kill ALL Aspire-related processes system-wide before starting +# This prevents any zombie processes from previous runs from interfering +echo "๐Ÿงน Final system-wide Aspire cleanup..." +pkill -9 -f "Aspire" 2>/dev/null || true +pkill -9 -f "dcp" 2>/dev/null || true +pkill -9 -f "Managing.AppHost" 2>/dev/null || true +pkill -9 -f "dotnet run.*AppHost" 2>/dev/null || true +pkill -9 -f "dotnet run.*http" 2>/dev/null || true +sleep 2 + +# One final verification that our ports are free +echo "๐Ÿ” Final pre-flight port check..." +for port in "$ASPIRE_DASHBOARD_PORT" "$ASPIRE_OTLP_PORT" "$ASPIRE_RESOURCE_SERVICE_PORT" "$API_PORT"; do + PIDS=$(lsof -ti :${port} 2>/dev/null) + if [ -n "$PIDS" ]; then + echo "โš ๏ธ Port $port is in use by PIDs: $PIDS - killing..." + for pid in $PIDS; do + kill -9 "$pid" 2>/dev/null || true + done + fi +done +sleep 1 + +# Start Aspire with the http launch profile (now configured with task-specific port) # All output goes to log file (warnings will be filtered when displaying) dotnet run --launch-profile http > "$ASPIRE_LOG" 2>&1 & ASPIRE_PID=$! @@ -200,56 +736,88 @@ echo $ASPIRE_PID > "$WORKTREE_PROJECT_ROOT/.task-pids/aspire-${TASK_ID}.pid" echo "โœ… Aspire started (PID: $ASPIRE_PID)" echo "๐Ÿ“‹ Log: $ASPIRE_LOG" echo "" -echo "โณ Aspire is starting (this may take 30-60 seconds on first run)..." +echo "โณ Aspire is starting (waiting up to 30 seconds)..." echo " Building projects and starting services..." # Wait a bit for Aspire to start writing to the log sleep 3 -# Try to extract the dashboard URL from the log (Aspire prints it when it starts) -ASPIRE_DASHBOARD_URL="" -ASPIRE_DASHBOARD_PORT="" - -# Common Aspire dashboard ports to try -POSSIBLE_PORTS=(15242 15000 15888 17247) - -echo "" -echo "โณ Waiting for Aspire dashboard to be ready..." -for i in {1..120}; do - # Try to extract dashboard URL from log +# Immediately check for binding errors in the log +echo "๐Ÿ” Checking for port binding errors..." +for i in {1..5}; do + sleep 1 if [ -f "$ASPIRE_LOG" ]; then - # Look for "Now listening on: http://localhost:PORT" or similar patterns - DASHBOARD_LINE=$(grep -i "listening\|dashboard\|http://localhost" "$ASPIRE_LOG" 2>/dev/null | tail -1) - if [ -n "$DASHBOARD_LINE" ]; then - # Extract port from the line - EXTRACTED_PORT=$(echo "$DASHBOARD_LINE" | grep -oE 'localhost:[0-9]+' | head -1 | cut -d: -f2) - if [ -n "$EXTRACTED_PORT" ]; then - ASPIRE_DASHBOARD_PORT="$EXTRACTED_PORT" - ASPIRE_DASHBOARD_URL="http://localhost:${ASPIRE_DASHBOARD_PORT}" + # Check for port binding errors (use actual ports, not hardcoded) + PORT_ERROR_PATTERN="address already in use|Failed to bind|bind.*${ASPIRE_DASHBOARD_PORT}|bind.*${ASPIRE_OTLP_PORT}|bind.*${ASPIRE_RESOURCE_SERVICE_PORT}|bind.*${API_PORT}" + if grep -qiE "$PORT_ERROR_PATTERN" "$ASPIRE_LOG" 2>/dev/null; then + echo "โŒ Port binding error detected in log!" + echo "โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”" + echo "๐Ÿ“‹ Error details:" + grep -iE "$PORT_ERROR_PATTERN" "$ASPIRE_LOG" 2>/dev/null | head -5 + echo "โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”" + echo "" + echo "๐Ÿ”ง Attempting to fix: killing processes and restarting..." + + # Kill Aspire process + kill -9 "$ASPIRE_PID" 2>/dev/null || true + pkill -P "$ASPIRE_PID" 2>/dev/null || true + + # Aggressively free all ports + for port in "$ASPIRE_DASHBOARD_PORT" "$ASPIRE_OTLP_PORT" "$ASPIRE_RESOURCE_SERVICE_PORT" "$API_PORT"; do + lsof -ti :${port} 2>/dev/null | xargs kill -9 2>/dev/null || true + done + + # Kill all Aspire processes + pkill -9 -f "Aspire.Dashboard" 2>/dev/null || true + pkill -9 -f "dcpctrl" 2>/dev/null || true + pkill -9 -f "dcp" 2>/dev/null || true + pkill -9 -f "Managing.AppHost" 2>/dev/null || true + + sleep 3 + + # Verify ports are free + PORTS_FREE=true + for port in "$ASPIRE_DASHBOARD_PORT" "$ASPIRE_OTLP_PORT" "$ASPIRE_RESOURCE_SERVICE_PORT" "$API_PORT"; do + if lsof -ti :${port} > /dev/null 2>&1; then + echo " โŒ Port $port is still in use!" + PORTS_FREE=false + fi + done + + if [ "$PORTS_FREE" = false ]; then + echo "โŒ Cannot free ports. Please run cleanup script manually." + cleanup_aspire + exit 1 fi + + # Clear the log and restart + echo "" > "$ASPIRE_LOG" + echo "๐Ÿ”„ Restarting Aspire..." + dotnet run --launch-profile http > "$ASPIRE_LOG" 2>&1 & + ASPIRE_PID=$! + echo $ASPIRE_PID > "$WORKTREE_PROJECT_ROOT/.task-pids/aspire-${TASK_ID}.pid" + echo "โœ… Aspire restarted (PID: $ASPIRE_PID)" + sleep 3 + break fi fi - - # If we don't have a URL yet, try common ports - if [ -z "$ASPIRE_DASHBOARD_URL" ]; then - for port in "${POSSIBLE_PORTS[@]}"; do - if curl -s -f "http://localhost:${port}" > /dev/null 2>&1; then - ASPIRE_DASHBOARD_PORT="$port" - ASPIRE_DASHBOARD_URL="http://localhost:${port}" - break - fi - done - fi - - # If we found the dashboard, break - if [ -n "$ASPIRE_DASHBOARD_URL" ] && curl -s -f "$ASPIRE_DASHBOARD_URL" > /dev/null 2>&1; then +done + +# Use the configured port (should match our launchSettings.json) +ASPIRE_DASHBOARD_URL="http://localhost:${ASPIRE_DASHBOARD_PORT}" + +echo "" +echo "โณ Waiting for Aspire dashboard to be ready on port $ASPIRE_DASHBOARD_PORT..." +for i in {1..30}; do + # Check the configured port + if curl -s -f "$ASPIRE_DASHBOARD_URL" > /dev/null 2>&1; then echo "โœ… Aspire dashboard is ready at $ASPIRE_DASHBOARD_URL!" break fi - # Show progress every 10 seconds - if [ $((i % 10)) -eq 0 ]; then - echo " Still starting... (${i}/120 seconds)" + # Show progress every 5 seconds + if [ $((i % 5)) -eq 0 ]; then + echo " Still starting... (${i}/30 seconds)" # Show last few lines of log for progress (filter warnings) if [ -f "$ASPIRE_LOG" ]; then LAST_LINE=$(tail -20 "$ASPIRE_LOG" 2>/dev/null | grep -vE "(warning|Warning|WARNING|NU[0-9]|\.csproj :)" | tail -1 | cut -c1-80) @@ -259,16 +827,35 @@ for i in {1..120}; do fi fi - if [ $i -eq 120 ]; then - echo "โš ๏ธ Aspire dashboard did not become ready after 120 seconds" + if [ $i -eq 30 ]; then + echo "โš ๏ธ Aspire dashboard did not become ready after 30 seconds" echo "๐Ÿ’ก Check the log: $ASPIRE_LOG" - echo "๐Ÿ’ก Last 10 lines of log (warnings filtered):" - tail -30 "$ASPIRE_LOG" 2>/dev/null | grep -vE "(warning|Warning|WARNING|NU[0-9]|\.csproj :)" | tail -10 || echo " (log file not found)" - # Try to use default port anyway - if [ -z "$ASPIRE_DASHBOARD_URL" ]; then - ASPIRE_DASHBOARD_PORT=15242 - ASPIRE_DASHBOARD_URL="http://localhost:15242" - echo "๐Ÿ’ก Using default port: $ASPIRE_DASHBOARD_URL" + echo "" + echo "โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”" + echo "๐Ÿ“‹ Last 50 lines of log (warnings filtered, errors highlighted):" + echo "โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”" + # Show last 50 lines, highlight errors + tail -200 "$ASPIRE_LOG" 2>/dev/null | grep -vE "(warning|Warning|WARNING|NU[0-9]|\.csproj :)" | tail -50 || echo " (log file not found)" + echo "" + # Check for specific errors + if grep -qiE "(error|exception|failed|unhandled|address already|bind)" "$ASPIRE_LOG" 2>/dev/null; then + echo "โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”" + echo "โŒ ERRORS FOUND IN LOG:" + echo "โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”" + tail -500 "$ASPIRE_LOG" 2>/dev/null | grep -iE "(error|exception|failed|unhandled|address already|bind)" | tail -20 + echo "โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”" + fi + # Try to extract port from log + if [ -f "$ASPIRE_LOG" ]; then + LOG_PORT=$(grep -i "listening\|Now listening" "$ASPIRE_LOG" 2>/dev/null | grep -oE 'localhost:[0-9]+' | head -1 | cut -d: -f2) + if [ -n "$LOG_PORT" ]; then + ASPIRE_DASHBOARD_URL="http://localhost:${LOG_PORT}" + echo "๐Ÿ’ก Dashboard may be at: $ASPIRE_DASHBOARD_URL (from log)" + else + echo "๐Ÿ’ก Dashboard should be at: $ASPIRE_DASHBOARD_URL" + fi + else + echo "๐Ÿ’ก Dashboard should be at: $ASPIRE_DASHBOARD_URL" fi fi sleep 1 @@ -285,11 +872,6 @@ for i in {1..90}; do break fi - # Show progress every 15 seconds - if [ $((i % 15)) -eq 0 ]; then - echo " Still waiting for API... (${i}/90 seconds)" - fi - if [ $i -eq 90 ]; then echo "โš ๏ธ API did not become ready after 90 seconds" echo "๐Ÿ’ก Check the log: $ASPIRE_LOG" @@ -319,11 +901,28 @@ echo "โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” echo "" # Tail the Aspire log (filter out warnings for cleaner output) -echo "๐Ÿ“‹ Showing Aspire logs (Press Ctrl+C to stop)" +echo "๐Ÿ“‹ Showing Aspire logs (Press Ctrl+C to stop and cleanup)" echo " (Warnings are hidden for cleaner output - full logs in: $ASPIRE_LOG)" echo "" -tail -f "$ASPIRE_LOG" 2>/dev/null | grep -vE "(warning|Warning|WARNING|NU[0-9]|\.csproj :)" || { - echo "โŒ Cannot read Aspire log: $ASPIRE_LOG" - echo "๐Ÿ’ก Aspire may still be starting. Check the log manually." - exit 1 -} + +# Use a background process group for tail so we can kill it properly +# This ensures cleanup can kill the tail process when interrupted +( + tail -f "$ASPIRE_LOG" 2>/dev/null | grep -vE "(warning|Warning|WARNING|NU[0-9]|\.csproj :)" || { + echo "โŒ Cannot read Aspire log: $ASPIRE_LOG" + echo "๐Ÿ’ก Aspire may still be starting. Check the log manually." + cleanup_aspire + exit 1 + } +) & +TAIL_PID=$! + +# Save tail PID so cleanup can kill it +echo $TAIL_PID > "$WORKTREE_PROJECT_ROOT/.task-pids/tail-${TASK_ID}.pid" 2>/dev/null || true + +# Wait for tail process (will be interrupted by Ctrl+C) +wait $TAIL_PID 2>/dev/null || true + +# Cleanup will be called by trap, but also ensure tail is killed +kill $TAIL_PID 2>/dev/null || true +rm -f "$WORKTREE_PROJECT_ROOT/.task-pids/tail-${TASK_ID}.pid" 2>/dev/null || true diff --git a/src/Managing.AppHost/Properties/launchSettings.json.backup b/src/Managing.AppHost/Properties/launchSettings.json.backup new file mode 100644 index 00000000..e73f3230 --- /dev/null +++ b/src/Managing.AppHost/Properties/launchSettings.json.backup @@ -0,0 +1,29 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "https://localhost:17247;http://localhost:15242", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development", + "DOTNET_ENVIRONMENT": "Development", + "DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "https://localhost:21005", + "DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "https://localhost:22209" + } + }, + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "http://localhost:15242", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development", + "DOTNET_ENVIRONMENT": "Development", + "DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:19204", + "DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "http://localhost:20284" + } + } + } +} diff --git a/src/Managing.AppHost/Properties/launchSettings.json.task-TASK-4485 b/src/Managing.AppHost/Properties/launchSettings.json.task-TASK-4485 new file mode 100644 index 00000000..f98a41b8 --- /dev/null +++ b/src/Managing.AppHost/Properties/launchSettings.json.task-TASK-4485 @@ -0,0 +1,17 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "http://localhost:15000", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development", + "DOTNET_ENVIRONMENT": "Development", + "DOTNET_DASHBOARD_OTLP_HTTP_ENDPOINT_URL": "http://localhost:19000", + "ASPIRE_ALLOW_UNSECURED_TRANSPORT": "true" + } + } + } +}