#!/bin/bash # 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)" # Check if we're in a nested structure (Vibe Kanban worktree) if [ -d "$WORKTREE_ROOT/managing-apps" ] && [ -d "$WORKTREE_ROOT/managing-apps/src/Managing.Api" ]; then WORKTREE_PROJECT_ROOT="$WORKTREE_ROOT/managing-apps" elif [ -d "$WORKTREE_ROOT/src/Managing.Api" ]; then WORKTREE_PROJECT_ROOT="$WORKTREE_ROOT" else echo "โŒ Cannot find project structure in worktree" echo " Current directory: $WORKTREE_ROOT" exit 1 fi echo "๐Ÿ“ Worktree project root: $WORKTREE_PROJECT_ROOT" # TASK_ID file to ensure consistency (same as vibe-setup.sh) TASK_ID_FILE="$WORKTREE_PROJECT_ROOT/.vibe-task-id" # Load setup configuration if available SETUP_CONFIG_FILE="$WORKTREE_PROJECT_ROOT/.vibe-setup.env" if [ -f "$SETUP_CONFIG_FILE" ]; then echo "๐Ÿ“‹ Loading setup configuration from: $SETUP_CONFIG_FILE" source "$SETUP_CONFIG_FILE" echo " Task ID: $TASK_ID" echo " Task Slot: ${TASK_SLOT:-not set}" echo " Port offset: $PORT_OFFSET" echo " API Port: $API_PORT" else echo "โš ๏ธ Setup configuration not found: $SETUP_CONFIG_FILE" echo "๐Ÿ’ก Run scripts/vibe-kanban/vibe-setup.sh first to set up the database" # Try to get TASK_ID from stored file (ensures consistency) if [ -f "$TASK_ID_FILE" ]; then TASK_ID=$(cat "$TASK_ID_FILE" 2>/dev/null | tr -d '[:space:]') if [ -n "$TASK_ID" ]; then echo "๐Ÿ“‹ Using stored TASK_ID: $TASK_ID" fi fi # Try command line argument if [ -z "$TASK_ID" ]; then TASK_ID=${1:-""} fi # Try environment variables if [ -z "$TASK_ID" ]; then if [ -n "$VIBE_TASK_ID" ]; then TASK_ID="$VIBE_TASK_ID" elif [ -n "$VIBE_TASK_NAME" ]; then TASK_ID="$VIBE_TASK_NAME" fi fi PORT_OFFSET=${2:-0} if [ -z "$TASK_ID" ]; then echo "โŒ TASK_ID is required" echo "๐Ÿ’ก Usage: $0 [PORT_OFFSET]" echo "๐Ÿ’ก Or run scripts/vibe-kanban/vibe-setup.sh first to create setup configuration" exit 1 fi API_PORT=$((5000 + PORT_OFFSET)) # Extract TASK_SLOT from TASK_ID if not in config if [ -z "$TASK_SLOT" ]; then TASK_SLOT=$(echo "$TASK_ID" | grep -oE '[0-9]+' | head -1) if [ -z "$TASK_SLOT" ] || [ "$TASK_SLOT" = "0" ]; then TASK_SLOT=$((PORT_OFFSET / 10 + 1)) fi fi echo " Using Task ID: $TASK_ID" echo " Using Task Slot: $TASK_SLOT" echo " Using Port offset: $PORT_OFFSET" fi # Find main repository MAIN_REPO_PATHS=( "/Users/oda/Desktop/Projects/managing-apps" "$(git -C "$WORKTREE_PROJECT_ROOT" rev-parse --show-toplevel 2>/dev/null || echo '')" "$(dirname "$WORKTREE_ROOT" 2>/dev/null)/managing-apps" "${MAIN_REPO:-}" ) MAIN_REPO="" for path in "${MAIN_REPO_PATHS[@]}"; do if [ -n "$path" ] && [ -d "$path" ] && [ -d "$path/src/Managing.AppHost" ]; then MAIN_REPO="$path" break fi done if [ -z "$MAIN_REPO" ]; then echo "โŒ Cannot find main repository with Aspire AppHost" exit 1 fi echo "๐Ÿ“ Main repository: $MAIN_REPO" echo "๐Ÿš€ Starting API and Workers using Aspire..." 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..." if ! PGPASSWORD=postgres psql -h localhost -p $POSTGRES_PORT -U postgres -d postgres -c '\q' 2>/dev/null; then echo "โŒ Database is not ready on port $POSTGRES_PORT" echo "๐Ÿ’ก Run scripts/vibe-kanban/vibe-setup.sh first to set up the database" exit 1 fi echo "โœ… Database is ready" 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" export PORT_OFFSET="$PORT_OFFSET" export TASK_SLOT="$TASK_SLOT" # Ensure HTTPS dev certificate is available (Aspire may need it even for HTTP mode) echo "๐Ÿ” Ensuring HTTPS developer certificate is available..." if ! dotnet dev-certs https --check > /dev/null 2>&1; then echo " Generating HTTPS developer certificate..." dotnet dev-certs https --trust > /dev/null 2>&1 || { echo "โš ๏ธ Could not generate/trust certificate" echo " Will try to use HTTP-only profile" } fi # Configure Aspire to use HTTP only (avoid certificate issues) # 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 "" echo "๐Ÿ“ฆ Restoring NuGet packages..." echo "โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”" # Restore at solution level in worktree if it exists if [ -f "$WORKTREE_PROJECT_ROOT/src/Managing.sln" ]; then echo " Restoring in worktree solution..." cd "$WORKTREE_PROJECT_ROOT/src" # Suppress all warnings and only show errors dotnet restore Managing.sln --verbosity quiet --nologo 2>&1 | \ grep -vE "(warning|Warning|WARNING|NU[0-9]|\.csproj :)" || true fi # Restore at solution level in main repo (where we'll actually run from) echo " Restoring in main repo solution..." cd "$MAIN_REPO/src" # Suppress all warnings and only show errors RESTORE_OUTPUT=$(dotnet restore Managing.sln --verbosity quiet --nologo 2>&1 | \ grep -vE "(warning|Warning|WARNING|NU[0-9]|\.csproj :)" || true) if echo "$RESTORE_OUTPUT" | grep -qE "(error|Error|ERROR|failed|Failed)"; then echo "โŒ Package restore failed:" echo "$RESTORE_OUTPUT" exit 1 else echo "โœ… Packages restored successfully" fi # Ensure we're in the AppHost directory for running Aspire 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 on port $ASPIRE_DASHBOARD_PORT..." echo "โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”" echo "" # Run Aspire in the background and capture output ASPIRE_LOG="$WORKTREE_PROJECT_ROOT/.task-pids/aspire-${TASK_ID}.log" mkdir -p "$(dirname "$ASPIRE_LOG")" # 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=$! # Save PID 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 (waiting up to 30 seconds)..." echo " Building projects and starting services..." # Wait a bit for Aspire to start writing to the log sleep 3 # 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 # 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 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 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) if [ -n "$LAST_LINE" ]; then echo " Latest: $LAST_LINE" fi fi fi if [ $i -eq 30 ]; then echo "โš ๏ธ Aspire dashboard did not become ready after 30 seconds" echo "๐Ÿ’ก Check the log: $ASPIRE_LOG" 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 done # Wait for API to be ready (give it more time since Aspire needs to build first) echo "" echo "โณ Waiting for API to be ready..." API_READY=false for i in {1..90}; do if curl -s -f "http://localhost:${API_PORT}/alive" > /dev/null 2>&1; then API_READY=true echo "โœ… API is ready!" break fi if [ $i -eq 90 ]; then echo "โš ๏ธ API did not become ready after 90 seconds" echo "๐Ÿ’ก Check the log: $ASPIRE_LOG" echo "๐Ÿ’ก The API may still be building or starting" fi sleep 1 done # Print the Aspire dashboard URL in the format Vibe Kanban expects # This must be printed so Vibe Kanban can detect the server is running echo "" echo "โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”" if [ "$API_READY" = true ]; then echo "โœ… Dev server is running" else echo "โš ๏ธ Dev server started (API may still be initializing)" fi echo "โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”" echo "$ASPIRE_DASHBOARD_URL" echo "โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”" echo "" echo "๐Ÿ“Š Additional URLs:" echo " API: http://localhost:${API_PORT}" echo " Swagger UI: http://localhost:${API_PORT}/swagger" echo " Health check: http://localhost:${API_PORT}/alive" echo "โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”" echo "" # Tail the Aspire log (filter out warnings for cleaner output) echo "๐Ÿ“‹ Showing Aspire logs (Press Ctrl+C to stop and cleanup)" echo " (Warnings are hidden for cleaner output - full logs in: $ASPIRE_LOG)" echo "" # 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