package main import ( "context" "embed" "flag" "fmt" "io/fs" "net/http" "os" "os/signal" "syscall" "time" "github.com/rs/zerolog" "github.com/rs/zerolog/log" "github.com/seu-usuario/go-react-web-tail/internal/broker" "github.com/seu-usuario/go-react-web-tail/internal/config" "github.com/seu-usuario/go-react-web-tail/internal/handlers" "github.com/seu-usuario/go-react-web-tail/internal/middleware" "github.com/seu-usuario/go-react-web-tail/internal/tail" ) //go:embed dist var staticFiles embed.FS var version = "2.0.0" func main() { if err := run(); err != nil { log.Fatal().Err(err).Msg("Application failed") } } func run() error { // Carregar configuração cfg, err := config.Load() if err != nil { return fmt.Errorf("failed to load config: %w", err) } // Configurar logging setupLogging(cfg.Logging) log.Info(). Str("version", version). Str("port", cfg.Server.Port). Bool("auth_enabled", cfg.Auth.Enabled). Bool("tls_enabled", cfg.Server.TLSEnabled). Bool("rate_limit_enabled", cfg.Security.RateLimitEnabled). Msg("Starting Web Tail Pro") // Obter arquivos de log dos argumentos logFiles := flag.Args() if len(logFiles) < 1 { fmt.Println("Uso: web-tail-pro [opções] ...") fmt.Println("\nOpções:") flag.PrintDefaults() fmt.Println("\nVariáveis de Ambiente:") fmt.Println(" PORT - Porta do servidor (padrão: :8080)") fmt.Println(" USERNAME - Nome de usuário (padrão: admin)") fmt.Println(" PASSWORD - Senha de autenticação") fmt.Println(" LOG_LEVEL - Nível de log: debug, info, warn, error (padrão: info)") fmt.Println(" LOG_FORMAT - Formato: json, console (padrão: console)") fmt.Println(" RATE_LIMIT_ENABLED - Habilitar rate limiting (padrão: true)") fmt.Println(" RATE_LIMIT_RPS - Requisições por segundo (padrão: 100)") fmt.Println(" CORS_ORIGINS - Origens CORS permitidas, separadas por vírgula") fmt.Println(" TLS_ENABLED - Habilitar TLS (padrão: false)") fmt.Println(" TLS_CERT_FILE - Arquivo de certificado TLS") fmt.Println(" TLS_KEY_FILE - Arquivo de chave TLS") os.Exit(1) } // Criar context para graceful shutdown ctx, cancel := context.WithCancel(context.Background()) defer cancel() // Inicializar broker brk := broker.New(ctx) // Inicializar tailer tailer := tail.New(ctx, logFiles, brk.Broadcast) if err := tailer.Start(); err != nil { return fmt.Errorf("failed to start tailer: %w", err) } // Configurar servidor HTTP server, err := setupServer(cfg, brk, logFiles) if err != nil { return fmt.Errorf("failed to setup server: %w", err) } // Iniciar servidor em goroutine serverErrors := make(chan error, 1) go func() { log.Info().Str("address", cfg.Server.Port).Msg("HTTP server listening") if cfg.Server.TLSEnabled { serverErrors <- server.ListenAndServeTLS(cfg.Server.TLSCertFile, cfg.Server.TLSKeyFile) } else { serverErrors <- server.ListenAndServe() } }() // Aguardar sinal de shutdown shutdown := make(chan os.Signal, 1) signal.Notify(shutdown, syscall.SIGINT, syscall.SIGTERM) select { case err := <-serverErrors: return fmt.Errorf("server error: %w", err) case sig := <-shutdown: log.Info().Str("signal", sig.String()).Msg("Shutdown signal received") // Graceful shutdown shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), cfg.Server.ShutdownTimeout) defer shutdownCancel() log.Info().Msg("Shutting down HTTP server...") if err := server.Shutdown(shutdownCtx); err != nil { log.Error().Err(err).Msg("HTTP server shutdown error") } log.Info().Msg("Stopping tailer...") if err := tailer.Shutdown(); err != nil { log.Error().Err(err).Msg("Tailer shutdown error") } log.Info().Msg("Stopping broker...") brk.Shutdown() log.Info().Msg("Shutdown complete") } return nil } func setupLogging(cfg config.LoggingConfig) { // Configurar nível de log level, err := zerolog.ParseLevel(cfg.Level) if err != nil { level = zerolog.InfoLevel } zerolog.SetGlobalLevel(level) // Configurar formato if cfg.Format == "console" { log.Logger = log.Output(zerolog.ConsoleWriter{ Out: os.Stderr, TimeFormat: time.RFC3339, }) } } func setupServer(cfg *config.Config, brk *broker.Broker, logFiles []string) (*http.Server, error) { // Configurar handlers h := handlers.New(brk, logFiles) // Configurar arquivos estáticos distFS, err := fs.Sub(staticFiles, "dist") if err != nil { return nil, fmt.Errorf("failed to create sub-filesystem: %w", err) } // Configurar rotas mux := http.NewServeMux() mux.Handle("/", http.FileServer(http.FS(distFS))) mux.HandleFunc("/logs", h.HandleSSE) mux.HandleFunc("/api/files", h.HandleFiles) mux.HandleFunc("/api/last", h.HandleLastLines) mux.HandleFunc("/health", h.HandleHealth) mux.HandleFunc("/metrics", h.HandleMetrics) // Aplicar cadeia de middleware var handler http.Handler = mux // Security headers handler = middleware.SecurityHeadersMiddleware(handler) // CORS corsMiddleware := middleware.NewCORSMiddleware(cfg.Security.CORSOrigins) handler = corsMiddleware.Handler(handler) // Rate limiting if cfg.Security.RateLimitEnabled { rateLimitMiddleware := middleware.NewRateLimitMiddleware(cfg.Security.RateLimitRPS) handler = rateLimitMiddleware.Handler(handler) log.Info().Int("rps", cfg.Security.RateLimitRPS).Msg("Rate limiting enabled") } // Authentication authMiddleware := middleware.NewAuthMiddleware(cfg.Auth.Enabled, cfg.Auth.Username, cfg.Auth.Password) handler = authMiddleware.Handler(handler) // Logging handler = middleware.LoggingMiddleware(handler) // Criar servidor server := &http.Server{ Addr: cfg.Server.Port, Handler: handler, ReadTimeout: cfg.Server.ReadTimeout, WriteTimeout: cfg.Server.WriteTimeout, } return server, nil }