package main import ( "context" "errors" "fmt" "log/slog" "net/http" "os" "os/signal" "strings" "syscall" "time" "gitea.nehmer.net/torben/gitea-mcp-forward-auth/internal/auth" ) const ( defaultListenAddr = ":8080" defaultLogLevel = "info" shutdownTimeout = 10 * time.Second ) func main() { os.Exit(run()) } func run() int { listenAddr := envOrDefault("AUTH_PROXY_LISTEN_ADDR", defaultListenAddr) hashesDir := os.Getenv("AUTH_PROXY_TOKEN_HASHES_DIR") hashesCSV := os.Getenv(auth.EnvTokenHashes) logLevel := envOrDefault("AUTH_PROXY_LOG_LEVEL", defaultLogLevel) logger, err := newLogger(logLevel) if err != nil { fmt.Fprintf(os.Stderr, "failed to initialize logger: %v\n", err) return 1 } hashes, err := auth.LoadTokenHashes(hashesDir, hashesCSV) if err != nil { logger.Error("failed to load token hashes", "error", err) logger.Error("startup aborted: configure at least one hash via AUTH_PROXY_TOKEN_HASHES_DIR and/or AUTH_PROXY_TOKEN_HASHES") return 1 } validator, err := auth.NewValidator(hashes) if err != nil { logger.Error("failed to initialize validator", "error", err) return 1 } logger.Info("loaded token set", "token_count", validator.TokenCount()) handler := auth.NewHandler(validator, logger) server := &http.Server{ Addr: listenAddr, Handler: handler, ReadHeaderTimeout: 5 * time.Second, } errCh := make(chan error, 1) go func() { errCh <- server.ListenAndServe() }() logger.Info("auth proxy started", "listen_addr", listenAddr) sigCh := make(chan os.Signal, 1) signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM) select { case sig := <-sigCh: logger.Info("shutdown signal received", "signal", sig.String()) case err := <-errCh: if err != nil && !errors.Is(err, http.ErrServerClosed) { logger.Error("server failed", "error", err) return 1 } return 0 } ctx, cancel := context.WithTimeout(context.Background(), shutdownTimeout) defer cancel() if err := server.Shutdown(ctx); err != nil { logger.Error("graceful shutdown failed", "error", err) return 1 } logger.Info("server stopped") return 0 } func newLogger(levelRaw string) (*slog.Logger, error) { level := new(slog.LevelVar) if err := level.UnmarshalText([]byte(strings.ToUpper(strings.TrimSpace(levelRaw)))); err != nil { return nil, fmt.Errorf("parse log level %q: %w", levelRaw, err) } handler := slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: level}) return slog.New(handler), nil } func envOrDefault(name, fallback string) string { v := strings.TrimSpace(os.Getenv(name)) if v == "" { return fallback } return v }