github.com/decred/politeia@v1.4.0/politeiad/config.go (about)

     1  // Copyright (c) 2013-2014 The btcsuite developers
     2  // Copyright (c) 2015-2020 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 main
     7  
     8  import (
     9  	"encoding/base64"
    10  	"errors"
    11  	"fmt"
    12  	"net"
    13  	"net/url"
    14  	"os"
    15  	"path/filepath"
    16  	"runtime"
    17  	"sort"
    18  	"strconv"
    19  	"strings"
    20  
    21  	"github.com/decred/dcrd/dcrutil/v3"
    22  	v1 "github.com/decred/dcrtime/api/v1"
    23  	"github.com/decred/politeia/util"
    24  	"github.com/decred/politeia/util/version"
    25  	flags "github.com/jessevdk/go-flags"
    26  )
    27  
    28  const (
    29  	defaultConfigFilename   = "politeiad.conf"
    30  	defaultDataDirname      = "data"
    31  	defaultLogLevel         = "info"
    32  	defaultLogDirname       = "logs"
    33  	defaultLogFilename      = "politeiad.log"
    34  	defaultIdentityFilename = "identity.json"
    35  
    36  	defaultMainnetPort = "49374"
    37  	defaultTestnetPort = "59374"
    38  
    39  	defaultMainnetDcrdata = "dcrdata.decred.org:443"
    40  	defaultTestnetDcrdata = "testnet.decred.org:443"
    41  
    42  	// Backend options
    43  	backendGit     = "git"
    44  	backendTstore  = "tstore"
    45  	defaultBackend = backendTstore
    46  
    47  	// Tstore default settings
    48  	defaultDBHost   = "localhost:3306" // MySQL default host
    49  	defaultTlogHost = "localhost:8090"
    50  
    51  	// Environment variables
    52  	envDBPass = "DBPASS"
    53  )
    54  
    55  var (
    56  	defaultHomeDir       = dcrutil.AppDataDir("politeiad", false)
    57  	defaultConfigFile    = filepath.Join(defaultHomeDir, defaultConfigFilename)
    58  	defaultDataDir       = filepath.Join(defaultHomeDir, defaultDataDirname)
    59  	defaultHTTPSKeyFile  = filepath.Join(defaultHomeDir, "https.key")
    60  	defaultHTTPSCertFile = filepath.Join(defaultHomeDir, "https.cert")
    61  	defaultLogDir        = filepath.Join(defaultHomeDir, defaultLogDirname)
    62  	defaultIdentityFile  = filepath.Join(defaultHomeDir, defaultIdentityFilename)
    63  
    64  	// defaultReadTimeout is the maximum duration in seconds that is spent
    65  	// reading the request headers and body.
    66  	defaultReadTimeout int64 = 5
    67  
    68  	// defaultWriteTimeout is the maximum duration in seconds that a request
    69  	// connection is kept open.
    70  	defaultWriteTimeout int64 = 60
    71  
    72  	// defaultReqBodySizeLimit is the maximum number of bytes allowed in a
    73  	// request body.
    74  	defaultReqBodySizeLimit int64 = 3 * 1024 * 1024 // 3 MiB
    75  )
    76  
    77  // runServiceCommand is only set to a real function on Windows.  It is used
    78  // to parse and execute service commands specified via the -s flag.
    79  var runServiceCommand func(string) error
    80  
    81  // config defines the configuration options for dcrd.
    82  //
    83  // See loadConfig for details on the configuration load process.
    84  type config struct {
    85  	HomeDir     string   `short:"A" long:"appdata" description:"Path to application home directory"`
    86  	ShowVersion bool     `short:"V" long:"version" description:"Display version information and exit"`
    87  	ConfigFile  string   `short:"C" long:"configfile" description:"Path to configuration file"`
    88  	DataDir     string   `short:"b" long:"datadir" description:"Directory to store data"`
    89  	LogDir      string   `long:"logdir" description:"Directory to log output."`
    90  	TestNet     bool     `long:"testnet" description:"Use the test network"`
    91  	SimNet      bool     `long:"simnet" description:"Use the simulation test network"`
    92  	Profile     string   `long:"profile" description:"Enable HTTP profiling on given port -- NOTE port must be between 1024 and 65536"`
    93  	CPUProfile  string   `long:"cpuprofile" description:"Write CPU profile to the specified file"`
    94  	MemProfile  string   `long:"memprofile" description:"Write mem profile to the specified file"`
    95  	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"`
    96  	Listeners   []string `long:"listen" description:"Add an interface/port to listen for connections (default all interfaces port: 49152, testnet: 59152)"`
    97  	Version     string
    98  	HTTPSCert   string `long:"httpscert" description:"File containing the https certificate file"`
    99  	HTTPSKey    string `long:"httpskey" description:"File containing the https certificate key"`
   100  	RPCUser     string `long:"rpcuser" description:"RPC user name for privileged commands"`
   101  	RPCPass     string `long:"rpcpass" description:"RPC password for privileged commands"`
   102  	DcrtimeHost string `long:"dcrtimehost" description:"Dcrtime ip:port"`
   103  	DcrtimeCert string `long:"dcrtimecert" description:"Dcrtime HTTPS certificate"`
   104  	Identity    string `long:"identity" description:"File containing the politeiad identity file"`
   105  	Backend     string `long:"backend" description:"Backend type"`
   106  	Fsck        bool   `long:"fsck" description:"Perform filesystem checks on all record and plugin data"`
   107  
   108  	// Web server settings
   109  	ReadTimeout      int64 `long:"readtimeout" description:"Maximum duration in seconds that is spent reading the request headers and body"`
   110  	WriteTimeout     int64 `long:"writetimeout" description:"Maximum duration in seconds that a request connection is kept open"`
   111  	ReqBodySizeLimit int64 `long:"reqbodysizelimit" description:"Maximum number of bytes allowed for a request body from a http client"`
   112  
   113  	// Git backend options
   114  	GitTrace    bool   `long:"gittrace" description:"Enable git tracing in logs"`
   115  	DcrdataHost string `long:"dcrdatahost" description:"Dcrdata ip:port"`
   116  
   117  	// Tstore backend options
   118  	DBHost   string `long:"dbhost" description:"Database ip:port"`
   119  	DBPass   string // Provided in env variable "DBPASS"
   120  	TlogHost string `long:"tloghost" description:"Trillian log ip:port"`
   121  
   122  	// Plugin options
   123  	Plugins        []string `long:"plugin" description:"Plugins"`
   124  	PluginSettings []string `long:"pluginsetting" description:"Plugin settings"`
   125  }
   126  
   127  // serviceOptions defines the configuration options for the daemon as a service
   128  // on Windows.
   129  type serviceOptions struct {
   130  	ServiceCommand string `short:"s" long:"service" description:"Service command {install, remove, start, stop}"`
   131  }
   132  
   133  // validLogLevel returns whether or not logLevel is a valid debug log level.
   134  func validLogLevel(logLevel string) bool {
   135  	switch logLevel {
   136  	case "trace":
   137  		fallthrough
   138  	case "debug":
   139  		fallthrough
   140  	case "info":
   141  		fallthrough
   142  	case "warn":
   143  		fallthrough
   144  	case "error":
   145  		fallthrough
   146  	case "critical":
   147  		return true
   148  	}
   149  	return false
   150  }
   151  
   152  // supportedSubsystems returns a sorted slice of the supported subsystems for
   153  // logging purposes.
   154  func supportedSubsystems() []string {
   155  	// Convert the subsystemLoggers map keys to a slice.
   156  	subsystems := make([]string, 0, len(subsystemLoggers))
   157  	for subsysID := range subsystemLoggers {
   158  		subsystems = append(subsystems, subsysID)
   159  	}
   160  
   161  	// Sort the subsytems for stable display.
   162  	sort.Strings(subsystems)
   163  	return subsystems
   164  }
   165  
   166  // parseAndSetDebugLevels attempts to parse the specified debug level and set
   167  // the levels accordingly.  An appropriate error is returned if anything is
   168  // invalid.
   169  func parseAndSetDebugLevels(debugLevel string) error {
   170  	// When the specified string doesn't have any delimters, treat it as
   171  	// the log level for all subsystems.
   172  	if !strings.Contains(debugLevel, ",") && !strings.Contains(debugLevel, "=") {
   173  		// Validate debug log level.
   174  		if !validLogLevel(debugLevel) {
   175  			str := "The specified debug level [%v] is invalid"
   176  			return fmt.Errorf(str, debugLevel)
   177  		}
   178  
   179  		// Change the logging level for all subsystems.
   180  		setLogLevels(debugLevel)
   181  
   182  		return nil
   183  	}
   184  
   185  	// Split the specified string into subsystem/level pairs while detecting
   186  	// issues and update the log levels accordingly.
   187  	for _, logLevelPair := range strings.Split(debugLevel, ",") {
   188  		if !strings.Contains(logLevelPair, "=") {
   189  			str := "The specified debug level contains an invalid " +
   190  				"subsystem/level pair [%v]"
   191  			return fmt.Errorf(str, logLevelPair)
   192  		}
   193  
   194  		// Extract the specified subsystem and log level.
   195  		fields := strings.Split(logLevelPair, "=")
   196  		subsysID, logLevel := fields[0], fields[1]
   197  
   198  		// Validate subsystem.
   199  		if _, exists := subsystemLoggers[subsysID]; !exists {
   200  			str := "The specified subsystem [%v] is invalid -- " +
   201  				"supported subsytems %v"
   202  			return fmt.Errorf(str, subsysID, supportedSubsystems())
   203  		}
   204  
   205  		// Validate log level.
   206  		if !validLogLevel(logLevel) {
   207  			str := "The specified debug level [%v] is invalid"
   208  			return fmt.Errorf(str, logLevel)
   209  		}
   210  
   211  		setLogLevel(subsysID, logLevel)
   212  	}
   213  
   214  	return nil
   215  }
   216  
   217  // removeDuplicateAddresses returns a new slice with all duplicate entries in
   218  // addrs removed.
   219  func removeDuplicateAddresses(addrs []string) []string {
   220  	result := make([]string, 0, len(addrs))
   221  	seen := map[string]struct{}{}
   222  	for _, val := range addrs {
   223  		if _, ok := seen[val]; !ok {
   224  			result = append(result, val)
   225  			seen[val] = struct{}{}
   226  		}
   227  	}
   228  	return result
   229  }
   230  
   231  // normalizeAddresses returns a new slice with all the passed peer addresses
   232  // normalized with the given default port, and all duplicates removed.
   233  func normalizeAddresses(addrs []string, defaultPort string) []string {
   234  	for i, addr := range addrs {
   235  		addrs[i] = util.NormalizeAddress(addr, defaultPort)
   236  	}
   237  
   238  	return removeDuplicateAddresses(addrs)
   239  }
   240  
   241  // newConfigParser returns a new command line flags parser.
   242  func newConfigParser(cfg *config, so *serviceOptions, options flags.Options) *flags.Parser {
   243  	parser := flags.NewParser(cfg, options)
   244  	if runtime.GOOS == "windows" {
   245  		parser.AddGroup("Service Options", "Service Options", so)
   246  	}
   247  	return parser
   248  }
   249  
   250  // loadConfig initializes and parses the config using a config file and command
   251  // line options.
   252  //
   253  // The configuration proceeds as follows:
   254  //  1. Start with a default config with sane settings
   255  //  2. Pre-parse the command line to check for an alternative config file
   256  //  3. Load configuration file overwriting defaults with any specified options
   257  //  4. Parse CLI options and overwrite/add any specified options
   258  //
   259  // The above results in daemon functioning properly without any config settings
   260  // while still allowing the user to override settings with config files and
   261  // command line options.  Command line options always take precedence.
   262  func loadConfig() (*config, []string, error) {
   263  	// Default config.
   264  	cfg := config{
   265  		HomeDir:          defaultHomeDir,
   266  		ConfigFile:       defaultConfigFile,
   267  		DebugLevel:       defaultLogLevel,
   268  		DataDir:          defaultDataDir,
   269  		LogDir:           defaultLogDir,
   270  		HTTPSKey:         defaultHTTPSKeyFile,
   271  		HTTPSCert:        defaultHTTPSCertFile,
   272  		Version:          version.Version,
   273  		Backend:          defaultBackend,
   274  		ReadTimeout:      defaultReadTimeout,
   275  		WriteTimeout:     defaultWriteTimeout,
   276  		ReqBodySizeLimit: defaultReqBodySizeLimit,
   277  		DBHost:           defaultDBHost,
   278  		TlogHost:         defaultTlogHost,
   279  	}
   280  
   281  	// Service options which are only added on Windows.
   282  	serviceOpts := serviceOptions{}
   283  
   284  	// Pre-parse the command line options to see if an alternative config
   285  	// file or the version flag was specified.  Any errors aside from the
   286  	// help message error can be ignored here since they will be caught by
   287  	// the final parse below.
   288  	preCfg := cfg
   289  	preParser := newConfigParser(&preCfg, &serviceOpts, flags.HelpFlag)
   290  	_, err := preParser.Parse()
   291  	if err != nil {
   292  		var e *flags.Error
   293  		if errors.As(err, &e) && e.Type == flags.ErrHelp {
   294  			fmt.Fprintln(os.Stderr, err)
   295  			os.Exit(0)
   296  		}
   297  	}
   298  
   299  	// Show the version and exit if the version flag was specified.
   300  	appName := filepath.Base(os.Args[0])
   301  	appName = strings.TrimSuffix(appName, filepath.Ext(appName))
   302  	usageMessage := fmt.Sprintf("Use %s -h to show usage", appName)
   303  	if preCfg.ShowVersion {
   304  		fmt.Printf("%s version %s (Go version %s %s/%s)\n", appName,
   305  			cfg.Version, runtime.Version(), runtime.GOOS,
   306  			runtime.GOARCH)
   307  		os.Exit(0)
   308  	}
   309  
   310  	// Perform service command and exit if specified.  Invalid service
   311  	// commands show an appropriate error.  Only runs on Windows since
   312  	// the runServiceCommand function will be nil when not on Windows.
   313  	if serviceOpts.ServiceCommand != "" && runServiceCommand != nil {
   314  		err := runServiceCommand(serviceOpts.ServiceCommand)
   315  		if err != nil {
   316  			fmt.Fprintln(os.Stderr, err)
   317  		}
   318  		os.Exit(0)
   319  	}
   320  
   321  	// Update the home directory for stakepoold if specified. Since the
   322  	// home directory is updated, other variables need to be updated to
   323  	// reflect the new changes.
   324  	if preCfg.HomeDir != "" {
   325  		cfg.HomeDir, _ = filepath.Abs(preCfg.HomeDir)
   326  
   327  		if preCfg.ConfigFile == defaultConfigFile {
   328  			cfg.ConfigFile = filepath.Join(cfg.HomeDir, defaultConfigFilename)
   329  		} else {
   330  			cfg.ConfigFile = preCfg.ConfigFile
   331  		}
   332  		if preCfg.DataDir == defaultDataDir {
   333  			cfg.DataDir = filepath.Join(cfg.HomeDir, defaultDataDirname)
   334  		} else {
   335  			cfg.DataDir = preCfg.DataDir
   336  		}
   337  		if preCfg.HTTPSKey == defaultHTTPSKeyFile {
   338  			cfg.HTTPSKey = filepath.Join(cfg.HomeDir, "https.key")
   339  		} else {
   340  			cfg.HTTPSKey = preCfg.HTTPSKey
   341  		}
   342  		if preCfg.HTTPSCert == defaultHTTPSCertFile {
   343  			cfg.HTTPSCert = filepath.Join(cfg.HomeDir, "https.cert")
   344  		} else {
   345  			cfg.HTTPSCert = preCfg.HTTPSCert
   346  		}
   347  		if preCfg.LogDir == defaultLogDir {
   348  			cfg.LogDir = filepath.Join(cfg.HomeDir, defaultLogDirname)
   349  		} else {
   350  			cfg.LogDir = preCfg.LogDir
   351  		}
   352  	}
   353  
   354  	// Load additional config from file.
   355  	var configFileError error
   356  	parser := newConfigParser(&cfg, &serviceOpts, flags.Default)
   357  	if !(preCfg.SimNet) || cfg.ConfigFile != defaultConfigFile {
   358  		err := flags.NewIniParser(parser).ParseFile(cfg.ConfigFile)
   359  		if err != nil {
   360  			var e *os.PathError
   361  			if !errors.As(err, &e) {
   362  				fmt.Fprintf(os.Stderr, "Error parsing config "+
   363  					"file: %v\n", err)
   364  				fmt.Fprintln(os.Stderr, usageMessage)
   365  				return nil, nil, err
   366  			}
   367  			configFileError = err
   368  		}
   369  	}
   370  
   371  	// Parse command line options again to ensure they take precedence.
   372  	remainingArgs, err := parser.Parse()
   373  	if err != nil {
   374  		var e *flags.Error
   375  		if !errors.As(err, &e) || e.Type != flags.ErrHelp {
   376  			fmt.Fprintln(os.Stderr, usageMessage)
   377  		}
   378  		return nil, nil, err
   379  	}
   380  
   381  	// Create the home directory if it doesn't already exist.
   382  	funcName := "loadConfig"
   383  	err = os.MkdirAll(defaultHomeDir, 0700)
   384  	if err != nil {
   385  		// Show a nicer error message if it's because a symlink is
   386  		// linked to a directory that does not exist (probably because
   387  		// it's not mounted).
   388  		var e *os.PathError
   389  		if errors.As(err, &e) && os.IsExist(err) {
   390  			if link, lerr := os.Readlink(e.Path); lerr == nil {
   391  				str := "is symlink %s -> %s mounted?"
   392  				err = fmt.Errorf(str, e.Path, link)
   393  			}
   394  		}
   395  
   396  		str := "%s: Failed to create home directory: %v"
   397  		err := fmt.Errorf(str, funcName, err)
   398  		fmt.Fprintln(os.Stderr, err)
   399  		return nil, nil, err
   400  	}
   401  
   402  	// Multiple networks can't be selected simultaneously.
   403  	numNets := 0
   404  
   405  	// Count number of network flags passed; assign active network params
   406  	// while we're at it
   407  	port := defaultMainnetPort
   408  	activeNetParams = &mainNetParams
   409  	if cfg.TestNet {
   410  		numNets++
   411  		activeNetParams = &testNet3Params
   412  		port = defaultTestnetPort
   413  	}
   414  	if cfg.SimNet {
   415  		numNets++
   416  		// Also disable dns seeding on the simulation test network.
   417  		activeNetParams = &simNetParams
   418  	}
   419  	if numNets > 1 {
   420  		str := "%s: The testnet and simnet params can't be " +
   421  			"used together -- choose one of the three"
   422  		err := fmt.Errorf(str, funcName)
   423  		fmt.Fprintln(os.Stderr, err)
   424  		fmt.Fprintln(os.Stderr, usageMessage)
   425  		return nil, nil, err
   426  	}
   427  
   428  	// Append the network type to the data directory so it is "namespaced"
   429  	// per network.  In addition to the block database, there are other
   430  	// pieces of data that are saved to disk such as address manager state.
   431  	// All data is specific to a network, so namespacing the data directory
   432  	// means each individual piece of serialized data does not have to
   433  	// worry about changing names per network and such.
   434  	cfg.DataDir = util.CleanAndExpandPath(cfg.DataDir)
   435  	cfg.DataDir = filepath.Join(cfg.DataDir, netName(activeNetParams))
   436  
   437  	// Append the network type to the log directory so it is "namespaced"
   438  	// per network in the same fashion as the data directory.
   439  	cfg.LogDir = util.CleanAndExpandPath(cfg.LogDir)
   440  	cfg.LogDir = filepath.Join(cfg.LogDir, netName(activeNetParams))
   441  
   442  	cfg.HTTPSKey = util.CleanAndExpandPath(cfg.HTTPSKey)
   443  	cfg.HTTPSCert = util.CleanAndExpandPath(cfg.HTTPSCert)
   444  
   445  	// Special show command to list supported subsystems and exit.
   446  	if cfg.DebugLevel == "show" {
   447  		fmt.Println("Supported subsystems", supportedSubsystems())
   448  		os.Exit(0)
   449  	}
   450  
   451  	// Initialize log rotation.  After log rotation has been initialized,
   452  	// the logger variables may be used.
   453  	initLogRotator(filepath.Join(cfg.LogDir, defaultLogFilename))
   454  
   455  	// Parse, validate, and set debug log level(s).
   456  	if err := parseAndSetDebugLevels(cfg.DebugLevel); err != nil {
   457  		err := fmt.Errorf("%s: %v", funcName, err.Error())
   458  		fmt.Fprintln(os.Stderr, err)
   459  		fmt.Fprintln(os.Stderr, usageMessage)
   460  		return nil, nil, err
   461  	}
   462  
   463  	// Validate profile port number
   464  	if cfg.Profile != "" {
   465  		profilePort, err := strconv.Atoi(cfg.Profile)
   466  		if err != nil || profilePort < 1024 || profilePort > 65535 {
   467  			str := "%s: The profile port must be between 1024 and 65535"
   468  			err := fmt.Errorf(str, funcName)
   469  			fmt.Fprintln(os.Stderr, err)
   470  			fmt.Fprintln(os.Stderr, usageMessage)
   471  			return nil, nil, err
   472  		}
   473  	}
   474  
   475  	// Add the default listener if none were specified. The default
   476  	// listener is all addresses on the listen port for the network
   477  	// we are to connect to.
   478  	if len(cfg.Listeners) == 0 {
   479  		cfg.Listeners = []string{
   480  			net.JoinHostPort("", port),
   481  		}
   482  	}
   483  
   484  	// Add default port to all listener addresses if needed and remove
   485  	// duplicate addresses.
   486  	cfg.Listeners = normalizeAddresses(cfg.Listeners, port)
   487  
   488  	if len(cfg.DcrdataHost) == 0 {
   489  		if cfg.TestNet {
   490  			cfg.DcrdataHost = defaultTestnetDcrdata
   491  		} else {
   492  			cfg.DcrdataHost = defaultMainnetDcrdata
   493  		}
   494  	}
   495  	cfg.DcrdataHost = "https://" + cfg.DcrdataHost
   496  
   497  	if cfg.TestNet {
   498  		var timeHost string
   499  		if len(cfg.DcrtimeHost) == 0 {
   500  			timeHost = v1.DefaultTestnetTimeHost
   501  		} else {
   502  			timeHost = cfg.DcrtimeHost
   503  		}
   504  		cfg.DcrtimeHost = util.NormalizeAddress(timeHost,
   505  			v1.DefaultTestnetTimePort)
   506  	} else {
   507  		var timeHost string
   508  		if len(cfg.DcrtimeHost) == 0 {
   509  			timeHost = v1.DefaultMainnetTimeHost
   510  		} else {
   511  			timeHost = cfg.DcrtimeHost
   512  		}
   513  		cfg.DcrtimeHost = util.NormalizeAddress(timeHost,
   514  			v1.DefaultMainnetTimePort)
   515  	}
   516  	cfg.DcrtimeHost = "https://" + cfg.DcrtimeHost
   517  
   518  	if len(cfg.DcrtimeCert) != 0 && !util.FileExists(cfg.DcrtimeCert) {
   519  		cfg.DcrtimeCert = util.CleanAndExpandPath(cfg.DcrtimeCert)
   520  		path := filepath.Join(cfg.HomeDir, cfg.DcrtimeCert)
   521  		if !util.FileExists(path) {
   522  			str := "%s: dcrtimecert " + cfg.DcrtimeCert + " and " +
   523  				path + " don't exist"
   524  			err := fmt.Errorf(str, funcName)
   525  			fmt.Fprintln(os.Stderr, err)
   526  			return nil, nil, err
   527  		}
   528  
   529  		cfg.DcrtimeCert = path
   530  	}
   531  
   532  	if cfg.Identity == "" {
   533  		cfg.Identity = defaultIdentityFile
   534  	}
   535  	cfg.Identity = util.CleanAndExpandPath(cfg.Identity)
   536  
   537  	// Set random username and password when not specified
   538  	if cfg.RPCUser == "" {
   539  		name, err := util.Random(32)
   540  		if err != nil {
   541  			return nil, nil, err
   542  		}
   543  		cfg.RPCUser = base64.StdEncoding.EncodeToString(name)
   544  		log.Warnf("RPC user name not set, using random value")
   545  	}
   546  	if cfg.RPCPass == "" {
   547  		pass, err := util.Random(32)
   548  		if err != nil {
   549  			return nil, nil, err
   550  		}
   551  		cfg.RPCPass = base64.StdEncoding.EncodeToString(pass)
   552  		log.Warnf("RPC password not set, using random value")
   553  	}
   554  
   555  	// Verify backend specific settings
   556  	switch cfg.Backend {
   557  	case backendGit:
   558  		// Nothing to do
   559  	case backendTstore:
   560  		err = verifyTstoreSettings(&cfg)
   561  		if err != nil {
   562  			return nil, nil, err
   563  		}
   564  	default:
   565  		return nil, nil, fmt.Errorf("invalid backend type '%v'", cfg.Backend)
   566  	}
   567  
   568  	// Warn about missing config file only after all other configuration is
   569  	// done.  This prevents the warning on help messages and invalid
   570  	// options.  Note this should go directly before the return.
   571  	if configFileError != nil {
   572  		log.Warnf("%v", configFileError)
   573  	}
   574  
   575  	return &cfg, remainingArgs, nil
   576  }
   577  
   578  // verifyTstoreSettings verifies the config settings that are specific to the
   579  // tstore backend.
   580  func verifyTstoreSettings(cfg *config) error {
   581  	// Parse the database password. It is provided in an env variable.
   582  	cfg.DBPass = os.Getenv(envDBPass)
   583  	if cfg.DBPass == "" {
   584  		return fmt.Errorf("dbpass not found; you must provide the " +
   585  			"database password for the politeiad user in the env " +
   586  			"variable DBPASS")
   587  	}
   588  
   589  	// Verify tlog options
   590  	_, err := url.Parse(cfg.TlogHost)
   591  	if err != nil {
   592  		return fmt.Errorf("invalid tlog host '%v': %v", cfg.TlogHost, err)
   593  	}
   594  
   595  	return nil
   596  }