diff --git a/cmd/root.go b/cmd/root.go index f3a4af5b..2df6c2cb 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -103,7 +103,7 @@ func startServer(ctx context.Context) func() error { if strings.HasPrefix(conf.Server.UILoginBackgroundURL, "/") { a.MountRouter("Background images", consts.DefaultUILoginBackgroundURL, backgrounds.NewHandler()) } - return a.Run(ctx, fmt.Sprintf("%s:%d", conf.Server.Address, conf.Server.Port)) + return a.Run(ctx, fmt.Sprintf("%s:%d", conf.Server.Address, conf.Server.Port), conf.Server.TLSCert, conf.Server.TLSKey) } } @@ -162,11 +162,14 @@ func init() { _ = viper.BindPFlag("datafolder", rootCmd.PersistentFlags().Lookup("datafolder")) _ = viper.BindPFlag("loglevel", rootCmd.PersistentFlags().Lookup("loglevel")) - rootCmd.Flags().StringP("address", "a", viper.GetString("address"), "IP address to bind") - rootCmd.Flags().IntP("port", "p", viper.GetInt("port"), "HTTP port Navidrome will use") + rootCmd.Flags().StringP("address", "a", viper.GetString("address"), "IP address to bind to") + rootCmd.Flags().IntP("port", "p", viper.GetInt("port"), "HTTP port Navidrome will listen to") + rootCmd.Flags().String("baseurl", viper.GetString("baseurl"), "base URL to configure Navidrome behind a proxy (ex: /music or http://my.server.com)") + rootCmd.Flags().String("tlscert", viper.GetString("tlscert"), "optional path to a TLS cert file (enables HTTPS listening)") + rootCmd.Flags().String("tlskey", viper.GetString("tlskey"), "optional path to a TLS key file (enables HTTPS listening)") + rootCmd.Flags().Duration("sessiontimeout", viper.GetDuration("sessiontimeout"), "how long Navidrome will wait before closing web ui idle sessions") rootCmd.Flags().Duration("scaninterval", viper.GetDuration("scaninterval"), "how frequently to scan for changes in your music library") - rootCmd.Flags().String("baseurl", viper.GetString("baseurl"), "base URL (only the path part) to configure Navidrome behind a proxy (ex: /music)") rootCmd.Flags().String("uiloginbackgroundurl", viper.GetString("uiloginbackgroundurl"), "URL to a backaground image used in the Login page") rootCmd.Flags().Bool("enabletranscodingconfig", viper.GetBool("enabletranscodingconfig"), "enables transcoding configuration in the UI") rootCmd.Flags().String("transcodingcachesize", viper.GetString("transcodingcachesize"), "size of transcoding cache") @@ -178,9 +181,12 @@ func init() { _ = viper.BindPFlag("address", rootCmd.Flags().Lookup("address")) _ = viper.BindPFlag("port", rootCmd.Flags().Lookup("port")) + _ = viper.BindPFlag("tlscert", rootCmd.Flags().Lookup("tlscert")) + _ = viper.BindPFlag("tlskey", rootCmd.Flags().Lookup("tlskey")) + _ = viper.BindPFlag("baseurl", rootCmd.Flags().Lookup("baseurl")) + _ = viper.BindPFlag("sessiontimeout", rootCmd.Flags().Lookup("sessiontimeout")) _ = viper.BindPFlag("scaninterval", rootCmd.Flags().Lookup("scaninterval")) - _ = viper.BindPFlag("baseurl", rootCmd.Flags().Lookup("baseurl")) _ = viper.BindPFlag("uiloginbackgroundurl", rootCmd.Flags().Lookup("uiloginbackgroundurl")) _ = viper.BindPFlag("prometheus.enabled", rootCmd.Flags().Lookup("prometheus.enabled")) diff --git a/conf/configuration.go b/conf/configuration.go index 5112e6ea..77cab4a3 100644 --- a/conf/configuration.go +++ b/conf/configuration.go @@ -32,6 +32,8 @@ type configOptions struct { BasePath string BaseHost string BaseScheme string + TLSCert string + TLSKey string UILoginBackgroundURL string UIWelcomeMessage string MaxSidebarPlaylists int @@ -246,6 +248,8 @@ func init() { viper.SetDefault("scaninterval", -1) viper.SetDefault("scanschedule", "@every 1m") viper.SetDefault("baseurl", "") + viper.SetDefault("tlscert", "") + viper.SetDefault("tlskey", "") viper.SetDefault("uiloginbackgroundurl", consts.DefaultUILoginBackgroundURL) viper.SetDefault("uiwelcomemessage", "") viper.SetDefault("maxsidebarplaylists", consts.DefaultMaxSidebarPlaylists) diff --git a/server/server.go b/server/server.go index dc35b97f..275ce761 100644 --- a/server/server.go +++ b/server/server.go @@ -46,30 +46,57 @@ func (s *Server) MountRouter(description, urlPath string, subRouter http.Handler }) } -func (s *Server) Run(ctx context.Context, addr string) error { +// Run starts the server with the given address, and if specified, with TLS enabled. +func (s *Server) Run(ctx context.Context, addr string, tlsCert string, tlsKey string) error { + // Mount the router for the frontend assets s.MountRouter("WebUI", consts.URLPathUI, s.frontendAssetsHandler()) + + // Create a new http.Server with the specified address and read header timeout server := &http.Server{ Addr: addr, ReadHeaderTimeout: consts.ServerReadHeaderTimeout, Handler: s.router, } - // Start HTTP server in its own goroutine, send a signal (errC) if failed to start + // Determine if TLS is enabled + tlsEnabled := tlsCert != "" && tlsKey != "" + + // Start the server in a new goroutine and send an error signal to errC if there's an error errC := make(chan error) go func() { - if err := server.ListenAndServe(); !errors.Is(err, http.ErrServerClosed) { - log.Error(ctx, "Could not start server. Aborting", err) - errC <- err + if tlsEnabled { + // Start the HTTPS server + log.Info("Starting server with TLS (HTTPS) enabled", "tlsCert", tlsCert, "tlsKey", tlsKey) + if err := server.ListenAndServeTLS(tlsCert, tlsKey); !errors.Is(err, http.ErrServerClosed) { + errC <- err + } + } else { + // Start the HTTP server + if err := server.ListenAndServe(); !errors.Is(err, http.ErrServerClosed) { + errC <- err + } } }() - log.Info(ctx, "Navidrome server is ready!", "address", addr, "startupTime", time.Since(consts.ServerStart)) + // Measure server startup time + startupTime := time.Since(consts.ServerStart) - // Wait for a signal to terminate (or an error during startup) + // Wait a short time before checking if the server has started successfully + time.Sleep(50 * time.Millisecond) select { case err := <-errC: - return err + log.Error(ctx, "Could not start server. Aborting", err) + return fmt.Errorf("error starting server: %w", err) + default: + log.Info(ctx, "----> Navidrome server is ready!", "address", addr, "startupTime", startupTime, "tlsEnabled", tlsEnabled) + } + + // Wait for a signal to terminate + select { + case err := <-errC: + return fmt.Errorf("error running server: %w", err) case <-ctx.Done(): + // If the context is done (i.e. the server should stop), proceed to shutting down the server } // Try to stop the HTTP server gracefully