2020-07-02 12:34:21 -08:00
package cmd
import (
2021-01-31 13:37:54 -09:00
"context"
2022-11-21 08:14:06 -09:00
"errors"
2020-07-02 12:34:21 -08:00
"fmt"
"os"
2022-11-29 10:40:44 -09:00
"strings"
2020-10-24 18:43:59 -08:00
"time"
2020-07-02 12:34:21 -08:00
2023-03-15 06:56:13 -08:00
"github.com/go-chi/chi/v5/middleware"
2020-07-02 12:34:21 -08:00
"github.com/navidrome/navidrome/conf"
"github.com/navidrome/navidrome/consts"
2023-01-24 15:26:07 -09:00
"github.com/navidrome/navidrome/core"
2020-07-02 13:21:25 -08:00
"github.com/navidrome/navidrome/db"
2020-10-24 18:43:59 -08:00
"github.com/navidrome/navidrome/log"
2021-10-28 06:25:25 -08:00
"github.com/navidrome/navidrome/resources"
2021-06-21 14:41:11 -08:00
"github.com/navidrome/navidrome/scheduler"
2022-11-27 17:37:19 -09:00
"github.com/navidrome/navidrome/server/backgrounds"
2022-10-02 15:59:53 -08:00
"github.com/prometheus/client_golang/prometheus/promhttp"
2022-11-21 08:14:06 -09:00
"golang.org/x/sync/errgroup"
2022-10-02 15:59:53 -08:00
2020-07-02 12:34:21 -08:00
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
2022-11-21 08:14:06 -09:00
var interrupted = errors . New ( "service was interrupted" )
2020-07-02 12:34:21 -08:00
var (
2020-07-02 16:08:41 -08:00
cfgFile string
noBanner bool
2020-07-02 12:34:21 -08:00
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`,
2020-07-02 16:08:41 -08:00
PersistentPreRun : func ( cmd * cobra . Command , args [ ] string ) {
preRun ( )
} ,
2020-07-02 12:34:21 -08:00
Run : func ( cmd * cobra . Command , args [ ] string ) {
2020-10-24 18:43:59 -08:00
runNavidrome ( )
2020-07-02 12:34:21 -08:00
} ,
2022-09-14 17:09:39 -08:00
Version : consts . Version ,
2020-07-02 12:34:21 -08:00
}
)
func Execute ( ) {
rootCmd . SetVersionTemplate ( ` {{ println .Version }} ` )
if err := rootCmd . Execute ( ) ; err != nil {
fmt . Println ( err )
os . Exit ( 1 )
}
}
2020-07-02 16:08:41 -08:00
func preRun ( ) {
if ! noBanner {
2021-10-28 06:25:25 -08:00
println ( resources . Banner ( ) )
2020-07-02 16:08:41 -08:00
}
2020-07-02 13:21:25 -08:00
conf . Load ( )
2020-07-02 16:08:41 -08:00
}
2020-10-24 18:43:59 -08:00
func runNavidrome ( ) {
2023-04-04 06:30:28 -08:00
db . Init ( )
2022-11-21 08:14:06 -09:00
defer func ( ) {
if err := db . Close ( ) ; err != nil {
log . Error ( "Error closing DB" , err )
}
2022-11-26 09:13:05 -09:00
log . Info ( "Navidrome stopped, bye." )
2022-11-21 08:14:06 -09:00
} ( )
2020-07-02 13:21:25 -08:00
2022-11-21 08:14:06 -09:00
g , ctx := errgroup . WithContext ( context . Background ( ) )
g . Go ( startServer ( ctx ) )
g . Go ( startSignaler ( ctx ) )
g . Go ( startScheduler ( ctx ) )
g . Go ( schedulePeriodicScan ( ctx ) )
2020-10-25 14:17:23 -08:00
2022-11-21 08:14:06 -09:00
if err := g . Wait ( ) ; err != nil && ! errors . Is ( err , interrupted ) {
2020-10-24 18:43:59 -08:00
log . Error ( "Fatal error in Navidrome. Aborting" , err )
}
}
2022-11-21 08:14:06 -09:00
func startServer ( ctx context . Context ) func ( ) error {
2020-10-24 18:43:59 -08:00
return func ( ) error {
2022-11-21 08:14:06 -09:00
a := CreateServer ( conf . Server . MusicFolder )
a . MountRouter ( "Native API" , consts . URLPathNativeAPI , CreateNativeAPIRouter ( ) )
a . MountRouter ( "Subsonic API" , consts . URLPathSubsonicAPI , CreateSubsonicAPIRouter ( ) )
2023-01-20 12:01:16 -09:00
a . MountRouter ( "Public Endpoints" , consts . URLPathPublic , CreatePublicRouter ( ) )
2022-11-21 08:14:06 -09:00
if conf . Server . LastFM . Enabled {
a . MountRouter ( "LastFM Auth" , consts . URLPathNativeAPI + "/lastfm" , CreateLastFMRouter ( ) )
2020-10-24 18:43:59 -08:00
}
2022-11-21 08:14:06 -09:00
if conf . Server . ListenBrainz . Enabled {
a . MountRouter ( "ListenBrainz Auth" , consts . URLPathNativeAPI + "/listenbrainz" , CreateListenBrainzRouter ( ) )
}
if conf . Server . Prometheus . Enabled {
2023-01-24 15:26:07 -09:00
// blocking call because takes <1ms but useful if fails
core . WriteInitialMetrics ( )
2022-11-21 08:14:06 -09:00
a . MountRouter ( "Prometheus metrics" , conf . Server . Prometheus . MetricsPath , promhttp . Handler ( ) )
}
2023-03-15 06:56:13 -08:00
if conf . Server . DevEnableProfiler {
a . MountRouter ( "Profiling" , "/debug" , middleware . Profiler ( ) )
}
2022-11-29 10:40:44 -09:00
if strings . HasPrefix ( conf . Server . UILoginBackgroundURL , "/" ) {
2022-11-27 17:37:19 -09:00
a . MountRouter ( "Background images" , consts . DefaultUILoginBackgroundURL , backgrounds . NewHandler ( ) )
}
2023-03-29 12:05:31 -08:00
return a . Run ( ctx , conf . Server . Address , conf . Server . Port , conf . Server . TLSCert , conf . Server . TLSKey )
2022-11-21 08:14:06 -09:00
}
2020-10-24 18:43:59 -08:00
}
2022-11-21 08:14:06 -09:00
func schedulePeriodicScan ( ctx context . Context ) func ( ) error {
2020-10-24 18:43:59 -08:00
return func ( ) error {
2022-11-21 08:14:06 -09:00
schedule := conf . Server . ScanSchedule
if schedule == "" {
log . Warn ( "Periodic scan is DISABLED" )
return nil
2021-04-29 09:10:10 -08:00
}
2022-11-21 08:14:06 -09:00
scanner := GetScanner ( )
schedulerInstance := scheduler . GetInstance ( )
2021-05-06 13:56:10 -08:00
2022-11-21 08:14:06 -09:00
log . Info ( "Scheduling periodic scan" , "schedule" , schedule )
err := schedulerInstance . Add ( schedule , func ( ) {
_ = scanner . RescanAll ( ctx , false )
} )
if err != nil {
log . Error ( "Error scheduling periodic scan" , err )
}
2021-05-08 15:19:31 -08:00
2022-11-21 08:14:06 -09:00
time . Sleep ( 2 * time . Second ) // Wait 2 seconds before the initial scan
log . Debug ( "Executing initial scan" )
if err := scanner . RescanAll ( ctx , false ) ; err != nil {
log . Error ( "Error executing initial scan" , err )
}
log . Debug ( "Finished initial scan" )
return nil
2021-05-08 15:19:31 -08:00
}
2021-05-06 13:56:10 -08:00
}
2022-11-21 08:14:06 -09:00
func startScheduler ( ctx context . Context ) func ( ) error {
log . Info ( ctx , "Starting scheduler" )
2021-06-21 14:41:11 -08:00
schedulerInstance := scheduler . GetInstance ( )
2021-04-29 09:10:10 -08:00
return func ( ) error {
2022-11-21 08:14:06 -09:00
schedulerInstance . Run ( ctx )
return nil
}
2020-07-02 13:21:25 -08:00
}
2020-10-24 18:43:59 -08:00
// TODO: Implement some struct tags to map flags to viper
2020-07-02 12:34:21 -08:00
func init ( ) {
2020-07-03 05:39:28 -08:00
cobra . OnInitialize ( func ( ) {
conf . InitConfig ( cfgFile )
} )
2020-07-02 12:34:21 -08:00
rootCmd . PersistentFlags ( ) . StringVarP ( & cfgFile , "configfile" , "c" , "" , ` config file (default "./navidrome.toml") ` )
2020-07-02 16:08:41 -08:00
rootCmd . PersistentFlags ( ) . BoolVarP ( & noBanner , "nobanner" , "n" , false , ` don't show banner ` )
2020-07-02 12:34:21 -08:00
rootCmd . PersistentFlags ( ) . String ( "musicfolder" , viper . GetString ( "musicfolder" ) , "folder where your music is stored" )
2023-06-02 13:14:11 -08:00
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" )
2020-07-02 12:34:21 -08:00
rootCmd . PersistentFlags ( ) . StringP ( "loglevel" , "l" , viper . GetString ( "loglevel" ) , "log level, possible values: error, info, debug, trace" )
2020-07-02 14:17:31 -08:00
_ = viper . BindPFlag ( "musicfolder" , rootCmd . PersistentFlags ( ) . Lookup ( "musicfolder" ) )
_ = viper . BindPFlag ( "datafolder" , rootCmd . PersistentFlags ( ) . Lookup ( "datafolder" ) )
2023-06-02 13:14:11 -08:00
_ = viper . BindPFlag ( "cachefolder" , rootCmd . PersistentFlags ( ) . Lookup ( "cachefolder" ) )
2020-07-02 14:17:31 -08:00
_ = viper . BindPFlag ( "loglevel" , rootCmd . PersistentFlags ( ) . Lookup ( "loglevel" ) )
2020-07-02 12:34:21 -08:00
2023-03-17 12:07:01 -08:00
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)" )
2020-07-02 13:21:25 -08:00
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" )
2020-07-02 12:34:21 -08:00
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" )
rootCmd . Flags ( ) . String ( "imagecachesize" , viper . GetString ( "imagecachesize" ) , "size of image (art work) cache. set to 0 to disable cache" )
2020-08-02 19:17:13 -08:00
rootCmd . Flags ( ) . Bool ( "autoimportplaylists" , viper . GetBool ( "autoimportplaylists" ) , "enable/disable .m3u playlist auto-import`" )
2020-07-02 12:34:21 -08:00
2022-10-02 15:59:53 -08:00
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" )
2020-07-08 16:54:56 -08:00
_ = viper . BindPFlag ( "address" , rootCmd . Flags ( ) . Lookup ( "address" ) )
2020-07-02 14:17:31 -08:00
_ = viper . BindPFlag ( "port" , rootCmd . Flags ( ) . Lookup ( "port" ) )
2023-03-17 12:07:01 -08:00
_ = viper . BindPFlag ( "tlscert" , rootCmd . Flags ( ) . Lookup ( "tlscert" ) )
_ = viper . BindPFlag ( "tlskey" , rootCmd . Flags ( ) . Lookup ( "tlskey" ) )
_ = viper . BindPFlag ( "baseurl" , rootCmd . Flags ( ) . Lookup ( "baseurl" ) )
2020-07-02 14:17:31 -08:00
_ = viper . BindPFlag ( "sessiontimeout" , rootCmd . Flags ( ) . Lookup ( "sessiontimeout" ) )
_ = viper . BindPFlag ( "scaninterval" , rootCmd . Flags ( ) . Lookup ( "scaninterval" ) )
_ = viper . BindPFlag ( "uiloginbackgroundurl" , rootCmd . Flags ( ) . Lookup ( "uiloginbackgroundurl" ) )
2022-10-02 15:59:53 -08:00
_ = viper . BindPFlag ( "prometheus.enabled" , rootCmd . Flags ( ) . Lookup ( "prometheus.enabled" ) )
_ = viper . BindPFlag ( "prometheus.metricspath" , rootCmd . Flags ( ) . Lookup ( "prometheus.metricspath" ) )
2020-07-02 14:17:31 -08:00
_ = viper . BindPFlag ( "enabletranscodingconfig" , rootCmd . Flags ( ) . Lookup ( "enabletranscodingconfig" ) )
_ = viper . BindPFlag ( "transcodingcachesize" , rootCmd . Flags ( ) . Lookup ( "transcodingcachesize" ) )
_ = viper . BindPFlag ( "imagecachesize" , rootCmd . Flags ( ) . Lookup ( "imagecachesize" ) )
2020-07-02 12:34:21 -08:00
}