package main import ( "context" "flag" "fmt" "net/http" "os" "os/signal" "strings" "syscall" "time" "github.com/chorus-services/whoosh/internal/config" "github.com/chorus-services/whoosh/internal/database" "github.com/chorus-services/whoosh/internal/server" "github.com/chorus-services/whoosh/internal/tracing" "github.com/kelseyhightower/envconfig" "github.com/rs/zerolog" "github.com/rs/zerolog/log" ) const ( serviceName = "whoosh" ) var ( // Build-time variables (set via ldflags) version = "0.1.1-debug" commitHash = "unknown" buildDate = "unknown" ) func main() { // Parse command line flags var ( healthCheck = flag.Bool("health-check", false, "Run health check and exit") showVersion = flag.Bool("version", false, "Show version information and exit") ) flag.Parse() // Handle version flag if *showVersion { fmt.Printf("WHOOSH %s\n", version) fmt.Printf("Commit: %s\n", commitHash) fmt.Printf("Built: %s\n", buildDate) return } // Handle health check flag if *healthCheck { if err := runHealthCheck(); err != nil { log.Fatal().Err(err).Msg("Health check failed") } return } // Configure structured logging setupLogging() log.Info(). Str("service", serviceName). Str("version", version). Str("commit", commitHash). Str("build_date", buildDate). Msg("🎭 Starting WHOOSH - Autonomous AI Development Teams") // Load configuration var cfg config.Config // Debug: Print all environment variables starting with WHOOSH log.Debug().Msg("Environment variables:") for _, env := range os.Environ() { if strings.HasPrefix(env, "WHOOSH_") { // Don't log passwords in full, just indicate they exist if strings.Contains(env, "PASSWORD") { parts := strings.SplitN(env, "=", 2) if len(parts) == 2 && len(parts[1]) > 0 { log.Debug().Str("env", parts[0]+"=[REDACTED]").Msg("Found password env var") } } else { log.Debug().Str("env", env).Msg("Found env var") } } } if err := envconfig.Process("whoosh", &cfg); err != nil { log.Fatal().Err(err).Msg("Failed to load configuration") } // Validate configuration if err := cfg.Validate(); err != nil { log.Fatal().Err(err).Msg("Invalid configuration") } log.Info(). Str("listen_addr", cfg.Server.ListenAddr). Str("database_host", cfg.Database.Host). Msg("📋 Configuration loaded") // Initialize database db, err := database.NewPostgresDB(cfg.Database) if err != nil { log.Fatal().Err(err).Msg("Failed to initialize database") } defer db.Close() log.Info().Msg("🗄️ Database connection established") // Run migrations if cfg.Database.AutoMigrate { log.Info().Msg("🔄 Running database migrations...") if err := database.RunMigrations(cfg.Database.URL); err != nil { log.Fatal().Err(err).Msg("Database migration failed") } log.Info().Msg("✅ Database migrations completed") } // Initialize tracing tracingCleanup, err := tracing.Initialize(cfg.OpenTelemetry) if err != nil { log.Fatal().Err(err).Msg("Failed to initialize tracing") } defer tracingCleanup() if cfg.OpenTelemetry.Enabled { log.Info(). Str("jaeger_endpoint", cfg.OpenTelemetry.JaegerEndpoint). Msg("🔍 OpenTelemetry tracing enabled") } else { log.Info().Msg("🔍 OpenTelemetry tracing disabled (no-op tracer)") } // Set version for server server.SetVersion(version) // Initialize server srv, err := server.NewServer(&cfg, db) if err != nil { log.Fatal().Err(err).Msg("Failed to create server") } // Start server ctx, cancel := context.WithCancel(context.Background()) defer cancel() go func() { log.Info(). Str("addr", cfg.Server.ListenAddr). Msg("🌐 Starting HTTP server") if err := srv.Start(ctx); err != nil { log.Error().Err(err).Msg("Server startup failed") cancel() } }() // Wait for shutdown signal sigChan := make(chan os.Signal, 1) signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM) select { case sig := <-sigChan: log.Info().Str("signal", sig.String()).Msg("🛑 Shutdown signal received") case <-ctx.Done(): log.Info().Msg("🛑 Context cancelled") } // Graceful shutdown log.Info().Msg("🔄 Starting graceful shutdown...") shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 30*time.Second) defer shutdownCancel() if err := srv.Shutdown(shutdownCtx); err != nil { log.Error().Err(err).Msg("Server shutdown failed") } log.Info().Msg("✅ WHOOSH shutdown complete") } func runHealthCheck() error { // Simple health check - try to connect to health endpoint client := &http.Client{Timeout: 5 * time.Second} // Use localhost for health check healthURL := "http://localhost:8080/health" resp, err := client.Get(healthURL) if err != nil { return fmt.Errorf("health check request failed: %w", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return fmt.Errorf("health check returned status %d", resp.StatusCode) } return nil } func setupLogging() { // Configure zerolog for structured logging zerolog.TimeFieldFormat = zerolog.TimeFormatUnix // Set log level from environment level := os.Getenv("LOG_LEVEL") switch level { case "debug": zerolog.SetGlobalLevel(zerolog.DebugLevel) case "info": zerolog.SetGlobalLevel(zerolog.InfoLevel) case "warn": zerolog.SetGlobalLevel(zerolog.WarnLevel) case "error": zerolog.SetGlobalLevel(zerolog.ErrorLevel) default: zerolog.SetGlobalLevel(zerolog.InfoLevel) } // Pretty logging for development if os.Getenv("ENVIRONMENT") == "development" { log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}) } }