package main import ( "context" "errors" "flag" "log" "net/http" "os" "os/signal" "syscall" "time" ) const defaultListenAddr = ":8080" const defaultConfigPath = "/etc/homelab-gateway/bots.yaml" func main() { subcmd := "" if len(os.Args) > 1 && os.Args[1] != "" && os.Args[1][0] != '-' { subcmd = os.Args[1] os.Args = append([]string{os.Args[0]}, os.Args[2:]...) } switch subcmd { case "setwebhook": runSetWebhook() case "deletewebhook": runDeleteWebhook() case "", "serve": runServer() default: log.Fatalf("unknown subcommand: %s (expected: serve | setwebhook | deletewebhook)", subcmd) } } func runServer() { addr := flag.String("addr", envOr("LISTEN_ADDR", defaultListenAddr), "listen address") configPath := flag.String("config", envOr("CONFIG_PATH", defaultConfigPath), "bot routing config (YAML)") flag.Parse() cfg, err := LoadConfig(*configPath) if err != nil { log.Fatalf("load config: %v", err) } registry, err := NewRegistry(cfg) if err != nil { log.Fatalf("build registry: %v", err) } srv := &http.Server{ Addr: *addr, Handler: NewServer(registry).Routes(), ReadHeaderTimeout: 5 * time.Second, ReadTimeout: 30 * time.Second, WriteTimeout: 30 * time.Second, IdleTimeout: 60 * time.Second, } ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM) defer stop() go func() { log.Printf("homelab-gateway listening on %s (%d bot(s) loaded)", *addr, registry.Count()) if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { log.Fatalf("server: %v", err) } }() <-ctx.Done() log.Print("shutdown signal received, draining...") shutdownCtx, cancel := context.WithTimeout(context.Background(), 15*time.Second) defer cancel() if err := srv.Shutdown(shutdownCtx); err != nil { log.Printf("graceful shutdown error: %v", err) } log.Print("bye") } func envOr(key, fallback string) string { if v := os.Getenv(key); v != "" { return v } return fallback }