diff --git a/config.example.yaml b/config.example.yaml index 9b9745ac..8457e103 100644 --- a/config.example.yaml +++ b/config.example.yaml @@ -1,6 +1,12 @@ # Server port port: 8317 +# TLS settings for HTTPS. When enabled, the server listens with the provided certificate and key. +tls: + enable: false + cert: "" + key: "" + # Management API settings remote-management: # Whether to allow remote (non-localhost) management access. diff --git a/internal/api/handlers/management/auth_files.go b/internal/api/handlers/management/auth_files.go index f0ee1a08..824e3fb0 100644 --- a/internal/api/handlers/management/auth_files.go +++ b/internal/api/handlers/management/auth_files.go @@ -235,7 +235,11 @@ func (h *Handler) managementCallbackURL(path string) (string, error) { if !strings.HasPrefix(path, "/") { path = "/" + path } - return fmt.Sprintf("http://127.0.0.1:%d%s", h.cfg.Port, path), nil + scheme := "http" + if h.cfg.TLS.Enable { + scheme = "https" + } + return fmt.Sprintf("%s://127.0.0.1:%d%s", scheme, h.cfg.Port, path), nil } func (h *Handler) ListAuthFiles(c *gin.Context) { diff --git a/internal/api/server.go b/internal/api/server.go index 0583eee1..3dd78c93 100644 --- a/internal/api/server.go +++ b/internal/api/server.go @@ -694,17 +694,33 @@ func (s *Server) unifiedModelsHandler(openaiHandler *openai.OpenAIAPIHandler, cl } } -// Start begins listening for and serving HTTP requests. +// Start begins listening for and serving HTTP or HTTPS requests. // It's a blocking call and will only return on an unrecoverable error. // // Returns: // - error: An error if the server fails to start func (s *Server) Start() error { - log.Debugf("Starting API server on %s", s.server.Addr) + if s == nil || s.server == nil { + return fmt.Errorf("failed to start HTTP server: server not initialized") + } - // Start the HTTP server. - if err := s.server.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { - return fmt.Errorf("failed to start HTTP server: %v", err) + useTLS := s.cfg != nil && s.cfg.TLS.Enable + if useTLS { + cert := strings.TrimSpace(s.cfg.TLS.Cert) + key := strings.TrimSpace(s.cfg.TLS.Key) + if cert == "" || key == "" { + return fmt.Errorf("failed to start HTTPS server: tls.cert or tls.key is empty") + } + log.Debugf("Starting API server on %s with TLS", s.server.Addr) + if errServeTLS := s.server.ListenAndServeTLS(cert, key); errServeTLS != nil && !errors.Is(errServeTLS, http.ErrServerClosed) { + return fmt.Errorf("failed to start HTTPS server: %v", errServeTLS) + } + return nil + } + + log.Debugf("Starting API server on %s", s.server.Addr) + if errServe := s.server.ListenAndServe(); errServe != nil && !errors.Is(errServe, http.ErrServerClosed) { + return fmt.Errorf("failed to start HTTP server: %v", errServe) } return nil diff --git a/internal/config/config.go b/internal/config/config.go index 726e585f..31920075 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -23,6 +23,9 @@ type Config struct { // Port is the network port on which the API server will listen. Port int `yaml:"port" json:"-"` + // TLS config controls HTTPS server settings. + TLS TLSConfig `yaml:"tls" json:"tls"` + // AmpUpstreamURL defines the upstream Amp control plane used for non-provider calls. AmpUpstreamURL string `yaml:"amp-upstream-url" json:"amp-upstream-url"` @@ -82,6 +85,16 @@ type Config struct { Payload PayloadConfig `yaml:"payload" json:"payload"` } +// TLSConfig holds HTTPS server settings. +type TLSConfig struct { + // Enable toggles HTTPS server mode. + Enable bool `yaml:"enable" json:"enable"` + // Cert is the path to the TLS certificate file. + Cert string `yaml:"cert" json:"cert"` + // Key is the path to the TLS private key file. + Key string `yaml:"key" json:"key"` +} + // RemoteManagement holds management API configuration under 'remote-management'. type RemoteManagement struct { // AllowRemote toggles remote (non-localhost) access to management API.