package main import ( "context" "crypto/x509" "encoding/pem" "errors" "fmt" "log/slog" "net/http" "os" "os/signal" "strings" "syscall" "time" "github.com/anthropic/oidc-platform/internal/config" "github.com/anthropic/oidc-platform/internal/router" ) func main() { if err := run(); err != nil { slog.Error("server failed", "error", err) os.Exit(1) } } func run() error { cfg, err := config.Load() if err != nil { return fmt.Errorf("load config: %w", err) } setupLogger(cfg.Log) ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGINT, syscall.SIGTERM) defer stop() // Note: actual DB/Redis/repository/service initialization is wired up via the // adapter and service layers (tasks #3-#5). The bootstrap below shows the // composition of HTTP layer pieces; concrete wiring happens via the helpers // exposed by the adapter and service packages. deps, cleanup, err := bootstrap(ctx, cfg) if err != nil { return err } defer cleanup() r := router.NewRouter(deps) addr := fmt.Sprintf("%s:%d", cfg.Server.Host, cfg.Server.Port) srv := &http.Server{ Addr: addr, Handler: r, ReadTimeout: cfg.Server.ReadTimeout, WriteTimeout: cfg.Server.WriteTimeout, } serverErrCh := make(chan error, 1) go func() { slog.Info("http server listening", "addr", addr) if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { serverErrCh <- err } close(serverErrCh) }() select { case err := <-serverErrCh: return err case <-ctx.Done(): slog.Info("shutdown signal received") } shutdownTimeout := cfg.Server.ShutdownTimeout if shutdownTimeout == 0 { shutdownTimeout = 30 * time.Second } shutdownCtx, cancel := context.WithTimeout(context.Background(), shutdownTimeout) defer cancel() if err := srv.Shutdown(shutdownCtx); err != nil { return fmt.Errorf("graceful shutdown: %w", err) } slog.Info("server stopped") return nil } func setupLogger(cfg config.LogConfig) { var level slog.Level switch strings.ToLower(cfg.Level) { case "debug": level = slog.LevelDebug case "warn": level = slog.LevelWarn case "error": level = slog.LevelError default: level = slog.LevelInfo } var h slog.Handler opts := &slog.HandlerOptions{Level: level} if strings.ToLower(cfg.Format) == "text" { h = slog.NewTextHandler(os.Stdout, opts) } else { h = slog.NewJSONHandler(os.Stdout, opts) } slog.SetDefault(slog.New(h)) } // parsePrivateKey parses a PEM-encoded RSA private key. func parsePrivateKey(pemBytes []byte) (any, error) { block, _ := pem.Decode(pemBytes) if block == nil { return nil, errors.New("no PEM block") } if key, err := x509.ParsePKCS1PrivateKey(block.Bytes); err == nil { return key, nil } return x509.ParsePKCS8PrivateKey(block.Bytes) }