diff --git a/cmd/mochi/config.go b/cmd/mochi/config.go index b32385e..aac39bd 100644 --- a/cmd/mochi/config.go +++ b/cmd/mochi/config.go @@ -7,12 +7,10 @@ import ( "gopkg.in/yaml.v3" + fh "github.com/sot-tech/mochi/frontend/http" + fu "github.com/sot-tech/mochi/frontend/udp" "github.com/sot-tech/mochi/pkg/conf" - // Imports to register frontends - _ "github.com/sot-tech/mochi/frontend/http" - _ "github.com/sot-tech/mochi/frontend/udp" - // Imports to register middleware hooks. _ "github.com/sot-tech/mochi/middleware/clientapproval" _ "github.com/sot-tech/mochi/middleware/jwt" @@ -21,12 +19,12 @@ import ( // Imports to register storage drivers. _ "github.com/sot-tech/mochi/storage/keydb" - _ "github.com/sot-tech/mochi/storage/memory" + sm "github.com/sot-tech/mochi/storage/memory" _ "github.com/sot-tech/mochi/storage/pg" _ "github.com/sot-tech/mochi/storage/redis" ) -// Config represents the configuration used for executing Conf. +// Config represents the configuration used for Server start. type Config struct { // TODO(jzelinskie): Evaluate whether we would like to make // AnnounceInterval and MinAnnounceInterval optional. @@ -42,6 +40,27 @@ type Config struct { PostHooks []conf.NamedMapConfig `yaml:"posthooks"` } +// QuickConfig is the simple configuration for quick start without config file. +// Includes in-memory store, http and udp frontends without any middleware. +var QuickConfig = &Config{ + Frontends: []conf.NamedMapConfig{ + { + Name: fh.Name, + Config: conf.MapConfig{}, + }, + { + Name: fu.Name, + Config: conf.MapConfig{}, + }, + }, + Storage: conf.NamedMapConfig{ + Name: sm.Name, + Config: conf.MapConfig{}, + }, + PreHooks: []conf.NamedMapConfig{}, + PostHooks: []conf.NamedMapConfig{}, +} + // ParseConfigFile returns a new Config given the path to a YAML // configuration file. // diff --git a/cmd/mochi/main.go b/cmd/mochi/main.go index f8564e6..393e28b 100644 --- a/cmd/mochi/main.go +++ b/cmd/mochi/main.go @@ -18,23 +18,36 @@ const ( logPrettyArg = "logPretty" logColorsArg = "logColored" configArg = "config" + quickArg = "quick" ) func main() { - var s Server + var err error logOut := flag.String(logOutArg, "stderr", "output for logging, might be 'stderr', 'stdout' or file path") logLevel := flag.String(logLevelArg, "warn", "logging level: trace, debug, info, warn, error, fatal, panic") logPretty := flag.Bool(logPrettyArg, false, "enable log pretty print. used only if 'logOut' set to 'stdout' or 'stderr'. if not set, log outputs json") logColored := flag.Bool(logColorsArg, runtime.GOOS == "windows", "enable log coloring. used only if set 'logPretty'") configPath := flag.String(configArg, "/etc/mochi.yaml", "location of configuration file") + quickStart := flag.Bool(quickArg, false, "start tracker with default configuration (all frontends, in-memory store, no hooks)") flag.Parse() - if err := l.ConfigureLogger(*logOut, *logLevel, *logPretty, *logColored); err != nil { + if err = l.ConfigureLogger(*logOut, *logLevel, *logPretty, *logColored); err != nil { log.Fatal("unable to configure logger: ", err) } - if err := s.Run(*configPath); err != nil { + var cfg *Config + if *quickStart { + cfg = QuickConfig + } else { + cfg, err = ParseConfigFile(*configPath) + if err != nil { + log.Fatal("unable to read config file: ", err) + } + } + var s Server + + if err = s.Run(cfg); err != nil { log.Fatal("unable to start server: ", err) } defer s.Shutdown() diff --git a/cmd/mochi/server.go b/cmd/mochi/server.go index 94a618c..6f16750 100644 --- a/cmd/mochi/server.go +++ b/cmd/mochi/server.go @@ -25,12 +25,7 @@ type Server struct { // Run begins an instance of Conf. // It is optional to provide an instance of the peer store to avoid the // creation of a new one. -func (r *Server) Run(configFilePath string) error { - cfg, err := ParseConfigFile(configFilePath) - if err != nil { - return fmt.Errorf("failed to read config: %w", err) - } - +func (r *Server) Run(cfg *Config) (err error) { if len(cfg.MetricsAddr) > 0 { log.Info().Str("address", cfg.MetricsAddr).Msg("starting metrics server") r.frontends = append(r.frontends, metrics.NewServer(cfg.MetricsAddr)) diff --git a/frontend/http/frontend.go b/frontend/http/frontend.go index 4e39661..c2c970c 100644 --- a/frontend/http/frontend.go +++ b/frontend/http/frontend.go @@ -20,14 +20,16 @@ import ( "github.com/sot-tech/mochi/pkg/metrics" ) +// Name - registered name of the frontend +const Name = "http" + var ( - logger = log.NewLogger("frontend/http") - errTLSNotProvided = errors.New("tls certificate/key not provided") - errRoutesNotProvided = errors.New("routes not provided") + logger = log.NewLogger("frontend/http") + errTLSNotProvided = errors.New("tls certificate/key not provided") ) func init() { - frontend.RegisterBuilder("http", NewFrontend) + frontend.RegisterBuilder(Name, NewFrontend) } // Config represents all configurable options for an HTTP BitTorrent Frontend @@ -44,15 +46,17 @@ type Config struct { ParseOptions } -const defaultIdleTimeout = 30 * time.Second +const ( + defaultIdleTimeout = 30 * time.Second + defaultAnnounceRoute = "/announce" + defaultScrapeRoute = "/scrape" +) // Validate sanity checks values set in a config and returns a new config with // default values replacing anything that is invalid. func (cfg Config) Validate() (validCfg Config, err error) { validCfg = cfg - if validCfg.ListenOptions, err = cfg.ListenOptions.Validate(); err != nil { - return - } + validCfg.ListenOptions = cfg.ListenOptions.Validate(false) if cfg.UseTLS && (len(cfg.TLSCertPath) == 0 || len(cfg.TLSKeyPath) == 0) { err = errTLSNotProvided return @@ -68,6 +72,22 @@ func (cfg Config) Validate() (validCfg Config, err error) { Msg("falling back to default configuration") } } + if len(cfg.AnnounceRoutes) == 0 { + validCfg.AnnounceRoutes = []string{defaultAnnounceRoute} + logger.Warn(). + Str("name", "AnnounceRoutes"). + Strs("provided", cfg.AnnounceRoutes). + Strs("default", validCfg.AnnounceRoutes). + Msg("falling back to default configuration") + } + if len(cfg.ScrapeRoutes) == 0 { + validCfg.ScrapeRoutes = []string{defaultScrapeRoute} + logger.Warn(). + Str("name", "ScrapeRoutes"). + Strs("provided", cfg.ScrapeRoutes). + Strs("default", validCfg.ScrapeRoutes). + Msg("falling back to default configuration") + } validCfg.ParseOptions.ParseOptions = cfg.ParseOptions.ParseOptions.Validate() return } @@ -89,9 +109,6 @@ func NewFrontend(c conf.MapConfig, logic *middleware.Logic) (frontend.Frontend, if cfg, err = cfg.Validate(); err != nil { return nil, err } - if len(cfg.AnnounceRoutes) < 1 || len(cfg.ScrapeRoutes) < 1 { - return nil, errRoutesNotProvided - } f := &httpFE{ logic: logic, diff --git a/frontend/options.go b/frontend/options.go index c104853..6a4d064 100644 --- a/frontend/options.go +++ b/frontend/options.go @@ -9,15 +9,12 @@ import ( ) const ( - defaultReadTimeout = 2 * time.Second - defaultWriteTimeout = 2 * time.Second + defaultReadTimeout = 2 * time.Second + defaultWriteTimeout = 2 * time.Second + defaultListenAddress = ":6969" ) -var ( - // ErrAddressNotProvided returned if listen address not provided in configuration - ErrAddressNotProvided = errors.New("address not provided") - errUnexpectedListenerType = errors.New("unexpected listener type") -) +var errUnexpectedListenerType = errors.New("unexpected listener type") // ListenOptions is the base configuration which may be used in net listeners type ListenOptions struct { @@ -30,11 +27,17 @@ type ListenOptions struct { // Validate checks if listen address provided and sets default // timeout options if needed -func (lo ListenOptions) Validate() (validOptions ListenOptions, err error) { +func (lo ListenOptions) Validate(ignoreTimeouts bool) (validOptions ListenOptions) { validOptions = lo if len(lo.Addr) == 0 { - err = ErrAddressNotProvided - } else { + validOptions.Addr = defaultListenAddress + logger.Warn(). + Str("name", "Addr"). + Str("provided", lo.Addr). + Str("default", validOptions.Addr). + Msg("falling back to default configuration") + } + if !ignoreTimeouts { if lo.ReadTimeout <= 0 { validOptions.ReadTimeout = defaultReadTimeout logger.Warn(). diff --git a/frontend/udp/frontend.go b/frontend/udp/frontend.go index 97c960e..c8c6689 100644 --- a/frontend/udp/frontend.go +++ b/frontend/udp/frontend.go @@ -23,13 +23,16 @@ import ( "github.com/sot-tech/mochi/pkg/timecache" ) +// Name - registered name of the frontend +const Name = "udp" + var ( logger = log.NewLogger("frontend/udp") allowedGeneratedPrivateKeyRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890") ) func init() { - frontend.RegisterBuilder("udp", NewFrontend) + frontend.RegisterBuilder(Name, NewFrontend) } // Config represents all the configurable options for a UDP BitTorrent @@ -43,13 +46,9 @@ type Config struct { // Validate sanity checks values set in a config and returns a new config with // default values replacing anything that is invalid. -func (cfg Config) Validate() (validCfg Config, err error) { - if len(cfg.Addr) == 0 { - err = frontend.ErrAddressNotProvided - return - } - +func (cfg Config) Validate() (validCfg Config) { validCfg = cfg + validCfg.ListenOptions = cfg.ListenOptions.Validate(true) // Generate a private key if one isn't provided by the user. if cfg.PrivateKey == "" { @@ -92,9 +91,7 @@ func NewFrontend(c conf.MapConfig, logic *middleware.Logic) (frontend.Frontend, if err = c.Unmarshal(&cfg); err != nil { return nil, err } - if cfg, err = cfg.Validate(); err != nil { - return nil, err - } + cfg = cfg.Validate() f := &udpFE{ closing: make(chan any), diff --git a/storage/memory/storage.go b/storage/memory/storage.go index 8d9740b..88c7c37 100644 --- a/storage/memory/storage.go +++ b/storage/memory/storage.go @@ -18,14 +18,18 @@ import ( "github.com/sot-tech/mochi/storage" ) -// Default config constants. -const defaultShardCount = 1024 +const ( + // Name - registered name of the storage + Name = "memory" + // Default config constants. + defaultShardCount = 1024 +) var logger = log.NewLogger("storage/memory") func init() { // Register the storage driver. - storage.RegisterDriver("memory", builder) + storage.RegisterDriver(Name, builder) } func builder(icfg conf.MapConfig) (storage.PeerStorage, error) {