github.com/decred/politeia@v1.4.0/politeiawww/config/config.go (about) 1 // Copyright (c) 2013-2014 The btcsuite developers 2 // Copyright (c) 2015-2022 The Decred developers 3 // Use of this source code is governed by an ISC 4 // license that can be found in the LICENSE file. 5 6 package config 7 8 import ( 9 "crypto/x509" 10 "encoding/pem" 11 "errors" 12 "fmt" 13 "net" 14 "net/mail" 15 "net/url" 16 "os" 17 "path/filepath" 18 "runtime" 19 "strings" 20 21 "github.com/decred/dcrd/dcrutil/v3" 22 "github.com/decred/politeia/politeiad/api/v1/identity" 23 "github.com/decred/politeia/politeiawww/logger" 24 "github.com/decred/politeia/util" 25 "github.com/decred/politeia/util/version" 26 "github.com/jessevdk/go-flags" 27 ) 28 29 // The following defaults are exported so that they can be used by external 30 // tools such as sysadmin and dev CLI tools. 31 const ( 32 // AppName is the politeiawww application name that is used to create the 33 // application home directory. 34 AppName = "politeiawww" 35 36 // DefaultMainnetPort is the default port that the HTTPS server will be 37 // listening on when politeiawww is configured to use the DCR mainnet. 38 DefaultMainnetPort = "4443" 39 40 // DefaultTestnetPort is the default port that the HTTPS server will be 41 // listening on when politeiawww is configured to use the DCR testnet. 42 DefaultTestnetPort = "4443" 43 ) 44 45 // The following defaults are exported so that they can be used by external 46 // tools such as sysadmin and dev CLI tools. 47 var ( 48 // DefaultHomeDir is the default path for the politeiawww application home 49 // directory. 50 DefaultHomeDir = dcrutil.AppDataDir(AppName, false) 51 52 // DefaultDataDir is the default path for the politeiawww application data 53 // directory. In practice, the data directory is namespaced by network, so 54 // the network name must be appended onto this path at runtime in order to 55 // get the true data directory. 56 DefaultDataDir = filepath.Join(DefaultHomeDir, defaultDataDirname) 57 58 // DefaultHTTPSCert is the default path for the politeiawww HTTPS 59 // certificate. 60 DefaultHTTPSCert = filepath.Join(DefaultHomeDir, defaultHTTPSCertFilename) 61 ) 62 63 const ( 64 // General application settings 65 defaultDataDirname = "data" 66 defaultLogDirname = "logs" 67 defaultConfigFilename = "politeiawww.conf" 68 defaultLogFilename = "politeiawww.log" 69 defaultLogLevel = "info" 70 71 // HTTP server settings 72 defaultHTTPSCertFilename = "https.cert" 73 defaultHTTPSKeyFilename = "https.key" 74 defaultCookieKeyFilename = "cookie.key" 75 76 defaultReadTimeout int64 = 5 // In seconds 77 defaultWriteTimeout int64 = 60 // In seconds 78 defaultReqBodySizeLimit int64 = 3 * 1024 * 1024 // 3 MiB 79 defaultWebsocketReadLimit int64 = 4 * 1024 * 1024 // 4 KiB 80 defaultPluginBatchLimit uint32 = 20 81 82 // politeiad RPC settings 83 defaultRPCHost = "localhost" 84 defaultRPCMainnetPort = "49374" 85 defaultRPCTestnetPort = "59374" 86 defaultRPCCertFilename = "rpc.cert" 87 defaultIdentityFilename = "identity.json" 88 allowInteractive = "i-know-this-is-a-bad-idea" 89 90 // Database settings 91 LevelDB = "leveldb" 92 CockroachDB = "cockroachdb" 93 MySQL = "mysql" 94 95 defaultMySQLDBHost = "localhost:3306" 96 defaultCockroachDBHost = "localhost:26257" 97 98 // SMTP settings 99 defaultMailAddress = "Politeia <noreply@example.org>" 100 101 // User layer settings 102 defaultUserPlugin = "" 103 defaultAuthPlugin = "" 104 105 // Environmental variable config settings 106 envDBPass = "DBPASS" 107 ) 108 109 var ( 110 // General application settings 111 defaultConfigFile = filepath.Join(DefaultHomeDir, defaultConfigFilename) 112 defaultLogDir = filepath.Join(DefaultHomeDir, defaultLogDirname) 113 ) 114 115 // Config defines the configuration options for politeiawww. 116 type Config struct { 117 // General application settings 118 ShowVersion bool `short:"V" long:"version" description:"Display version information and exit"` 119 HomeDir string `short:"A" long:"appdata" description:"Path to application home directory"` 120 ConfigFile string `short:"C" long:"configfile" description:"Path to configuration file"` 121 DataDir string `short:"b" long:"datadir" description:"Directory to store data"` 122 LogDir string `long:"logdir" description:"Directory to log output."` 123 TestNet bool `long:"testnet" description:"Use the test network"` 124 DebugLevel string `short:"d" long:"debuglevel" description:"Logging level for all subsystems {trace, debug, info, warn, error, critical} -- You may also specify <subsystem>=<level>,<subsystem2>=<level>,... to set the log level for individual subsystems -- Use show to list available subsystems"` 125 126 // HTTP server settings 127 Listeners []string `long:"listen" description:"Add an interface/port to listen for connections (default all interfaces port: 4443)"` 128 HTTPSCert string `long:"httpscert" description:"File containing the https certificate file"` 129 HTTPSKey string `long:"httpskey" description:"File containing the https certificate key"` 130 CookieKeyFile string `long:"cookiekey" description:"File containing the secret cookies key"` 131 ReadTimeout int64 `long:"readtimeout" description:"Maximum duration in seconds that is spent reading the request headers and body"` 132 WriteTimeout int64 `long:"writetimeout" description:"Maximum duration in seconds that a request connection is kept open"` 133 ReqBodySizeLimit int64 `long:"reqbodysizelimit" description:"Maximum number of bytes allowed in a request body submitted by a client"` 134 WebsocketReadLimit int64 `long:"websocketreadlimit" description:"Maximum number of bytes allowed for a message read from a websocket client"` 135 PluginBatchLimit uint32 `long:"pluginbatchlimit" description:"Maximum number of plugins command allowed in a batch request."` 136 137 // politeiad RPC settings 138 RPCHost string `long:"rpchost" description:"politeiad host <host>:<port>"` 139 RPCCert string `long:"rpccert" description:"File containing the politeiad https certificate file"` 140 RPCIdentityFile string `long:"rpcidentityfile" description:"Path to file containing the politeiad identity"` 141 RPCUser string `long:"rpcuser" description:"RPC username for privileged politeaid commands"` 142 RPCPass string `long:"rpcpass" description:"RPC password for privileged politeiad commands"` 143 FetchIdentity bool `long:"fetchidentity" description:"Fetch the identity from politeiad"` 144 Interactive string `long:"interactive" description:"Set to i-know-this-is-a-bad-idea to turn off interactive mode during --fetchidentity"` 145 146 // User database settings 147 UserDB string `long:"userdb" description:"Database choice for the user database"` 148 DBHost string `long:"dbhost" description:"Database ip:port"` 149 DBPass string // Provided in env variable "DBPASS" 150 151 // SMTP settings 152 MailHost string `long:"mailhost" description:"Email server address <host>:<port>"` 153 MailCert string `long:"mailcert" description:"Email server certificate file"` 154 MailSkipVerify bool `long:"mailskipverify" description:"Skip email server TLS verification"` 155 MailUser string `long:"mailuser" description:"Email server username"` 156 MailPass string `long:"mailpass" description:"Email server password"` 157 MailAddress string `long:"mailaddress" description:"Email address for outgoing email in the format: name <address>"` 158 159 // User layer settings 160 DisableUsers bool `long:"disableusers" description:"Disable the user layer"` 161 UserPlugin string `long:"userplugin" description:"ID of the plugin that manages user accounts"` 162 AuthPlugin string `long:"authplugin" description:"ID of the plugin that handles user authorization"` 163 164 // Plugin settings 165 Plugins []string `long:"plugin" description:"IDs of all plugins to be registered"` 166 PluginSettings []string `long:"pluginsetting" description:"Plugin settings"` 167 168 // Embedded legacy settings. This will be deleted soon. 169 DisableLegacy bool `long:"disablelegacy" description:"Disable legacy routes"` 170 LegacyConfig 171 172 Version string 173 ActiveNet *ChainParams // Active DCR network 174 Identity *identity.PublicIdentity // politeiad identity 175 SystemCerts *x509.CertPool 176 } 177 178 // Load initializes and parses the config using a config file and command line 179 // options. 180 // 181 // The configuration proceeds as follows: 182 // 1. Start with a default config with sane settings 183 // 2. Pre-parse the command line to check for an alternative config file 184 // 3. Load configuration file overwriting defaults with any specified options 185 // 4. Parse CLI options and overwrite/add any specified options 186 // 187 // The above results in rpc functioning properly without any config settings 188 // while still allowing the user to override settings with config files and 189 // command line options. Command line options always take precedence. 190 func Load() (*Config, []string, error) { 191 // Setup the default config. Most of settings that contain file 192 // paths are not set to default values and handled later on, once 193 // the CLI args and config file have been fully parsed so that we 194 // don't need to worry about updating the default paths with any 195 // changes to the home dir. 196 cfg := &Config{ 197 // General application settings 198 ShowVersion: false, 199 HomeDir: DefaultHomeDir, 200 ConfigFile: defaultConfigFile, 201 DataDir: DefaultDataDir, 202 LogDir: defaultLogDir, 203 TestNet: false, 204 DebugLevel: defaultLogLevel, 205 206 // HTTP server settings 207 Listeners: []string{}, 208 HTTPSCert: "", 209 HTTPSKey: "", 210 CookieKeyFile: "", 211 ReadTimeout: defaultReadTimeout, 212 WriteTimeout: defaultWriteTimeout, 213 ReqBodySizeLimit: defaultReqBodySizeLimit, 214 WebsocketReadLimit: defaultWebsocketReadLimit, 215 PluginBatchLimit: defaultPluginBatchLimit, 216 217 // User database settings 218 UserDB: LevelDB, 219 220 // SMTP settings 221 MailAddress: defaultMailAddress, 222 223 // User settings 224 UserPlugin: defaultUserPlugin, 225 AuthPlugin: defaultAuthPlugin, 226 227 // Legacy settings. These are deprecated and will be removed soon. 228 LegacyConfig: LegacyConfig{ 229 Mode: PiWWWMode, 230 PaywallAmount: defaultPaywallAmount, 231 MinConfirmationsRequired: defaultPaywallMinConfirmations, 232 VoteDurationMin: defaultVoteDurationMin, 233 VoteDurationMax: defaultVoteDurationMax, 234 MailRateLimit: defaultMailRateLimit, 235 }, 236 237 Version: version.Version, 238 } 239 240 // Service options which are only added on Windows. 241 serviceOpts := serviceOptions{} 242 243 // Pre-parse the command line options to see if an alternative config 244 // file or the version flag was specified. Any errors aside from the 245 // help message error can be ignored here since they will be caught by 246 // the final parse below. 247 var ( 248 preCfg = cfg 249 preParser = newConfigParser(preCfg, &serviceOpts, flags.HelpFlag) 250 usageMessage = fmt.Sprintf("use %s -h to show usage", AppName) 251 ) 252 _, err := preParser.Parse() 253 if err != nil { 254 var e *flags.Error 255 if errors.As(err, &e) { 256 fmt.Fprintln(os.Stderr, err) 257 fmt.Fprintln(os.Stderr, usageMessage) 258 os.Exit(0) 259 } 260 return nil, nil, err 261 } 262 263 // Show the version and exit if the version flag was specified. 264 if preCfg.ShowVersion { 265 fmt.Printf("%s version %s (Go version %s %s/%s)\n", AppName, 266 cfg.Version, runtime.Version(), runtime.GOOS, 267 runtime.GOARCH) 268 os.Exit(0) 269 } 270 271 // Perform service command and exit if specified. Invalid service 272 // commands show an appropriate error. Only runs on Windows since 273 // the runServiceCommand function will be nil when not on Windows. 274 if serviceOpts.ServiceCommand != "" && runServiceCommand != nil { 275 err := runServiceCommand(serviceOpts.ServiceCommand) 276 if err != nil { 277 fmt.Fprintln(os.Stderr, err) 278 } 279 os.Exit(0) 280 } 281 282 // Update the home directory if specified. Since the home directory is 283 // updated, other variables need to be updated to reflect the new changes. 284 if preCfg.HomeDir != "" { 285 cfg.HomeDir, _ = filepath.Abs(preCfg.HomeDir) 286 287 if preCfg.ConfigFile == defaultConfigFile { 288 cfg.ConfigFile = filepath.Join(cfg.HomeDir, defaultConfigFilename) 289 } else { 290 cfg.ConfigFile = preCfg.ConfigFile 291 } 292 if preCfg.DataDir == DefaultDataDir { 293 cfg.DataDir = filepath.Join(cfg.HomeDir, defaultDataDirname) 294 } else { 295 cfg.DataDir = preCfg.DataDir 296 } 297 if preCfg.LogDir == defaultLogDir { 298 cfg.LogDir = filepath.Join(cfg.HomeDir, defaultLogDirname) 299 } else { 300 cfg.LogDir = preCfg.LogDir 301 } 302 } 303 304 // Clean and exand the config file path. 305 cfg.ConfigFile = util.CleanAndExpandPath(cfg.ConfigFile) 306 307 // Load additional config from file. 308 var configFileError error 309 parser := newConfigParser(cfg, &serviceOpts, flags.Default) 310 err = flags.NewIniParser(parser).ParseFile(cfg.ConfigFile) 311 if err != nil { 312 var e *os.PathError 313 if !errors.As(err, &e) { 314 fmt.Fprintf(os.Stderr, "Error parsing config "+ 315 "file: %v\n", err) 316 fmt.Fprintln(os.Stderr, usageMessage) 317 return nil, nil, err 318 } 319 configFileError = err 320 } 321 322 // Parse command line options again to ensure they take precedence. 323 remainingArgs, err := parser.Parse() 324 if err != nil { 325 return nil, nil, err 326 } 327 328 // Special show command to list supported subsystems and exit. 329 if cfg.DebugLevel == "show" { 330 fmt.Println("Supported subsystems", logger.SupportedSubsystems()) 331 os.Exit(0) 332 } 333 334 // Clean and expand all file paths 335 cfg.HomeDir = util.CleanAndExpandPath(cfg.HomeDir) 336 cfg.ConfigFile = util.CleanAndExpandPath(cfg.ConfigFile) 337 cfg.DataDir = util.CleanAndExpandPath(cfg.DataDir) 338 cfg.LogDir = util.CleanAndExpandPath(cfg.LogDir) 339 340 // Create the home directory if it doesn't already exist. 341 funcName := "loadConfig" 342 err = os.MkdirAll(cfg.HomeDir, 0700) 343 if err != nil { 344 // Show a nicer error message if it's because a symlink 345 // is linked to a directory that does not exist (probably 346 // because it's not mounted). 347 var e *os.PathError 348 if errors.As(err, &e) && os.IsExist(err) { 349 if link, lerr := os.Readlink(e.Path); lerr == nil { 350 str := "is symlink %s -> %s mounted?" 351 err = fmt.Errorf(str, e.Path, link) 352 } 353 } 354 355 str := "%s: Failed to create home directory: %v" 356 err := fmt.Errorf(str, funcName, err) 357 fmt.Fprintln(os.Stderr, err) 358 return nil, nil, err 359 } 360 361 // Setup the active network 362 cfg.ActiveNet = &MainNetParams 363 if cfg.TestNet { 364 cfg.ActiveNet = &TestNet3Params 365 } 366 367 // Append the network type to the data and log directories 368 // so that they are "namespaced" per network. 369 cfg.DataDir = filepath.Join(cfg.DataDir, cfg.ActiveNet.Name) 370 cfg.LogDir = filepath.Join(cfg.LogDir, cfg.ActiveNet.Name) 371 372 // Parse, validate, and set debug log level(s). 373 if err := parseAndSetDebugLevels(cfg.DebugLevel); err != nil { 374 err := fmt.Errorf("%s: %v", funcName, err.Error()) 375 fmt.Fprintln(os.Stderr, err) 376 fmt.Fprintln(os.Stderr, usageMessage) 377 return nil, nil, err 378 } 379 380 // Initialize log rotation. After the log rotation has 381 // been initialized, the logger variables may be used. 382 logger.InitLogRotator(filepath.Join(cfg.LogDir, defaultLogFilename)) 383 384 // Load the system cert pool 385 cfg.SystemCerts, err = x509.SystemCertPool() 386 if err != nil { 387 return nil, nil, err 388 } 389 390 // Setup the various config settings 391 err = setupHTTPServerSettings(cfg) 392 if err != nil { 393 return nil, nil, err 394 } 395 err = setupRPCSettings(cfg) 396 if err != nil { 397 return nil, nil, err 398 } 399 err = setupUserDBSettings(cfg) 400 if err != nil { 401 return nil, nil, err 402 } 403 err = setupMailSettings(cfg) 404 if err != nil { 405 return nil, nil, err 406 } 407 err = setupLegacyConfig(cfg) 408 if err != nil { 409 return nil, nil, err 410 } 411 412 // Warn about missing config file only after all other 413 // configuration is done. This prevents the warning on 414 // help messages and invalid options. Note this should 415 // go directly before the return. 416 if configFileError != nil { 417 log.Warnf("%v", configFileError) 418 } 419 420 return cfg, remainingArgs, nil 421 } 422 423 // setupHTTPServerSettings sets up the politeiawww http server config settings. 424 func setupHTTPServerSettings(cfg *Config) error { 425 // Setup default values if none were provided. Only the file path 426 // defaults need to be checked. All other defaults should be set 427 // on the original config initialization. 428 if cfg.HTTPSCert == "" { 429 cfg.HTTPSCert = DefaultHTTPSCert 430 } 431 if cfg.HTTPSKey == "" { 432 cfg.HTTPSKey = filepath.Join(cfg.HomeDir, defaultHTTPSKeyFilename) 433 } 434 if cfg.CookieKeyFile == "" { 435 cfg.CookieKeyFile = filepath.Join(cfg.HomeDir, defaultCookieKeyFilename) 436 } 437 438 // Clean file paths 439 cfg.HTTPSCert = util.CleanAndExpandPath(cfg.HTTPSCert) 440 cfg.HTTPSKey = util.CleanAndExpandPath(cfg.HTTPSKey) 441 cfg.CookieKeyFile = util.CleanAndExpandPath(cfg.CookieKeyFile) 442 443 // Add the default listener if none were specified. The 444 // default listener is all addresses on the listen port 445 // for the network we are to connect to. 446 port := DefaultMainnetPort 447 if cfg.TestNet { 448 port = DefaultTestnetPort 449 } 450 if len(cfg.Listeners) == 0 { 451 cfg.Listeners = []string{ 452 net.JoinHostPort("", port), 453 } 454 } 455 456 // Add default port to all listener addresses if needed 457 // and remove duplicate addresses. 458 cfg.Listeners = normalizeAddresses(cfg.Listeners, port) 459 460 return nil 461 } 462 463 // setupRPCSettings sets up the politeiad RPC config settings. 464 func setupRPCSettings(cfg *Config) error { 465 // Setup default values if none were provided 466 if cfg.RPCCert == "" { 467 cfg.RPCCert = filepath.Join(cfg.HomeDir, defaultRPCCertFilename) 468 } 469 if cfg.RPCIdentityFile == "" { 470 cfg.RPCIdentityFile = filepath.Join(cfg.HomeDir, defaultIdentityFilename) 471 } 472 473 // Clean file paths 474 cfg.RPCCert = util.CleanAndExpandPath(cfg.RPCCert) 475 cfg.RPCIdentityFile = util.CleanAndExpandPath(cfg.RPCIdentityFile) 476 477 // Setup the RPC host 478 if cfg.RPCHost == "" { 479 cfg.RPCHost = defaultRPCHost 480 } 481 port := defaultRPCMainnetPort 482 if cfg.TestNet { 483 port = defaultRPCTestnetPort 484 } 485 cfg.RPCHost = util.NormalizeAddress(cfg.RPCHost, port) 486 u, err := url.Parse("https://" + cfg.RPCHost) 487 if err != nil { 488 return fmt.Errorf("parse politeiad RPC host: %v", err) 489 } 490 cfg.RPCHost = u.String() 491 492 // Verify remaining RPC settings 493 if cfg.RPCUser == "" { 494 return fmt.Errorf("politeiad rpc user " + 495 "must be provided with --rpcuser") 496 } 497 if cfg.RPCPass == "" { 498 return fmt.Errorf("politeiad rpc pass " + 499 "must be provided with --rpcpass") 500 } 501 if cfg.Interactive != "" && cfg.Interactive != allowInteractive { 502 return fmt.Errorf("--interactive flag used incorrectly") 503 } 504 505 // Load the identity politeaid identity from disk 506 if cfg.FetchIdentity { 507 // Don't try to load the identity from the existing 508 // file if the caller is trying to fetch a new one. 509 return nil 510 } 511 if !util.FileExists(cfg.RPCIdentityFile) { 512 return fmt.Errorf("identity file not found; you must load the " + 513 "identity from politeiad first using the --fetchidentity flag") 514 } 515 cfg.Identity, err = identity.LoadPublicIdentity(cfg.RPCIdentityFile) 516 if err != nil { 517 return err 518 } 519 520 log.Infof("Identity loaded from: %v", cfg.RPCIdentityFile) 521 522 return nil 523 } 524 525 // setupUserDBSettings sets up the user database config settings. 526 func setupUserDBSettings(cfg *Config) error { 527 // Verify database selection 528 switch cfg.UserDB { 529 case LevelDB, CockroachDB, MySQL: 530 // These are allowed 531 default: 532 return fmt.Errorf("invalid db selection '%v'", 533 cfg.UserDB) 534 } 535 536 // Verify individual database requirements 537 switch cfg.UserDB { 538 case LevelDB: 539 // LevelDB should not have a host 540 if cfg.DBHost != "" { 541 return fmt.Errorf("dbhost should not be set when using leveldb") 542 } 543 544 case CockroachDB: 545 // The CockroachDB option is deprecated. All CockroachDB 546 // validation is performed in the legacy config setup. 547 548 case MySQL: 549 // Verify database host 550 if cfg.DBHost == "" { 551 cfg.DBHost = defaultMySQLDBHost 552 } 553 _, err := url.Parse(cfg.DBHost) 554 if err != nil { 555 return fmt.Errorf("invalid dbhost '%v': %v", 556 cfg.DBHost, err) 557 } 558 559 // Pull password from env variable 560 cfg.DBPass = os.Getenv(envDBPass) 561 if cfg.DBPass == "" { 562 return fmt.Errorf("dbpass not found; you must provide "+ 563 "the database password for the politeiawww user in "+ 564 "the env variable %v", envDBPass) 565 } 566 } 567 568 return nil 569 } 570 571 // setupMailSettings sets up the SMTP mail server config settings. 572 func setupMailSettings(cfg *Config) error { 573 // Clean file paths 574 cfg.MailCert = util.CleanAndExpandPath(cfg.MailCert) 575 576 // Verify the host 577 u, err := url.Parse(cfg.MailHost) 578 if err != nil { 579 return fmt.Errorf("unable to parse mail host: %v", err) 580 } 581 cfg.MailHost = u.String() 582 583 // Verify the certificate 584 if cfg.MailCert != "" { 585 if cfg.MailSkipVerify { 586 return fmt.Errorf("cannot set mailskipverify " + 587 "and provide a mailcert at the same time") 588 } 589 if !util.FileExists(cfg.MailCert) { 590 return fmt.Errorf("mail cert file '%v' not found", 591 cfg.MailCert) 592 } 593 b, err := os.ReadFile(cfg.MailCert) 594 if err != nil { 595 return fmt.Errorf("read mail cert: %v", err) 596 } 597 block, _ := pem.Decode(b) 598 cert, err := x509.ParseCertificate(block.Bytes) 599 if err != nil { 600 return fmt.Errorf("parse mail cert: %v", err) 601 } 602 cfg.SystemCerts.AddCert(cert) 603 } 604 605 // Verify the provided email address 606 a, err := mail.ParseAddress(cfg.MailAddress) 607 if err != nil { 608 return fmt.Errorf("cannot parse mail address '%v': %v", 609 cfg.MailAddress, err) 610 } 611 cfg.MailAddress = a.String() 612 613 return nil 614 } 615 616 // runServiceCommand is only set to a real function on Windows. It is used 617 // to parse and execute service commands specified via the -s flag. 618 var runServiceCommand func(string) error 619 620 // serviceOptions defines the configuration options for the rpc as a service 621 // on Windows. 622 type serviceOptions struct { 623 ServiceCommand string `short:"s" long:"service" description:"Service command {install, remove, start, stop}"` 624 } 625 626 // newConfigParser returns a new command line flags parser. 627 func newConfigParser(cfg *Config, so *serviceOptions, options flags.Options) *flags.Parser { 628 parser := flags.NewParser(cfg, options) 629 if runtime.GOOS == "windows" { 630 parser.AddGroup("Service Options", "Service Options", so) 631 } 632 return parser 633 } 634 635 // validLogLevel returns whether or not logLevel is a valid debug log level. 636 func validLogLevel(logLevel string) bool { 637 switch logLevel { 638 case "trace": 639 fallthrough 640 case "debug": 641 fallthrough 642 case "info": 643 fallthrough 644 case "warn": 645 fallthrough 646 case "error": 647 fallthrough 648 case "critical": 649 return true 650 } 651 return false 652 } 653 654 // parseAndSetDebugLevels attempts to parse the specified debug level and set 655 // the levels accordingly. An appropriate error is returned if anything is 656 // invalid. 657 func parseAndSetDebugLevels(debugLevel string) error { 658 // When the specified string doesn't have any delimters, treat it as 659 // the log level for all subsystems. 660 if !strings.Contains(debugLevel, ",") && !strings.Contains(debugLevel, "=") { 661 // Validate debug log level. 662 if !validLogLevel(debugLevel) { 663 str := "The specified debug level [%v] is invalid" 664 return fmt.Errorf(str, debugLevel) 665 } 666 667 // Change the logging level for all subsystems. 668 logger.SetLogLevels(debugLevel) 669 670 return nil 671 } 672 673 // Split the specified string into subsystem/level pairs while detecting 674 // issues and update the log levels accordingly. 675 for _, logLevelPair := range strings.Split(debugLevel, ",") { 676 if !strings.Contains(logLevelPair, "=") { 677 str := "The specified debug level contains an invalid " + 678 "subsystem/level pair [%v]" 679 return fmt.Errorf(str, logLevelPair) 680 } 681 682 // Extract the specified subsystem and log level. 683 fields := strings.Split(logLevelPair, "=") 684 subsysID, logLevel := fields[0], fields[1] 685 686 // Validate subsystem. 687 subsystems := make(map[string]struct{}) 688 for _, v := range logger.SupportedSubsystems() { 689 subsystems[v] = struct{}{} 690 } 691 if _, exists := subsystems[subsysID]; !exists { 692 str := "The specified subsystem [%v] is invalid -- " + 693 "supported subsytems %v" 694 return fmt.Errorf(str, subsysID, logger.SupportedSubsystems()) 695 } 696 697 // Validate log level. 698 if !validLogLevel(logLevel) { 699 str := "The specified debug level [%v] is invalid" 700 return fmt.Errorf(str, logLevel) 701 } 702 703 logger.SetLogLevel(subsysID, logLevel) 704 } 705 706 return nil 707 } 708 709 // normalizeAddresses returns a new slice with all the passed peer addresses 710 // normalized with the given default port, and all duplicates removed. 711 func normalizeAddresses(addrs []string, defaultPort string) []string { 712 for i, addr := range addrs { 713 addrs[i] = util.NormalizeAddress(addr, defaultPort) 714 } 715 716 return removeDuplicateAddresses(addrs) 717 } 718 719 // removeDuplicateAddresses returns a new slice with all duplicate entries in 720 // addrs removed. 721 func removeDuplicateAddresses(addrs []string) []string { 722 result := make([]string, 0, len(addrs)) 723 seen := map[string]struct{}{} 724 for _, val := range addrs { 725 if _, ok := seen[val]; !ok { 726 result = append(result, val) 727 seen[val] = struct{}{} 728 } 729 } 730 return result 731 }