diff --git a/Makefile b/Makefile index c41a390..23b44c6 100644 --- a/Makefile +++ b/Makefile @@ -51,8 +51,7 @@ deps: # Run the application locally .PHONY: run run: - $(GOBUILD) -o $(BINARY_NAME) $(MAIN_PATH) - ./$(BINARY_NAME) + go run $(MAIN_PATH)/main.go # Development setup .PHONY: setup diff --git a/cmd/teleport-discord-bot/main.go b/cmd/teleport-discord-bot/main.go index e13d906..329d574 100644 --- a/cmd/teleport-discord-bot/main.go +++ b/cmd/teleport-discord-bot/main.go @@ -9,6 +9,7 @@ import ( "github.com/dwarvesf/teleport-discord-bot/internal/config" "github.com/dwarvesf/teleport-discord-bot/internal/discord" + "github.com/dwarvesf/teleport-discord-bot/internal/httpserver" "github.com/dwarvesf/teleport-discord-bot/internal/teleport" ) @@ -20,6 +21,14 @@ func main() { os.Exit(1) } + // Create HTTP server + httpServer := httpserver.NewServer(cfg.Port) + defer func() { + if shutdownErr := httpServer.Shutdown(context.Background()); shutdownErr != nil { + fmt.Fprintf(os.Stderr, "Error shutting down HTTP server: %v\n", shutdownErr) + } + }() + // Create Discord client discordClient := discord.NewClient(cfg) @@ -45,6 +54,12 @@ func main() { errChan <- plugin.Run(ctx) }() + // Start the HTTP server + if err := httpServer.Start(); err != nil { + fmt.Fprintf(os.Stderr, "Failed to start HTTP server: %v\n", err) + os.Exit(1) + } + // Wait for either a signal or an error select { case sig := <-sigChan: diff --git a/internal/config/config.go b/internal/config/config.go index a74e284..01c8e98 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -12,7 +12,8 @@ type Config struct { ProxyAddr string DiscordWebhookURL string WatcherList string - AuthPemPath string + AuthPem string + Port string } // Load reads configuration from environment variables @@ -26,7 +27,11 @@ func Load() (*Config, error) { ProxyAddr: os.Getenv("PROXY_ADDR"), DiscordWebhookURL: os.Getenv("DISCORD_WEBHOOK_URL"), WatcherList: os.Getenv("WATCHER_LIST"), - AuthPemPath: os.Getenv("AUTH_PEM_PATH"), + AuthPem: os.Getenv("AUTH_PEM"), + Port: "8080", + } + if os.Getenv("PORT") != "" { + cfg.Port = os.Getenv("PORT") } // Validate required configuration @@ -36,8 +41,8 @@ func Load() (*Config, error) { if cfg.DiscordWebhookURL == "" { return nil, fmt.Errorf("DISCORD_WEBHOOK_URL is required") } - if cfg.AuthPemPath == "" { - return nil, fmt.Errorf("AUTH_PEM_PATH is required") + if cfg.AuthPem == "" { + return nil, fmt.Errorf("AUTH_PEM is required") } return cfg, nil diff --git a/internal/httpserver/server.go b/internal/httpserver/server.go new file mode 100644 index 0000000..77c7034 --- /dev/null +++ b/internal/httpserver/server.go @@ -0,0 +1,61 @@ +package httpserver + +import ( + "context" + "fmt" + "net/http" + "sync" +) + +// Server represents an HTTP server for health checks and other utilities +type Server struct { + server *http.Server + port string + mu sync.Mutex +} + +// NewServer creates a new HTTP server with a healthz endpoint +func NewServer(port string) *Server { + mux := http.NewServeMux() + s := &Server{ + port: port, + server: &http.Server{ + Addr: fmt.Sprintf(":%v", port), + Handler: mux, + }, + } + + // Add healthz endpoint + mux.HandleFunc("/healthz", s.healthzHandler) + + return s +} + +// healthzHandler responds with a 200 OK status for health checks +func (s *Server) healthzHandler(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + w.Write([]byte("OK")) +} + +// Start starts the HTTP server in a separate goroutine +func (s *Server) Start() error { + s.mu.Lock() + defer s.mu.Unlock() + + fmt.Printf("Starting HTTP server on port %v\n", s.port) + go func() { + if err := s.server.ListenAndServe(); err != http.ErrServerClosed { + fmt.Printf("HTTP server error: %v\n", err) + } + }() + + return nil +} + +// Shutdown gracefully shuts down the HTTP server +func (s *Server) Shutdown(ctx context.Context) error { + s.mu.Lock() + defer s.mu.Unlock() + + return s.server.Shutdown(ctx) +} diff --git a/internal/teleport/plugin.go b/internal/teleport/plugin.go index ba25eb9..7b7c1ec 100644 --- a/internal/teleport/plugin.go +++ b/internal/teleport/plugin.go @@ -2,6 +2,7 @@ package teleport import ( "context" + "encoding/base64" "fmt" "github.com/gravitational/teleport/api/client" @@ -29,11 +30,16 @@ type Plugin struct { func NewPlugin(cfg *config.Config, eventHandler EventHandler) (*Plugin, error) { ctx := context.Background() + content, err := base64.StdEncoding.DecodeString(cfg.AuthPem) + if err != nil { + return nil, trace.Wrap(err, "failed to decode pem file") + } + // Create a new Teleport client teleportClient, err := client.New(ctx, client.Config{ Addrs: []string{cfg.ProxyAddr}, Credentials: []client.Credentials{ - client.LoadIdentityFile(cfg.AuthPemPath), + client.LoadIdentityFileFromString(string(content)), }, DialOpts: []grpc.DialOption{ grpc.WithReturnConnectionError(),