diff --git a/scripts/vibe-kanban/vibe-dev-server.sh b/scripts/vibe-kanban/vibe-dev-server.sh index ef483e65..266b4c8e 100755 --- a/scripts/vibe-kanban/vibe-dev-server.sh +++ b/scripts/vibe-kanban/vibe-dev-server.sh @@ -127,11 +127,59 @@ export TASK_ID="$TASK_ID" export PORT_OFFSET="$PORT_OFFSET" export TASK_SLOT="$TASK_SLOT" -# Change to AppHost directory +# 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) +# 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" +export ASPNETCORE_ENVIRONMENT="Development" +export DOTNET_ENVIRONMENT="Development" + +# 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 "" # Run Aspire (this will start the API and Workers) -echo "" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "🚀 Starting Aspire..." echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" @@ -141,8 +189,9 @@ echo "" ASPIRE_LOG="$WORKTREE_PROJECT_ROOT/.task-pids/aspire-${TASK_ID}.log" mkdir -p "$(dirname "$ASPIRE_LOG")" -# Start Aspire -dotnet run > "$ASPIRE_LOG" 2>&1 & +# Start Aspire using the "http" launch profile (HTTP only, no HTTPS) +# 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 @@ -201,9 +250,9 @@ for i in {1..120}; do # Show progress every 10 seconds if [ $((i % 10)) -eq 0 ]; then echo " Still starting... (${i}/120 seconds)" - # Show last few lines of log for progress + # Show last few lines of log for progress (filter warnings) if [ -f "$ASPIRE_LOG" ]; then - LAST_LINE=$(tail -1 "$ASPIRE_LOG" 2>/dev/null | cut -c1-80) + 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 @@ -213,8 +262,8 @@ for i in {1..120}; do if [ $i -eq 120 ]; then echo "⚠️ Aspire dashboard did not become ready after 120 seconds" echo "💡 Check the log: $ASPIRE_LOG" - echo "💡 Last 10 lines of log:" - tail -10 "$ASPIRE_LOG" 2>/dev/null || echo " (log file not found)" + 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 @@ -269,10 +318,11 @@ echo " Health check: http://localhost:${API_PORT}/alive" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "" -# Tail the Aspire log +# Tail the Aspire log (filter out warnings for cleaner output) echo "📋 Showing Aspire logs (Press Ctrl+C to stop)" +echo " (Warnings are hidden for cleaner output - full logs in: $ASPIRE_LOG)" echo "" -tail -f "$ASPIRE_LOG" 2>/dev/null || { +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 diff --git a/src/Managing.AppHost/Managing.AppHost.csproj b/src/Managing.AppHost/Managing.AppHost.csproj index 981645df..c220ba97 100644 --- a/src/Managing.AppHost/Managing.AppHost.csproj +++ b/src/Managing.AppHost/Managing.AppHost.csproj @@ -12,6 +12,10 @@ + + + + diff --git a/src/Managing.AppHost/Program.cs b/src/Managing.AppHost/Program.cs index 817a84ed..885c0bf1 100644 --- a/src/Managing.AppHost/Program.cs +++ b/src/Managing.AppHost/Program.cs @@ -1,31 +1,152 @@ -using Aspire.Hosting; +using DotNetEnv; +using Microsoft.Extensions.Configuration; + +// Detect running mode: IDE mode (appsettings + .env) or Vibe-kanban mode (env vars) +var isVibeKanbanMode = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("TASK_ID")) + && Environment.GetEnvironmentVariable("TASK_ID") != "DEFAULT"; + +string taskId; +int portOffset; +string taskSlot; +int apiPort; +int postgresPort; +int redisPort; +int orleansSiloPort; +int orleansGatewayPort; +int orleansDashboardPort; +string dbName; +string orleansDbName; +string postgresConnectionString; +string postgresOrleansConnectionString; +string redisConnectionString; +string influxDbUrl; +string influxDbToken; + +if (isVibeKanbanMode) +{ + // Vibe-kanban mode: Use environment variables directly + Console.WriteLine("🔧 Running in Vibe-kanban mode (using environment variables)"); + + taskId = Environment.GetEnvironmentVariable("TASK_ID") ?? "DEFAULT"; + portOffset = int.Parse(Environment.GetEnvironmentVariable("PORT_OFFSET") ?? "0"); + taskSlot = Environment.GetEnvironmentVariable("TASK_SLOT") ?? "1"; + + // Calculate ports based on task configuration + apiPort = 5000 + portOffset; + postgresPort = 5432 + portOffset; + redisPort = 6379 + portOffset; + + // Calculate Orleans ports from TASK_SLOT + var taskSlotInt = int.Parse(taskSlot); + orleansSiloPort = 11111 + (taskSlotInt - 1) * 10; + orleansGatewayPort = 30000 + (taskSlotInt - 1) * 10; + orleansDashboardPort = 9999 + (taskSlotInt - 1); + + // Database names + dbName = $"managing_{taskId.ToLower()}"; + orleansDbName = $"orleans_{taskId.ToLower()}"; + + // Connection strings (using existing Docker containers managed by Docker Compose) + postgresConnectionString = $"Host=localhost;Port={postgresPort};Database={dbName};Username=postgres;Password=postgres"; + postgresOrleansConnectionString = $"Host=localhost;Port={postgresPort};Database={orleansDbName};Username=postgres;Password=postgres"; + redisConnectionString = $"localhost:{redisPort}"; + + // InfluxDB from environment or defaults + influxDbUrl = Environment.GetEnvironmentVariable("InfluxDb__Url") ?? "http://localhost:8086/"; + influxDbToken = Environment.GetEnvironmentVariable("InfluxDb__Token") ?? "Fw2FPL2OwTzDHzSbR2Sd5xs0EKQYy00Q-hYKYAhr9cC1_q5YySONpxuf_Ck0PTjyUiF13xXmi__bu_pXH-H9zA=="; +} +else +{ + // IDE mode: Load .env file and optional appsettings.json + Console.WriteLine("🔧 Running in IDE mode (using environment variables, .env, and optional appsettings.json)"); + + // Load .env file if it exists (optional) + var enableEnvFile = Environment.GetEnvironmentVariable("ENABLE_ENV_FILE") != "false"; + if (enableEnvFile) + { + var envFilePaths = new[] + { + Path.Combine(Directory.GetCurrentDirectory(), ".env"), + Path.Combine(AppContext.BaseDirectory, ".env"), + Path.GetFullPath(Path.Combine(AppContext.BaseDirectory, "..", "..", "..", "..", ".env")), + Path.GetFullPath(Path.Combine(Directory.GetCurrentDirectory(), "..", "..", "..", ".env")), // From AppHost to project root + }; + + string? loadedEnvPath = null; + foreach (var envPath in envFilePaths) + { + if (File.Exists(envPath)) + { + try + { + Env.Load(envPath); + loadedEnvPath = envPath; + Console.WriteLine($"✅ Loaded .env file from: {envPath}"); + break; + } + catch (Exception ex) + { + Console.WriteLine($"⚠️ Failed to load .env file from {envPath}: {ex.Message}"); + } + } + } + } + + // Build configuration from appsettings.json (optional) and environment variables + // appsettings.json is optional because environment variables and .env files take precedence + var configBuilder = new ConfigurationBuilder() + .SetBasePath(AppContext.BaseDirectory) + .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) + .AddJsonFile("appsettings.Development.json", optional: true, reloadOnChange: true) + .AddEnvironmentVariables(); + + var configuration = configBuilder.Build(); + + // Read configuration values + taskId = configuration["TASK_ID"] ?? Environment.GetEnvironmentVariable("TASK_ID") ?? "DEFAULT"; + portOffset = int.Parse(configuration["PORT_OFFSET"] ?? Environment.GetEnvironmentVariable("PORT_OFFSET") ?? "0"); + taskSlot = configuration["TASK_SLOT"] ?? Environment.GetEnvironmentVariable("TASK_SLOT") ?? "1"; + + // Calculate ports based on task configuration + apiPort = 5000 + portOffset; + postgresPort = 5432 + portOffset; + redisPort = 6379 + portOffset; + + // Calculate Orleans ports from TASK_SLOT + var taskSlotInt = int.Parse(taskSlot); + orleansSiloPort = 11111 + (taskSlotInt - 1) * 10; + orleansGatewayPort = 30000 + (taskSlotInt - 1) * 10; + orleansDashboardPort = 9999 + (taskSlotInt - 1); + + // Database names + dbName = $"managing_{taskId.ToLower()}"; + orleansDbName = $"orleans_{taskId.ToLower()}"; + + // Connection strings from configuration or defaults + postgresConnectionString = configuration["PostgreSql:ConnectionString"] + ?? configuration["PostgreSql__ConnectionString"] + ?? $"Host=localhost;Port={postgresPort};Database={dbName};Username=postgres;Password=postgres"; + + postgresOrleansConnectionString = configuration["PostgreSql:Orleans"] + ?? configuration["PostgreSql__Orleans"] + ?? $"Host=localhost;Port={postgresPort};Database={orleansDbName};Username=postgres;Password=postgres"; + + redisConnectionString = configuration["Redis:ConnectionString"] + ?? configuration["Redis__ConnectionString"] + ?? $"localhost:{redisPort}"; + + // InfluxDB from configuration + influxDbUrl = configuration["InfluxDb:Url"] + ?? configuration["InfluxDb__Url"] + ?? "http://localhost:8086/"; + + influxDbToken = configuration["InfluxDb:Token"] + ?? configuration["InfluxDb__Token"] + ?? "Fw2FPL2OwTzDHzSbR2Sd5xs0EKQYy00Q-hYKYAhr9cC1_q5YySONpxuf_Ck0PTjyUiF13xXmi__bu_pXH-H9zA=="; +} var builder = DistributedApplication.CreateBuilder(args); -// Get task-specific configuration from environment variables -var taskId = Environment.GetEnvironmentVariable("TASK_ID") ?? "DEFAULT"; -var portOffset = int.Parse(Environment.GetEnvironmentVariable("PORT_OFFSET") ?? "0"); -var taskSlot = Environment.GetEnvironmentVariable("TASK_SLOT") ?? "1"; - -// Calculate ports based on task configuration -var apiPort = 5000 + portOffset; -var postgresPort = 5432 + portOffset; -var redisPort = 6379 + portOffset; - -// Calculate Orleans ports from TASK_SLOT -var taskSlotInt = int.Parse(taskSlot); -var orleansSiloPort = 11111 + (taskSlotInt - 1) * 10; -var orleansGatewayPort = 30000 + (taskSlotInt - 1) * 10; -var orleansDashboardPort = 9999 + (taskSlotInt - 1); - -// Database names -var dbName = $"managing_{taskId.ToLower()}"; -var orleansDbName = $"orleans_{taskId.ToLower()}"; - -// Connection strings (using existing Docker containers managed by Docker Compose) -var postgresConnectionString = $"Host=localhost;Port={postgresPort};Database={dbName};Username=postgres;Password=postgres"; -var postgresOrleansConnectionString = $"Host=localhost;Port={postgresPort};Database={orleansDbName};Username=postgres;Password=postgres"; -var redisConnectionString = $"localhost:{redisPort}"; // Add API project var api = builder.AddProject("api", "../Managing.Api/Managing.Api.csproj") @@ -39,8 +160,8 @@ var api = builder.AddProject("api", "../Managing.Api/Managing.Api.csproj") .WithEnvironment("SILO_ROLE", "Trading") .WithEnvironment("PostgreSql__ConnectionString", postgresConnectionString) .WithEnvironment("PostgreSql__Orleans", postgresOrleansConnectionString) - .WithEnvironment("InfluxDb__Url", "http://localhost:8086/") - .WithEnvironment("InfluxDb__Token", "Fw2FPL2OwTzDHzSbR2Sd5xs0EKQYy00Q-hYKYAhr9cC1_q5YySONpxuf_Ck0PTjyUiF13xXmi__bu_pXH-H9zA==") + .WithEnvironment("InfluxDb__Url", influxDbUrl) + .WithEnvironment("InfluxDb__Token", influxDbToken) .WithEnvironment("ORLEANS_SILO_PORT", orleansSiloPort.ToString()) .WithEnvironment("ORLEANS_GATEWAY_PORT", orleansGatewayPort.ToString()) .WithEnvironment("ORLEANS_DASHBOARD_PORT", orleansDashboardPort.ToString()); @@ -51,8 +172,8 @@ var workers = builder.AddProject("workers", "../Managing.Workers/Managing.Worker .WithEnvironment("TASK_SLOT", taskSlot) .WithEnvironment("ASPNETCORE_ENVIRONMENT", "Development") .WithEnvironment("PostgreSql__ConnectionString", postgresConnectionString) - .WithEnvironment("InfluxDb__Url", "http://localhost:8086/") - .WithEnvironment("InfluxDb__Token", "Fw2FPL2OwTzDHzSbR2Sd5xs0EKQYy00Q-hYKYAhr9cC1_q5YySONpxuf_Ck0PTjyUiF13xXmi__bu_pXH-H9zA=="); + .WithEnvironment("InfluxDb__Url", influxDbUrl) + .WithEnvironment("InfluxDb__Token", influxDbToken); // Build and run builder.Build().Run();