1fa143fa0e
For users don't want to set/manage a passphrase a default passphrase "passphrase" will be used during wallet creation. At startup, the wallet tries to unlock itself using the default passphrase, or a user provided one (using -p). Users that prefer a passphrase can override the default one at wallet creation time using -p, or use the walletpassphrase rpc when the wallet is running. This will prevent the wallet from auto-unlock, and preserve the lock-by-default behavior.
555 lines
19 KiB
Go
555 lines
19 KiB
Go
// Copyright (c) 2013-2017 The btcsuite developers
|
|
// Use of this source code is governed by an ISC
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"net"
|
|
"os"
|
|
"os/user"
|
|
"path/filepath"
|
|
"runtime"
|
|
"sort"
|
|
"strings"
|
|
"time"
|
|
|
|
flags "github.com/jessevdk/go-flags"
|
|
"github.com/lbryio/lbcd/version"
|
|
btcutil "github.com/lbryio/lbcutil"
|
|
"github.com/lbryio/lbcwallet/internal/cfgutil"
|
|
"github.com/lbryio/lbcwallet/netparams"
|
|
"github.com/lbryio/lbcwallet/wallet"
|
|
)
|
|
|
|
const (
|
|
defaultCAFilename = "lbcd.cert"
|
|
defaultConfigFilename = "lbcwallet.conf"
|
|
defaultLogLevel = "info"
|
|
defaultLogDirname = "logs"
|
|
defaultLogFilename = "lbcwallet.log"
|
|
defaultRPCMaxClients = 10
|
|
defaultRPCMaxWebsockets = 25
|
|
defaultPassphrase = "password"
|
|
)
|
|
|
|
var (
|
|
lbcdDefaultCAFile = filepath.Join(btcutil.AppDataDir("lbcd", false), "rpc.cert")
|
|
defaultAppDataDir = btcutil.AppDataDir("lbcwallet", false)
|
|
defaultConfigFile = filepath.Join(defaultAppDataDir, defaultConfigFilename)
|
|
defaultRPCKeyFile = filepath.Join(defaultAppDataDir, "rpc.key")
|
|
defaultRPCCertFile = filepath.Join(defaultAppDataDir, "rpc.cert")
|
|
defaultLogDir = filepath.Join(defaultAppDataDir, defaultLogDirname)
|
|
)
|
|
|
|
type config struct {
|
|
// General application behavior
|
|
ConfigFile *cfgutil.ExplicitString `short:"C" long:"configfile" description:"Path to configuration file"`
|
|
ShowVersion bool `short:"V" long:"version" description:"Display version information and exit"`
|
|
Create bool `long:"create" description:"Create the wallet if it does not exist"`
|
|
CreateTemp bool `long:"createtemp" description:"Create a temporary simulation wallet (pass=password) in the data directory indicated; must call with --datadir"`
|
|
AppDataDir *cfgutil.ExplicitString `short:"A" long:"appdata" description:"Application data directory for wallet config, databases and logs"`
|
|
TestNet3 bool `long:"testnet" description:"Use the test Bitcoin network (version 3) (default client port: 19244, server port: 19245)"`
|
|
Regtest bool `long:"regtest" description:"Use the regression test network (default client port: 29244, server port: 29245)"`
|
|
DebugLevel string `short:"d" long:"debuglevel" description:"Logging level {trace, debug, info, warn, error, critical}"`
|
|
LogDir string `long:"logdir" description:"Directory to log output."`
|
|
Profile string `long:"profile" description:"Enable HTTP profiling on given port -- NOTE port must be between 1024 and 65536"`
|
|
DBTimeout time.Duration `long:"dbtimeout" description:"The timeout value to use when opening the wallet database."`
|
|
|
|
// Passphrase options
|
|
Passphrase string `short:"p" long:"passphrase" default-mask:"-" description:"The wallet passphrase (default: \"passphrase\")"`
|
|
|
|
// RPC client options
|
|
RPCConnect string `short:"c" long:"rpcconnect" description:"Hostname/IP and port of lbcd RPC server to connect to (default localhost:9245, testnet: localhost:19245, regtest: localhost:29245)"`
|
|
CAFile *cfgutil.ExplicitString `long:"cafile" description:"File containing root certificates to authenticate a TLS connections with lbcd"`
|
|
DisableClientTLS bool `long:"noclienttls" description:"Disable TLS for the RPC client"`
|
|
SkipVerify bool `long:"skipverify" description:"Skip verifying TLS for the RPC client"`
|
|
Proxy string `long:"proxy" description:"Connect via SOCKS5 proxy (eg. 127.0.0.1:9050)"`
|
|
ProxyUser string `long:"proxyuser" description:"Username for proxy server"`
|
|
ProxyPass string `long:"proxypass" default-mask:"-" description:"Password for proxy server"`
|
|
|
|
// RPC server options
|
|
RPCCert *cfgutil.ExplicitString `long:"rpccert" description:"File containing the certificate file"`
|
|
RPCKey *cfgutil.ExplicitString `long:"rpckey" description:"File containing the certificate key"`
|
|
OneTimeTLSKey bool `long:"onetimetlskey" description:"Generate a new TLS certpair at startup, but only write the certificate to disk"`
|
|
DisableServerTLS bool `long:"noservertls" description:"Disable TLS for the RPC server"`
|
|
LegacyRPCListeners []string `long:"rpclisten" description:"Listen for legacy RPC connections on this interface/port (default port: 9244, testnet: 19244, regtest: 29244)"`
|
|
LegacyRPCMaxClients int64 `long:"rpcmaxclients" description:"Max number of legacy RPC clients for standard connections"`
|
|
LegacyRPCMaxWebsockets int64 `long:"rpcmaxwebsockets" description:"Max number of RPC websocket connections"`
|
|
RPCUser string `short:"u" long:"rpcuser" description:"Username for RPC and lbcd authentication"`
|
|
RPCPass string `short:"P" long:"rpcpass" default-mask:"-" description:"Password for RPC and lbcd authentication"`
|
|
|
|
// Deprecated options
|
|
DataDir *cfgutil.ExplicitString `short:"b" long:"datadir" default-mask:"-" description:"DEPRECATED -- use appdata instead"`
|
|
}
|
|
|
|
// cleanAndExpandPath expands environement variables and leading ~ in the
|
|
// passed path, cleans the result, and returns it.
|
|
func cleanAndExpandPath(path string) string {
|
|
// NOTE: The os.ExpandEnv doesn't work with Windows cmd.exe-style
|
|
// %VARIABLE%, but they variables can still be expanded via POSIX-style
|
|
// $VARIABLE.
|
|
path = os.ExpandEnv(path)
|
|
|
|
if !strings.HasPrefix(path, "~") {
|
|
return filepath.Clean(path)
|
|
}
|
|
|
|
// Expand initial ~ to the current user's home directory, or ~otheruser
|
|
// to otheruser's home directory. On Windows, both forward and backward
|
|
// slashes can be used.
|
|
path = path[1:]
|
|
|
|
var pathSeparators string
|
|
if runtime.GOOS == "windows" {
|
|
pathSeparators = string(os.PathSeparator) + "/"
|
|
} else {
|
|
pathSeparators = string(os.PathSeparator)
|
|
}
|
|
|
|
userName := ""
|
|
if i := strings.IndexAny(path, pathSeparators); i != -1 {
|
|
userName = path[:i]
|
|
path = path[i:]
|
|
}
|
|
|
|
homeDir := ""
|
|
var u *user.User
|
|
var err error
|
|
if userName == "" {
|
|
u, err = user.Current()
|
|
} else {
|
|
u, err = user.Lookup(userName)
|
|
}
|
|
if err == nil {
|
|
homeDir = u.HomeDir
|
|
}
|
|
// Fallback to CWD if user lookup fails or user has no home directory.
|
|
if homeDir == "" {
|
|
homeDir = "."
|
|
}
|
|
|
|
return filepath.Join(homeDir, path)
|
|
}
|
|
|
|
// validLogLevel returns whether or not logLevel is a valid debug log level.
|
|
func validLogLevel(logLevel string) bool {
|
|
switch logLevel {
|
|
case "trace":
|
|
fallthrough
|
|
case "debug":
|
|
fallthrough
|
|
case "info":
|
|
fallthrough
|
|
case "warn":
|
|
fallthrough
|
|
case "error":
|
|
fallthrough
|
|
case "critical":
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
// supportedSubsystems returns a sorted slice of the supported subsystems for
|
|
// logging purposes.
|
|
func supportedSubsystems() []string {
|
|
// Convert the subsystemLoggers map keys to a slice.
|
|
subsystems := make([]string, 0, len(subsystemLoggers))
|
|
for subsysID := range subsystemLoggers {
|
|
subsystems = append(subsystems, subsysID)
|
|
}
|
|
|
|
// Sort the subsytems for stable display.
|
|
sort.Strings(subsystems)
|
|
return subsystems
|
|
}
|
|
|
|
// parseAndSetDebugLevels attempts to parse the specified debug level and set
|
|
// the levels accordingly. An appropriate error is returned if anything is
|
|
// invalid.
|
|
func parseAndSetDebugLevels(debugLevel string) error {
|
|
// When the specified string doesn't have any delimters, treat it as
|
|
// the log level for all subsystems.
|
|
if !strings.Contains(debugLevel, ",") && !strings.Contains(debugLevel, "=") {
|
|
// Validate debug log level.
|
|
if !validLogLevel(debugLevel) {
|
|
str := "the specified debug level [%v] is invalid"
|
|
return fmt.Errorf(str, debugLevel)
|
|
}
|
|
|
|
// Change the logging level for all subsystems.
|
|
setLogLevels(debugLevel)
|
|
|
|
return nil
|
|
}
|
|
|
|
// Split the specified string into subsystem/level pairs while detecting
|
|
// issues and update the log levels accordingly.
|
|
for _, logLevelPair := range strings.Split(debugLevel, ",") {
|
|
if !strings.Contains(logLevelPair, "=") {
|
|
str := "the specified debug level contains an invalid " +
|
|
"subsystem/level pair [%v]"
|
|
return fmt.Errorf(str, logLevelPair)
|
|
}
|
|
|
|
// Extract the specified subsystem and log level.
|
|
fields := strings.Split(logLevelPair, "=")
|
|
subsysID, logLevel := fields[0], fields[1]
|
|
|
|
// Validate subsystem.
|
|
if _, exists := subsystemLoggers[subsysID]; !exists {
|
|
str := "the specified subsystem [%v] is invalid -- " +
|
|
"supported subsytems %v"
|
|
return fmt.Errorf(str, subsysID, supportedSubsystems())
|
|
}
|
|
|
|
// Validate log level.
|
|
if !validLogLevel(logLevel) {
|
|
str := "the specified debug level [%v] is invalid"
|
|
return fmt.Errorf(str, logLevel)
|
|
}
|
|
|
|
setLogLevel(subsysID, logLevel)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// loadConfig initializes and parses the config using a config file and command
|
|
// line options.
|
|
//
|
|
// The configuration proceeds as follows:
|
|
// 1. Start with a default config with sane settings
|
|
// 2. Pre-parse the command line to check for an alternative config file
|
|
// 3. Load configuration file overwriting defaults with any specified options
|
|
// 4. Parse CLI options and overwrite/add any specified options
|
|
//
|
|
// The above results in lbcwallet functioning properly without any config
|
|
// settings while still allowing the user to override settings with config files
|
|
// and command line options. Command line options always take precedence.
|
|
func loadConfig() (*config, []string, error) {
|
|
// Default config.
|
|
cfg := config{
|
|
DebugLevel: defaultLogLevel,
|
|
ConfigFile: cfgutil.NewExplicitString(defaultConfigFile),
|
|
AppDataDir: cfgutil.NewExplicitString(defaultAppDataDir),
|
|
LogDir: defaultLogDir,
|
|
CAFile: cfgutil.NewExplicitString(""),
|
|
RPCKey: cfgutil.NewExplicitString(defaultRPCKeyFile),
|
|
RPCCert: cfgutil.NewExplicitString(defaultRPCCertFile),
|
|
LegacyRPCMaxClients: defaultRPCMaxClients,
|
|
LegacyRPCMaxWebsockets: defaultRPCMaxWebsockets,
|
|
DataDir: cfgutil.NewExplicitString(defaultAppDataDir),
|
|
DBTimeout: wallet.DefaultDBTimeout,
|
|
Passphrase: defaultPassphrase,
|
|
}
|
|
|
|
// Pre-parse the command line options to see if an alternative config
|
|
// file or the version flag was specified.
|
|
preCfg := cfg
|
|
preParser := flags.NewParser(&preCfg, flags.Default)
|
|
_, err := preParser.Parse()
|
|
if err != nil {
|
|
if e, ok := err.(*flags.Error); !ok || e.Type != flags.ErrHelp {
|
|
preParser.WriteHelp(os.Stderr)
|
|
}
|
|
return nil, nil, err
|
|
}
|
|
|
|
// Show the version and exit if the version flag was specified.
|
|
funcName := "loadConfig"
|
|
appName := filepath.Base(os.Args[0])
|
|
appName = strings.TrimSuffix(appName, filepath.Ext(appName))
|
|
usageMessage := fmt.Sprintf("Use %s -h to show usage", appName)
|
|
if preCfg.ShowVersion {
|
|
fmt.Println(appName, "version", version.Full())
|
|
os.Exit(0)
|
|
}
|
|
|
|
// Load additional config from file.
|
|
var configFileError error
|
|
parser := flags.NewParser(&cfg, flags.Default)
|
|
configFilePath := preCfg.ConfigFile.Value
|
|
if preCfg.ConfigFile.ExplicitlySet() {
|
|
configFilePath = cleanAndExpandPath(configFilePath)
|
|
} else {
|
|
appDataDir := preCfg.AppDataDir.Value
|
|
if !preCfg.AppDataDir.ExplicitlySet() && preCfg.DataDir.ExplicitlySet() {
|
|
appDataDir = cleanAndExpandPath(preCfg.DataDir.Value)
|
|
}
|
|
if appDataDir != defaultAppDataDir {
|
|
configFilePath = filepath.Join(appDataDir, defaultConfigFilename)
|
|
}
|
|
}
|
|
err = flags.NewIniParser(parser).ParseFile(configFilePath)
|
|
if err != nil {
|
|
if _, ok := err.(*os.PathError); !ok {
|
|
fmt.Fprintln(os.Stderr, err)
|
|
parser.WriteHelp(os.Stderr)
|
|
return nil, nil, err
|
|
}
|
|
configFileError = err
|
|
}
|
|
|
|
// Parse command line options again to ensure they take precedence.
|
|
remainingArgs, err := parser.Parse()
|
|
if err != nil {
|
|
if e, ok := err.(*flags.Error); !ok || e.Type != flags.ErrHelp {
|
|
parser.WriteHelp(os.Stderr)
|
|
}
|
|
return nil, nil, err
|
|
}
|
|
|
|
// Check deprecated aliases. The new options receive priority when both
|
|
// are changed from the default.
|
|
if cfg.DataDir.ExplicitlySet() {
|
|
fmt.Fprintln(os.Stderr, "datadir option has been replaced by "+
|
|
"appdata -- please update your config")
|
|
if !cfg.AppDataDir.ExplicitlySet() {
|
|
cfg.AppDataDir.Value = cfg.DataDir.Value
|
|
}
|
|
}
|
|
|
|
// If an alternate data directory was specified, and paths with defaults
|
|
// relative to the data dir are unchanged, modify each path to be
|
|
// relative to the new data dir.
|
|
if cfg.AppDataDir.ExplicitlySet() {
|
|
cfg.AppDataDir.Value = cleanAndExpandPath(cfg.AppDataDir.Value)
|
|
if !cfg.RPCKey.ExplicitlySet() {
|
|
cfg.RPCKey.Value = filepath.Join(cfg.AppDataDir.Value, "rpc.key")
|
|
}
|
|
if !cfg.RPCCert.ExplicitlySet() {
|
|
cfg.RPCCert.Value = filepath.Join(cfg.AppDataDir.Value, "rpc.cert")
|
|
}
|
|
}
|
|
|
|
// Choose the active network params based on the selected network.
|
|
// Multiple networks can't be selected simultaneously.
|
|
numNets := 0
|
|
if cfg.TestNet3 {
|
|
activeNet = &netparams.TestNet3Params
|
|
numNets++
|
|
}
|
|
if cfg.Regtest {
|
|
activeNet = &netparams.RegTestParams
|
|
numNets++
|
|
}
|
|
if numNets > 1 {
|
|
str := "%s: more than one networks has been specified"
|
|
err := fmt.Errorf(str, "loadConfig")
|
|
fmt.Fprintln(os.Stderr, err)
|
|
parser.WriteHelp(os.Stderr)
|
|
return nil, nil, err
|
|
}
|
|
|
|
// Append the network type to the log directory so it is "namespaced"
|
|
// per network.
|
|
cfg.LogDir = cleanAndExpandPath(cfg.LogDir)
|
|
cfg.LogDir = filepath.Join(cfg.LogDir, activeNet.Params.Name)
|
|
|
|
// Special show command to list supported subsystems and exit.
|
|
if cfg.DebugLevel == "show" {
|
|
fmt.Println("Supported subsystems", supportedSubsystems())
|
|
os.Exit(0)
|
|
}
|
|
|
|
// Initialize log rotation. After log rotation has been initialized, the
|
|
// logger variables may be used.
|
|
initLogRotator(filepath.Join(cfg.LogDir, defaultLogFilename))
|
|
|
|
// Parse, validate, and set debug log level(s).
|
|
if err := parseAndSetDebugLevels(cfg.DebugLevel); err != nil {
|
|
err := fmt.Errorf("%s: %v", "loadConfig", err.Error())
|
|
fmt.Fprintln(os.Stderr, err)
|
|
parser.WriteHelp(os.Stderr)
|
|
return nil, nil, err
|
|
}
|
|
|
|
if cfg.CreateTemp {
|
|
errMsg := "Tried to create a temporary simulation wallet"
|
|
// Exit if you try to use a simulation wallet with a standard
|
|
// data directory.
|
|
if !(cfg.AppDataDir.ExplicitlySet() || cfg.DataDir.ExplicitlySet()) {
|
|
errMsg += ", but failed to specify data directory!"
|
|
fmt.Fprintln(os.Stderr, errMsg)
|
|
os.Exit(0)
|
|
}
|
|
|
|
// Exit if you try to use a simulation wallet on anything other than
|
|
// regtest or testnet3.
|
|
if !(cfg.Regtest || cfg.TestNet3) {
|
|
errMsg += "for network other than regtest, or testnet3"
|
|
fmt.Fprintln(os.Stderr, errMsg)
|
|
os.Exit(0)
|
|
}
|
|
}
|
|
|
|
// Ensure the wallet exists or create it when the create flag is set.
|
|
netDir := networkDir(cfg.AppDataDir.Value, activeNet.Params)
|
|
dbPath := filepath.Join(netDir, wallet.WalletDBName)
|
|
|
|
if cfg.CreateTemp && cfg.Create {
|
|
err := fmt.Errorf("the flags --create and --createtemp can not " +
|
|
"be specified together. Use --help for more information")
|
|
fmt.Fprintln(os.Stderr, err)
|
|
return nil, nil, err
|
|
}
|
|
|
|
dbFileExists, err := cfgutil.FileExists(dbPath)
|
|
if err != nil {
|
|
fmt.Fprintln(os.Stderr, err)
|
|
return nil, nil, err
|
|
}
|
|
|
|
if cfg.CreateTemp { // nolint:gocritic
|
|
tempWalletExists := false
|
|
|
|
if dbFileExists {
|
|
str := fmt.Sprintf("The wallet already exists. Loading this " +
|
|
"wallet instead.")
|
|
fmt.Fprintln(os.Stdout, str)
|
|
tempWalletExists = true
|
|
}
|
|
|
|
// Ensure the data directory for the network exists.
|
|
if err := checkCreateDir(netDir); err != nil {
|
|
fmt.Fprintln(os.Stderr, err)
|
|
return nil, nil, err
|
|
}
|
|
|
|
if !tempWalletExists {
|
|
// Perform the initial wallet creation wizard.
|
|
if err := createSimulationWallet(&cfg); err != nil {
|
|
fmt.Fprintln(os.Stderr, "Unable to create wallet:", err)
|
|
return nil, nil, err
|
|
}
|
|
}
|
|
} else if cfg.Create {
|
|
// Error if the create flag is set and the wallet already
|
|
// exists.
|
|
if dbFileExists {
|
|
err := fmt.Errorf("the wallet database file `%v` "+
|
|
"already exists", dbPath)
|
|
fmt.Fprintln(os.Stderr, err)
|
|
return nil, nil, err
|
|
}
|
|
|
|
// Ensure the data directory for the network exists.
|
|
if err := checkCreateDir(netDir); err != nil {
|
|
fmt.Fprintln(os.Stderr, err)
|
|
return nil, nil, err
|
|
}
|
|
|
|
// Perform the initial wallet creation wizard.
|
|
if err := createWallet(&cfg); err != nil {
|
|
fmt.Fprintln(os.Stderr, "Unable to create wallet:", err)
|
|
return nil, nil, err
|
|
}
|
|
|
|
// Created successfully, so exit now with success.
|
|
os.Exit(0)
|
|
}
|
|
|
|
localhostListeners := map[string]struct{}{
|
|
"localhost": {},
|
|
"127.0.0.1": {},
|
|
"::1": {},
|
|
}
|
|
|
|
if cfg.RPCConnect == "" {
|
|
cfg.RPCConnect = net.JoinHostPort("localhost", activeNet.RPCClientPort)
|
|
}
|
|
|
|
// Add default port to connect flag if missing.
|
|
cfg.RPCConnect, err = cfgutil.NormalizeAddress(cfg.RPCConnect,
|
|
activeNet.RPCClientPort)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr,
|
|
"Invalid rpcconnect network address: %v\n", err)
|
|
return nil, nil, err
|
|
}
|
|
|
|
RPCHost, _, err := net.SplitHostPort(cfg.RPCConnect)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
if !cfg.DisableClientTLS {
|
|
// If CAFile is unset, choose either the copy or local lbcd cert.
|
|
if !cfg.CAFile.ExplicitlySet() {
|
|
cfg.CAFile.Value = filepath.Join(cfg.AppDataDir.Value, defaultCAFilename)
|
|
|
|
// If the CA copy does not exist, check if we're connecting to
|
|
// a local lbcd and switch to its RPC cert if it exists.
|
|
certExists, err := cfgutil.FileExists(cfg.CAFile.Value)
|
|
if err != nil {
|
|
fmt.Fprintln(os.Stderr, err)
|
|
return nil, nil, err
|
|
}
|
|
if !certExists {
|
|
if _, ok := localhostListeners[RPCHost]; ok {
|
|
lbcdCertExists, err := cfgutil.FileExists(
|
|
lbcdDefaultCAFile)
|
|
if err != nil {
|
|
fmt.Fprintln(os.Stderr, err)
|
|
return nil, nil, err
|
|
}
|
|
if lbcdCertExists {
|
|
cfg.CAFile.Value = lbcdDefaultCAFile
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if len(cfg.LegacyRPCListeners) == 0 {
|
|
addrs, err := net.LookupHost("localhost")
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
cfg.LegacyRPCListeners = make([]string, 0, len(addrs))
|
|
for _, addr := range addrs {
|
|
addr = net.JoinHostPort(addr, activeNet.RPCServerPort)
|
|
cfg.LegacyRPCListeners = append(cfg.LegacyRPCListeners, addr)
|
|
}
|
|
}
|
|
|
|
// Add default port to all rpc listener addresses if needed and remove
|
|
// duplicate addresses.
|
|
cfg.LegacyRPCListeners, err = cfgutil.NormalizeAddresses(
|
|
cfg.LegacyRPCListeners, activeNet.RPCServerPort)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr,
|
|
"Invalid network address in legacy RPC listeners: %v\n", err)
|
|
return nil, nil, err
|
|
}
|
|
|
|
if cfg.DisableServerTLS {
|
|
for _, addr := range cfg.LegacyRPCListeners {
|
|
_, _, err := net.SplitHostPort(addr)
|
|
if err != nil {
|
|
str := "%s: RPC listen interface '%s' is " +
|
|
"invalid: %v"
|
|
err := fmt.Errorf(str, funcName, addr, err)
|
|
fmt.Fprintln(os.Stderr, err)
|
|
fmt.Fprintln(os.Stderr, usageMessage)
|
|
return nil, nil, err
|
|
}
|
|
}
|
|
}
|
|
|
|
// Expand environment variable and leading ~ for filepaths.
|
|
cfg.CAFile.Value = cleanAndExpandPath(cfg.CAFile.Value)
|
|
cfg.RPCCert.Value = cleanAndExpandPath(cfg.RPCCert.Value)
|
|
cfg.RPCKey.Value = cleanAndExpandPath(cfg.RPCKey.Value)
|
|
|
|
// Warn about missing config file after the final command line parse
|
|
// succeeds. This prevents the warning on help messages and invalid
|
|
// options.
|
|
if configFileError != nil {
|
|
log.Warnf("%v", configFileError)
|
|
}
|
|
|
|
return &cfg, remainingArgs, nil
|
|
}
|