mirror of
https://github.com/navidrome/navidrome.git
synced 2026-10-08 02:17:25 +02:00
* feat(jellyfin): compute the address advertised by auto-discovery * fix(jellyfin): use TLSEnabled and path.Join for the auto-discovery address * feat(jellyfin): answer LAN auto-discovery broadcasts * test(jellyfin): e2e check that auto-discovery matches the public server identity * feat(jellyfin): add opt-in AutoDiscovery option and start the listener * fix(jellyfin): close the auto-discovery socket on read errors and quiet reply failures * refactor(jellyfin): build the auto-discovery address with publicurl and log bind failures in place discoveryAddress now hands only the discovery-specific part (the requester-facing host) to publicurl.AbsoluteURL, so the BaseURL, scheme and BasePath rules live in one place. ServeDiscovery logs its own bind failure and returns nothing, so the caller cannot route the error into the server errgroup. Tests use a DescribeTable and no longer wait on a fixed timeout to prove a packet was ignored. * refactor(jellyfin): run auto-discovery as its own service in the main errgroup Discovery is now a small type that needs only a DataStore, started by startJellyfinDiscovery like the other background services, so the errgroup waits for it on shutdown and startServer is back to a one-line mount. The server id resolution moved to resolveServerID with a package-level lock: the Router and Discovery are separate objects, and a per-Router lock would let them persist two different ids on first boot. * refactor(jellyfin): build Discovery through wire and drop the serverName forwarder startJellyfinDiscovery now follows its siblings: negative guard with a DISABLED debug log, and the service comes from a CreateJellyfinDiscovery wire injector instead of an inline constructor. The Router.serverName method only forwarded to the package function, so its four call sites call the function directly. * fix(jellyfin): skip auto-discovery for unix socket servers without a BaseURL host With Address set to a unix socket nothing listens on Port, so the route-facing IP plus Port pointed clients at a dead URL. Discovery now logs a warning and does not start in that mode unless BaseURL names the proxy host, which AbsoluteURL already advertises as-is. * docs(jellyfin): note which address auto-discovery advertises on restricted binds Also stop using a hostname Address in the fallback spec: a hostname like localhost binds a single interface, so it is not an example of the route-facing fallback being right. An empty Address is.
488 lines
18 KiB
Go
488 lines
18 KiB
Go
package cmd
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
"os"
|
|
"os/signal"
|
|
"strings"
|
|
"syscall"
|
|
"time"
|
|
|
|
"github.com/go-chi/chi/v5/middleware"
|
|
"github.com/navidrome/navidrome/conf"
|
|
"github.com/navidrome/navidrome/consts"
|
|
"github.com/navidrome/navidrome/core/artwork"
|
|
"github.com/navidrome/navidrome/db"
|
|
"github.com/navidrome/navidrome/log"
|
|
"github.com/navidrome/navidrome/model"
|
|
"github.com/navidrome/navidrome/resources"
|
|
"github.com/navidrome/navidrome/scanner"
|
|
"github.com/navidrome/navidrome/scheduler"
|
|
"github.com/navidrome/navidrome/server/backgrounds"
|
|
"github.com/spf13/cobra"
|
|
"github.com/spf13/viper"
|
|
"golang.org/x/sync/errgroup"
|
|
|
|
// Import adapters to register them
|
|
_ "github.com/navidrome/navidrome/adapters/deezer"
|
|
_ "github.com/navidrome/navidrome/adapters/gotaglib"
|
|
_ "github.com/navidrome/navidrome/adapters/lastfm"
|
|
_ "github.com/navidrome/navidrome/adapters/listenbrainz"
|
|
)
|
|
|
|
var (
|
|
cfgFile string
|
|
noBanner bool
|
|
|
|
rootCmd = &cobra.Command{
|
|
Use: "navidrome",
|
|
Short: "Navidrome is a self-hosted music server and streamer",
|
|
Long: `Navidrome is a self-hosted music server and streamer.
|
|
Complete documentation is available at https://www.navidrome.org/docs`,
|
|
PersistentPreRun: func(cmd *cobra.Command, args []string) {
|
|
preRun()
|
|
},
|
|
Run: func(cmd *cobra.Command, args []string) {
|
|
runNavidrome(cmd.Context())
|
|
},
|
|
PostRun: func(cmd *cobra.Command, args []string) {
|
|
postRun()
|
|
},
|
|
Version: consts.Version,
|
|
}
|
|
)
|
|
|
|
// Execute runs the root cobra command, which will start the Navidrome server by calling the runNavidrome function.
|
|
func Execute() {
|
|
ctx, cancel := mainContext(context.Background())
|
|
defer cancel()
|
|
|
|
rootCmd.SetVersionTemplate(`{{println .Version}}`)
|
|
if err := rootCmd.ExecuteContext(ctx); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func preRun() {
|
|
if !noBanner {
|
|
println(resources.Banner())
|
|
}
|
|
conf.Load(noBanner)
|
|
}
|
|
|
|
func postRun() {
|
|
log.Info("Navidrome stopped, bye.")
|
|
}
|
|
|
|
// runNavidrome is the main entry point for the Navidrome server. It starts all the services and blocks.
|
|
// If any of the services returns an error, it will log it and exit. If the process receives a signal to exit,
|
|
// it will cancel the context and exit gracefully.
|
|
func runNavidrome(ctx context.Context) {
|
|
defer db.Init(ctx)()
|
|
|
|
g, ctx := errgroup.WithContext(ctx)
|
|
g.Go(startServer(ctx))
|
|
g.Go(startSignaller(ctx))
|
|
g.Go(startScheduler(ctx))
|
|
g.Go(startPlaybackServer(ctx))
|
|
g.Go(startJellyfinDiscovery(ctx))
|
|
g.Go(schedulePeriodicBackup(ctx))
|
|
g.Go(startInsightsCollector(ctx))
|
|
g.Go(scheduleDBAnalyzer(ctx))
|
|
g.Go(startPluginManager(ctx))
|
|
artworkWorker := CreateArtworkWorker()
|
|
g.Go(startArtworkWorker(ctx, artworkWorker))
|
|
g.Go(scheduleArtworkHousekeeping(ctx, artworkWorker))
|
|
g.Go(runInitialScan(ctx))
|
|
if conf.Server.Scanner.Enabled {
|
|
g.Go(startScanWatcher(ctx))
|
|
g.Go(schedulePeriodicScan(ctx))
|
|
} else {
|
|
log.Warn(ctx, "Automatic Scanning is DISABLED")
|
|
}
|
|
|
|
if err := g.Wait(); err != nil {
|
|
log.Error("Fatal error in Navidrome. Aborting", err)
|
|
}
|
|
}
|
|
|
|
// mainContext returns a context that is cancelled when the process receives a signal to exit.
|
|
func mainContext(ctx context.Context) (context.Context, context.CancelFunc) {
|
|
return signal.NotifyContext(ctx,
|
|
os.Interrupt,
|
|
syscall.SIGHUP,
|
|
syscall.SIGTERM,
|
|
syscall.SIGABRT,
|
|
)
|
|
}
|
|
|
|
// startServer starts the Navidrome web server, adding all the necessary routers.
|
|
func startServer(ctx context.Context) func() error {
|
|
return func() error {
|
|
a := CreateServer()
|
|
a.MountRouter("Native API", consts.URLPathNativeAPI, CreateNativeAPIRouter(ctx))
|
|
a.MountRouter("Subsonic API", consts.URLPathSubsonicAPI, CreateSubsonicAPIRouter(ctx))
|
|
a.MountRouter("Public Endpoints", consts.URLPathPublic, CreatePublicRouter())
|
|
if conf.Server.LastFM.Enabled {
|
|
a.MountRouter("LastFM Auth", consts.URLPathNativeAPI+"/lastfm", CreateLastFMRouter())
|
|
}
|
|
if conf.Server.ListenBrainz.Enabled {
|
|
a.MountRouter("ListenBrainz Auth", consts.URLPathNativeAPI+"/listenbrainz", CreateListenBrainzRouter())
|
|
}
|
|
if conf.Server.Jellyfin.Enabled {
|
|
a.MountRouter("Jellyfin API", consts.URLPathJellyfinAPI, CreateJellyfinAPIRouter(ctx))
|
|
}
|
|
if conf.Server.Prometheus.Enabled {
|
|
p := CreatePrometheus()
|
|
// blocking call because takes <100ms but useful if fails
|
|
p.WriteInitialMetrics(ctx)
|
|
a.MountRouter("Prometheus metrics", conf.Server.Prometheus.MetricsPath, p.GetHandler())
|
|
}
|
|
if conf.Server.DevEnableProfiler {
|
|
a.MountRouter("Profiling", "/debug", profilerHandler())
|
|
}
|
|
if strings.HasPrefix(conf.Server.UILoginBackgroundURL, "/") {
|
|
a.MountRouter("Background images", conf.Server.UILoginBackgroundURL, backgrounds.NewHandler())
|
|
}
|
|
return a.Run(ctx, conf.Server.Address, conf.Server.Port, conf.Server.TLSCert, conf.Server.TLSKey)
|
|
}
|
|
}
|
|
|
|
// profilerHandler returns the pprof handler. net/http/pprof resolves the profile
|
|
// name from the raw request path, so the BasePath has to come off first.
|
|
func profilerHandler() http.Handler {
|
|
// A trailing or root slash would make StripPrefix drop the leading slash chi needs.
|
|
basePath := strings.TrimRight(conf.Server.BasePath, "/")
|
|
return http.StripPrefix(basePath, middleware.Profiler())
|
|
}
|
|
|
|
// schedulePeriodicScan schedules a periodic scan of the music library, if configured.
|
|
func schedulePeriodicScan(ctx context.Context) func() error {
|
|
return func() error {
|
|
schedule := conf.Server.Scanner.Schedule
|
|
if schedule == "" {
|
|
log.Info(ctx, "Periodic scan is DISABLED")
|
|
return nil
|
|
}
|
|
|
|
s := CreateScanner(ctx)
|
|
schedulerInstance := scheduler.GetInstance()
|
|
|
|
log.Info("Scheduling periodic scan", "schedule", schedule)
|
|
_, err := schedulerInstance.Add(schedule, func() {
|
|
_, err := s.ScanAll(ctx, false)
|
|
if err != nil {
|
|
log.Error(ctx, "Error executing periodic scan", err)
|
|
}
|
|
})
|
|
if err != nil {
|
|
log.Error(ctx, "Error scheduling periodic scan", err)
|
|
}
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func pidHashChanged(ds model.DataStore) (bool, error) {
|
|
pidAlbum, err := ds.Property(context.Background()).DefaultGet(consts.PIDAlbumKey, "")
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
pidTrack, err := ds.Property(context.Background()).DefaultGet(consts.PIDTrackKey, "")
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
return !strings.EqualFold(pidAlbum, conf.Server.PID.Album) || !strings.EqualFold(pidTrack, conf.Server.PID.Track), nil
|
|
}
|
|
|
|
// runInitialScan runs an initial scan of the music library if needed.
|
|
func runInitialScan(ctx context.Context) func() error {
|
|
return func() error {
|
|
ds := CreateDataStore()
|
|
fullScanRequired, err := ds.Property(ctx).DefaultGet(consts.FullScanAfterMigrationFlagKey, "0")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
inProgress, err := ds.Library(ctx).ScanInProgress()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
pidHasChanged, err := pidHashChanged(ds)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
scanOnStartup := conf.Server.Scanner.Enabled && conf.Server.Scanner.ScanOnStartup
|
|
scanNeeded := scanOnStartup || inProgress || fullScanRequired == "1" || pidHasChanged
|
|
time.Sleep(2 * time.Second) // Wait 2 seconds before the initial scan
|
|
if scanNeeded {
|
|
s := CreateScanner(ctx)
|
|
switch {
|
|
case fullScanRequired == "1":
|
|
log.Warn(ctx, "Full scan required after migration")
|
|
_ = ds.Property(ctx).Delete(consts.FullScanAfterMigrationFlagKey)
|
|
case pidHasChanged:
|
|
log.Warn(ctx, "PID config changed, performing full scan")
|
|
fullScanRequired = "1"
|
|
case inProgress:
|
|
log.Warn(ctx, "Resuming interrupted scan")
|
|
default:
|
|
log.Info("Executing initial scan")
|
|
}
|
|
|
|
_, err = s.ScanAll(ctx, fullScanRequired == "1")
|
|
if err != nil {
|
|
log.Error(ctx, "Scan failed", err)
|
|
} else {
|
|
log.Info(ctx, "Scan completed")
|
|
}
|
|
} else {
|
|
log.Debug(ctx, "Initial scan not needed")
|
|
}
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func startScanWatcher(ctx context.Context) func() error {
|
|
return func() error {
|
|
if conf.Server.Scanner.WatcherWait == 0 {
|
|
log.Debug("Folder watcher is DISABLED")
|
|
return nil
|
|
}
|
|
w := CreateScanWatcher(ctx)
|
|
err := w.Run(ctx)
|
|
if err != nil {
|
|
log.Error("Error starting watcher", err)
|
|
}
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func schedulePeriodicBackup(ctx context.Context) func() error {
|
|
return func() error {
|
|
schedule := conf.Server.Backup.Schedule
|
|
if schedule == "" {
|
|
log.Info(ctx, "Periodic backup is DISABLED")
|
|
return nil
|
|
}
|
|
|
|
schedulerInstance := scheduler.GetInstance()
|
|
|
|
log.Info("Scheduling periodic backup", "schedule", schedule)
|
|
_, err := schedulerInstance.Add(schedule, func() {
|
|
start := time.Now()
|
|
path, err := db.Backup(ctx)
|
|
elapsed := time.Since(start)
|
|
if err != nil {
|
|
log.Error(ctx, "Error backing up database", "elapsed", elapsed, err)
|
|
return
|
|
}
|
|
log.Info(ctx, "Backup complete", "elapsed", elapsed, "path", path)
|
|
|
|
count, err := db.Prune(ctx)
|
|
if err != nil {
|
|
log.Error(ctx, "Error pruning database", "error", err)
|
|
} else if count > 0 {
|
|
log.Info(ctx, "Successfully pruned old files", "count", count)
|
|
} else {
|
|
log.Info(ctx, "No backups pruned")
|
|
}
|
|
})
|
|
|
|
return err
|
|
}
|
|
}
|
|
|
|
func scheduleDBAnalyzer(ctx context.Context) func() error {
|
|
return func() error {
|
|
if !conf.Server.EnableScheduledDBAnalyze {
|
|
log.Info(ctx, "Scheduled DB analysis is DISABLED")
|
|
return nil
|
|
}
|
|
log.Info(ctx, "Scheduling DB analysis check", "schedule", consts.DBAnalyzeCheckSchedule)
|
|
schedulerInstance := scheduler.GetInstance()
|
|
_, err := schedulerInstance.Add(consts.DBAnalyzeCheckSchedule, func() {
|
|
release, ok := scanner.LockForMaintenance()
|
|
if !ok {
|
|
log.Debug(ctx, "Skipping DB analysis check because a scan is in progress")
|
|
return
|
|
}
|
|
defer release()
|
|
if _, err := db.OptimizeIfNeeded(ctx); err != nil {
|
|
log.Error(ctx, "Error analyzing DB", err)
|
|
}
|
|
})
|
|
return err
|
|
}
|
|
}
|
|
|
|
// startScheduler starts the Navidrome scheduler, which is used to run periodic tasks.
|
|
func startScheduler(ctx context.Context) func() error {
|
|
return func() error {
|
|
log.Info(ctx, "Starting scheduler")
|
|
schedulerInstance := scheduler.GetInstance()
|
|
schedulerInstance.Run(ctx)
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// startInsightsCollector starts the Navidrome Insight Collector, if configured.
|
|
func startInsightsCollector(ctx context.Context) func() error {
|
|
return func() error {
|
|
if !conf.Server.EnableInsightsCollector {
|
|
log.Info(ctx, "Insight Collector is DISABLED")
|
|
return nil
|
|
}
|
|
log.Info(ctx, "Starting Insight Collector")
|
|
select {
|
|
case <-time.After(conf.Server.DevInsightsInitialDelay):
|
|
case <-ctx.Done():
|
|
return nil
|
|
}
|
|
ic := CreateInsights()
|
|
ic.Run(ctx)
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// startJellyfinDiscovery never returns an error: a discovery failure must not stop the server.
|
|
func startJellyfinDiscovery(ctx context.Context) func() error {
|
|
return func() error {
|
|
if !conf.Server.Jellyfin.Enabled || !conf.Server.Jellyfin.AutoDiscovery {
|
|
log.Debug("Jellyfin auto-discovery is DISABLED")
|
|
return nil
|
|
}
|
|
CreateJellyfinDiscovery().Serve(ctx)
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// startPlaybackServer starts the Navidrome playback server, if configured.
|
|
// It is responsible for the Jukebox functionality
|
|
func startPlaybackServer(ctx context.Context) func() error {
|
|
return func() error {
|
|
if !conf.Server.Jukebox.Enabled {
|
|
log.Debug("Jukebox is DISABLED")
|
|
return nil
|
|
}
|
|
log.Info(ctx, "Starting Jukebox service")
|
|
playbackInstance := GetPlaybackServer()
|
|
return playbackInstance.Run(ctx)
|
|
}
|
|
}
|
|
|
|
// startArtworkWorker starts the background artwork acquisition worker. It always
|
|
// runs; the queue is simply empty until something enqueues work into it.
|
|
func startArtworkWorker(ctx context.Context, worker *artwork.Worker) func() error {
|
|
return func() error {
|
|
log.Info(ctx, "Starting artwork worker")
|
|
return worker.Run(ctx)
|
|
}
|
|
}
|
|
|
|
// scheduleArtworkHousekeeping registers the recurring missing-state and prune jobs, and
|
|
// reports an artwork config change without acting on it.
|
|
func scheduleArtworkHousekeeping(ctx context.Context, worker *artwork.Worker) func() error {
|
|
return func() error {
|
|
schedulerInstance := scheduler.GetInstance()
|
|
|
|
if _, err := schedulerInstance.Add(consts.ArtworkEnqueueMissingSchedule, func() {
|
|
if err := worker.EnqueueMissingAll(ctx); err != nil {
|
|
log.Error(ctx, "Error enqueueing missing artwork rechecks", err)
|
|
}
|
|
}); err != nil {
|
|
log.Error(ctx, "Error scheduling artwork missing-state recheck", err)
|
|
}
|
|
|
|
if _, err := schedulerInstance.Add(consts.ArtworkPruneSchedule, func() {
|
|
if err := worker.RunPrune(ctx); err != nil {
|
|
log.Error(ctx, "Error running artwork prune", err)
|
|
}
|
|
}); err != nil {
|
|
log.Error(ctx, "Error scheduling artwork prune", err)
|
|
}
|
|
|
|
// Also run the missing-row recheck once at startup so a never-scanned entity is picked up
|
|
// immediately, not only on the next hourly tick (e.g. after enabling the feature).
|
|
if err := worker.EnqueueMissingAll(ctx); err != nil {
|
|
log.Error(ctx, "Error enqueueing missing artwork rechecks", err)
|
|
}
|
|
|
|
if err := worker.ReconcileConfig(ctx); err != nil {
|
|
log.Error(ctx, "Error checking the artwork config fingerprint", err)
|
|
}
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// startPluginManager starts the plugin manager, if configured.
|
|
func startPluginManager(ctx context.Context) func() error {
|
|
return func() error {
|
|
manager := GetPluginManager(ctx)
|
|
if !conf.Server.Plugins.Enabled {
|
|
log.Debug("Plugin system is DISABLED")
|
|
return nil
|
|
}
|
|
log.Info(ctx, "Starting plugin manager")
|
|
return manager.Start(ctx)
|
|
}
|
|
}
|
|
|
|
// TODO: Implement some struct tags to map flags to viper
|
|
func init() {
|
|
cobra.OnInitialize(func() {
|
|
conf.InitConfig(cfgFile, true)
|
|
})
|
|
|
|
rootCmd.PersistentFlags().StringVarP(&cfgFile, "configfile", "c", "", `config file (default "./navidrome.toml")`)
|
|
rootCmd.PersistentFlags().BoolVarP(&noBanner, "nobanner", "n", false, `don't show banner`)
|
|
rootCmd.PersistentFlags().String("musicfolder", viper.GetString("musicfolder"), "folder where your music is stored")
|
|
rootCmd.PersistentFlags().String("datafolder", viper.GetString("datafolder"), "folder to store application data (DB), needs write access")
|
|
rootCmd.PersistentFlags().String("cachefolder", viper.GetString("cachefolder"), "folder to store cache data (transcoding, images...), needs write access")
|
|
rootCmd.PersistentFlags().StringP("loglevel", "l", viper.GetString("loglevel"), "log level, possible values: error, info, debug, trace")
|
|
rootCmd.PersistentFlags().String("logfile", viper.GetString("logfile"), "log file path, if not set logs will be printed to stderr")
|
|
|
|
_ = viper.BindPFlag("musicfolder", rootCmd.PersistentFlags().Lookup("musicfolder"))
|
|
_ = viper.BindPFlag("datafolder", rootCmd.PersistentFlags().Lookup("datafolder"))
|
|
_ = viper.BindPFlag("cachefolder", rootCmd.PersistentFlags().Lookup("cachefolder"))
|
|
_ = viper.BindPFlag("loglevel", rootCmd.PersistentFlags().Lookup("loglevel"))
|
|
_ = viper.BindPFlag("logfile", rootCmd.PersistentFlags().Lookup("logfile"))
|
|
|
|
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("unixsocketperm", viper.GetString("unixsocketperm"), "optional file permission for the unix socket")
|
|
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("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().Bool("enabletranscodingcancellation", viper.GetBool("enabletranscodingcancellation"), "enables transcoding context cancellation")
|
|
rootCmd.Flags().String("transcodingcachesize", viper.GetString("transcodingcachesize"), "size of transcoding cache")
|
|
rootCmd.Flags().String("imagecachesize", viper.GetString("imagecachesize"), "size of image (art work) cache. set to 0 to disable cache")
|
|
rootCmd.Flags().String("albumplaycountmode", viper.GetString("albumplaycountmode"), "how to compute playcount for albums. absolute (default) or normalized")
|
|
rootCmd.Flags().Bool("autoimportplaylists", viper.GetBool("autoimportplaylists"), "enable/disable .m3u playlist auto-import`")
|
|
|
|
rootCmd.Flags().Bool("prometheus.enabled", viper.GetBool("prometheus.enabled"), "enable/disable prometheus metrics endpoint")
|
|
rootCmd.Flags().String("prometheus.metricspath", viper.GetString("prometheus.metricspath"), "http endpoint for prometheus metrics")
|
|
|
|
_ = viper.BindPFlag("address", rootCmd.Flags().Lookup("address"))
|
|
_ = viper.BindPFlag("port", rootCmd.Flags().Lookup("port"))
|
|
_ = viper.BindPFlag("tlscert", rootCmd.Flags().Lookup("tlscert"))
|
|
_ = viper.BindPFlag("unixsocketperm", rootCmd.Flags().Lookup("unixsocketperm"))
|
|
_ = 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("uiloginbackgroundurl", rootCmd.Flags().Lookup("uiloginbackgroundurl"))
|
|
|
|
_ = viper.BindPFlag("prometheus.enabled", rootCmd.Flags().Lookup("prometheus.enabled"))
|
|
_ = viper.BindPFlag("prometheus.metricspath", rootCmd.Flags().Lookup("prometheus.metricspath"))
|
|
|
|
_ = viper.BindPFlag("enabletranscodingconfig", rootCmd.Flags().Lookup("enabletranscodingconfig"))
|
|
_ = viper.BindPFlag("enabletranscodingcancellation", rootCmd.Flags().Lookup("enabletranscodingcancellation"))
|
|
_ = viper.BindPFlag("transcodingcachesize", rootCmd.Flags().Lookup("transcodingcachesize"))
|
|
_ = viper.BindPFlag("imagecachesize", rootCmd.Flags().Lookup("imagecachesize"))
|
|
}
|