diff --git a/cmd/lncli/main.go b/cmd/lncli/main.go index 9a2a70e39..85d0f7add 100644 --- a/cmd/lncli/main.go +++ b/cmd/lncli/main.go @@ -8,6 +8,7 @@ import ( "fmt" "io/ioutil" "os" + "os/user" "path/filepath" "strings" @@ -28,9 +29,9 @@ const ( ) var ( - lndHomeDir = btcutil.AppDataDir("lnd", false) - defaultTLSCertPath = filepath.Join(lndHomeDir, defaultTLSCertFilename) - defaultMacaroonPath = filepath.Join(lndHomeDir, defaultMacaroonFilename) + defaultLndDir = btcutil.AppDataDir("lnd", false) + defaultTLSCertPath = filepath.Join(defaultLndDir, defaultTLSCertFilename) + defaultMacaroonPath = filepath.Join(defaultLndDir, defaultMacaroonFilename) ) func fatal(err error) { @@ -59,6 +60,27 @@ func getClient(ctx *cli.Context) (lnrpc.LightningClient, func()) { } func getClientConn(ctx *cli.Context, skipMacaroons bool) *grpc.ClientConn { + lndDir := cleanAndExpandPath(ctx.GlobalString("lnddir")) + if lndDir != defaultLndDir { + // If a custom lnd directory was set, we'll also check if custom + // paths for the TLS cert and macaroon file were set as well. If + // not, we'll override their paths so they can be found within + // the custom lnd directory set. This allows us to set a custom + // lnd directory, along with custom paths to the TLS cert and + // macaroon file. + tlsCertPath := cleanAndExpandPath(ctx.GlobalString("tlscertpath")) + if tlsCertPath == defaultTLSCertPath { + ctx.GlobalSet("tlscertpath", + filepath.Join(lndDir, defaultTLSCertFilename)) + } + + macPath := cleanAndExpandPath(ctx.GlobalString("macaroonpath")) + if macPath == defaultMacaroonPath { + ctx.GlobalSet("no-macaroons", + filepath.Join(lndDir, defaultMacaroonFilename)) + } + } + // Load the specified TLS certificate and build transport credentials // with it. tlsCertPath := cleanAndExpandPath(ctx.GlobalString("tlscertpath")) @@ -136,6 +158,11 @@ func main() { Value: "localhost:10009", Usage: "host:port of ln daemon", }, + cli.StringFlag{ + Name: "lnddir", + Value: defaultLndDir, + Usage: "path to lnd's base directory", + }, cli.StringFlag{ Name: "tlscertpath", Value: defaultTLSCertPath, @@ -210,7 +237,15 @@ func main() { func cleanAndExpandPath(path string) string { // Expand initial ~ to OS specific home directory. if strings.HasPrefix(path, "~") { - homeDir := filepath.Dir(lndHomeDir) + var homeDir string + + user, err := user.Current() + if err == nil { + homeDir = user.HomeDir + } else { + homeDir = os.Getenv("HOME") + } + path = strings.Replace(path, "~", homeDir, 1) } diff --git a/config.go b/config.go index 4403ef620..594f14fc0 100644 --- a/config.go +++ b/config.go @@ -9,6 +9,7 @@ import ( "io/ioutil" "net" "os" + "os/user" "path" "path/filepath" "regexp" @@ -66,23 +67,22 @@ const ( ) var ( - // TODO(roasbeef): base off of datadir instead? - lndHomeDir = btcutil.AppDataDir("lnd", false) - defaultConfigFile = filepath.Join(lndHomeDir, defaultConfigFilename) - defaultDataDir = filepath.Join(lndHomeDir, defaultDataDirname) - defaultTLSCertPath = filepath.Join(lndHomeDir, defaultTLSCertFilename) - defaultTLSKeyPath = filepath.Join(lndHomeDir, defaultTLSKeyFilename) - defaultAdminMacPath = filepath.Join(lndHomeDir, defaultAdminMacFilename) - defaultReadMacPath = filepath.Join(lndHomeDir, defaultReadMacFilename) - defaultLogDir = filepath.Join(lndHomeDir, defaultLogDirname) + defaultLndDir = btcutil.AppDataDir("lnd", false) + defaultConfigFile = filepath.Join(defaultLndDir, defaultConfigFilename) + defaultDataDir = filepath.Join(defaultLndDir, defaultDataDirname) + defaultTLSCertPath = filepath.Join(defaultLndDir, defaultTLSCertFilename) + defaultTLSKeyPath = filepath.Join(defaultLndDir, defaultTLSKeyFilename) + defaultAdminMacPath = filepath.Join(defaultLndDir, defaultAdminMacFilename) + defaultReadMacPath = filepath.Join(defaultLndDir, defaultReadMacFilename) + defaultLogDir = filepath.Join(defaultLndDir, defaultLogDirname) - btcdHomeDir = btcutil.AppDataDir("btcd", false) - defaultBtcdRPCCertFile = filepath.Join(btcdHomeDir, "rpc.cert") + defaultBtcdDir = btcutil.AppDataDir("btcd", false) + defaultBtcdRPCCertFile = filepath.Join(defaultBtcdDir, "rpc.cert") - ltcdHomeDir = btcutil.AppDataDir("ltcd", false) - defaultLtcdRPCCertFile = filepath.Join(ltcdHomeDir, "rpc.cert") + defaultLtcdDir = btcutil.AppDataDir("ltcd", false) + defaultLtcdRPCCertFile = filepath.Join(defaultLtcdDir, "rpc.cert") - bitcoindHomeDir = btcutil.AppDataDir("bitcoin", false) + defaultBitcoindDir = btcutil.AppDataDir("bitcoin", false) ) type chainConfig struct { @@ -112,6 +112,7 @@ type neutrinoConfig struct { } type btcdConfig struct { + Dir string `long:"dir" description:"The base directory that contains the node's data, logs, configuration file, etc."` RPCHost string `long:"rpchost" description:"The daemon's rpc listening address. If a port is omitted, then the default port for the selected chain parameters will be used."` RPCUser string `long:"rpcuser" description:"Username for RPC connections"` RPCPass string `long:"rpcpass" default-mask:"-" description:"Password for RPC connections"` @@ -120,6 +121,7 @@ type btcdConfig struct { } type bitcoindConfig struct { + Dir string `long:"dir" description:"The base directory that contains the node's data, logs, configuration file, etc."` RPCHost string `long:"rpchost" description:"The daemon's rpc listening address. If a port is omitted, then the default port for the selected chain parameters will be used."` RPCUser string `long:"rpcuser" description:"Username for RPC connections"` RPCPass string `long:"rpcpass" default-mask:"-" description:"Password for RPC connections"` @@ -146,10 +148,11 @@ type torConfig struct { type config struct { ShowVersion bool `short:"V" long:"version" description:"Display version information and exit"` + LndDir string `long:"lnddir" description:"The base directory that contains lnd's data, logs, configuration file, etc."` ConfigFile string `long:"C" long:"configfile" description:"Path to configuration file"` DataDir string `short:"b" long:"datadir" description:"The directory to store lnd's data within"` - TLSCertPath string `long:"tlscertpath" description:"Path to TLS certificate for lnd's RPC and REST services"` - TLSKeyPath string `long:"tlskeypath" description:"Path to TLS private key for lnd's RPC and REST services"` + TLSCertPath string `long:"tlscertpath" description:"Path to write the TLS certificate for lnd's RPC and REST services"` + TLSKeyPath string `long:"tlskeypath" description:"Path to write the TLS private key for lnd's RPC and REST services"` TLSExtraIP string `long:"tlsextraip" description:"Adds an extra ip to the generated certificate"` NoMacaroons bool `long:"no-macaroons" description:"Disable macaroon authentication"` AdminMacPath string `long:"adminmacaroonpath" description:"Path to write the admin macaroon for lnd's RPC and REST services if it doesn't exist"` @@ -208,6 +211,7 @@ type config struct { // 4) Parse CLI options and overwrite/add any specified options func loadConfig() (*config, error) { defaultCfg := config{ + LndDir: defaultLndDir, ConfigFile: defaultConfigFile, DataDir: defaultDataDir, DebugLevel: defaultLogLevel, @@ -224,10 +228,12 @@ func loadConfig() (*config, error) { Node: "btcd", }, BtcdMode: &btcdConfig{ + Dir: defaultBtcdDir, RPCHost: defaultRPCHost, RPCCert: defaultBtcdRPCCertFile, }, BitcoindMode: &bitcoindConfig{ + Dir: defaultBitcoindDir, RPCHost: defaultRPCHost, }, Litecoin: &chainConfig{ @@ -238,6 +244,7 @@ func loadConfig() (*config, error) { Node: "btcd", }, LtcdMode: &btcdConfig{ + Dir: defaultLtcdDir, RPCHost: defaultRPCHost, RPCCert: defaultLtcdRPCCertFile, }, @@ -268,9 +275,22 @@ func loadConfig() (*config, error) { os.Exit(0) } - // Create the home directory if it doesn't already exist. + // If the provided lnd directory is not the default, we'll modify the + // path to all of the files and directories that will live within it. + lndDir := cleanAndExpandPath(preCfg.LndDir) + if lndDir != defaultLndDir { + defaultCfg.ConfigFile = filepath.Join(lndDir, defaultConfigFilename) + defaultCfg.DataDir = filepath.Join(lndDir, defaultDataDirname) + defaultCfg.TLSCertPath = filepath.Join(lndDir, defaultTLSCertFilename) + defaultCfg.TLSKeyPath = filepath.Join(lndDir, defaultTLSKeyFilename) + defaultCfg.AdminMacPath = filepath.Join(lndDir, defaultAdminMacFilename) + defaultCfg.ReadMacPath = filepath.Join(lndDir, defaultReadMacFilename) + defaultCfg.LogDir = filepath.Join(lndDir, defaultLogDirname) + } + + // Create the lnd directory if it doesn't already exist. funcName := "loadConfig" - if err := os.MkdirAll(lndHomeDir, 0700); err != nil { + if err := os.MkdirAll(lndDir, 0700); err != nil { // Show a nicer error message if it's because a symlink is // linked to a directory that does not exist (probably because // it's not mounted). @@ -281,7 +301,7 @@ func loadConfig() (*config, error) { } } - str := "%s: Failed to create home directory: %v" + str := "%s: Failed to create lnd directory: %v" err := fmt.Errorf(str, funcName, err) fmt.Fprintln(os.Stderr, err) return nil, err @@ -290,7 +310,8 @@ func loadConfig() (*config, error) { // Next, load any additional configuration options from the file. var configFileError error cfg := defaultCfg - if err := flags.IniParse(preCfg.ConfigFile, &cfg); err != nil { + configFile := cleanAndExpandPath(preCfg.ConfigFile) + if err := flags.IniParse(configFile, &cfg); err != nil { configFileError = err } @@ -300,6 +321,19 @@ func loadConfig() (*config, error) { return nil, err } + // As soon as we're done parsing configuration options, ensure all paths + // to directories and files are cleaned and expanded before attempting + // to use them later on. + cfg.DataDir = cleanAndExpandPath(cfg.DataDir) + cfg.TLSCertPath = cleanAndExpandPath(cfg.TLSCertPath) + cfg.TLSKeyPath = cleanAndExpandPath(cfg.TLSKeyPath) + cfg.AdminMacPath = cleanAndExpandPath(cfg.AdminMacPath) + cfg.ReadMacPath = cleanAndExpandPath(cfg.ReadMacPath) + cfg.LogDir = cleanAndExpandPath(cfg.LogDir) + cfg.BtcdMode.Dir = cleanAndExpandPath(cfg.BtcdMode.Dir) + cfg.LtcdMode.Dir = cleanAndExpandPath(cfg.LtcdMode.Dir) + cfg.BitcoindMode.Dir = cleanAndExpandPath(cfg.BitcoindMode.Dir) + // Setup dial and DNS resolution functions depending on the specified // options. The default is to use the standard golang "net" package // functions. When Tor's proxy is specified, the dial function is set to @@ -489,28 +523,12 @@ func loadConfig() (*config, error) { cfg.ReadMacPath = filepath.Join(cfg.DataDir, defaultReadMacFilename) } - // Append the network type to the data directory so it is "namespaced" - // per network. In addition to the block database, there are other - // pieces of data that are saved to disk such as address manager state. - // All data is specific to a network, so namespacing the data directory - // means each individual piece of serialized data does not have to - // worry about changing names per network and such. - // TODO(roasbeef): when we go full multi-chain remove the additional - // namespacing on the target chain. - cfg.DataDir = cleanAndExpandPath(cfg.DataDir) - // Append the network type to the log directory so it is "namespaced" // per network in the same fashion as the data directory. - cfg.LogDir = cleanAndExpandPath(cfg.LogDir) cfg.LogDir = filepath.Join(cfg.LogDir, registeredChains.PrimaryChain().String(), normalizeNetwork(activeNetParams.Name)) - // Ensure that the paths to the TLS key and certificate files are - // expanded and cleaned. - cfg.TLSCertPath = cleanAndExpandPath(cfg.TLSCertPath) - cfg.TLSKeyPath = cleanAndExpandPath(cfg.TLSKeyPath) - // Initialize logging at the default logging level. initLogRotator(filepath.Join(cfg.LogDir, defaultLogFilename)) @@ -589,7 +607,15 @@ func loadConfig() (*config, error) { func cleanAndExpandPath(path string) string { // Expand initial ~ to OS specific home directory. if strings.HasPrefix(path, "~") { - homeDir := filepath.Dir(lndHomeDir) + var homeDir string + + user, err := user.Current() + if err == nil { + homeDir = user.HomeDir + } else { + homeDir = os.Getenv("HOME") + } + path = strings.Replace(path, "~", homeDir, 1) } @@ -693,9 +719,11 @@ func noiseDial(idPriv *btcec.PrivateKey) func(net.Addr) (net.Conn, error) { func parseRPCParams(cConfig *chainConfig, nodeConfig interface{}, net chainCode, funcName string) error { - // If the configuration has already set the RPCUser and RPCPass, and - // if we're either not using bitcoind mode or the ZMQ path is already - // specified, we can return. + + // First, we'll check our node config to make sure the RPC parameters + // were set correctly. We'll also determine the path to the conf file + // depending on the backend node. + var daemonName, confDir, confFile string switch conf := nodeConfig.(type) { case *btcdConfig: // If both RPCUser and RPCPass are set, we assume those @@ -709,6 +737,17 @@ func parseRPCParams(cConfig *chainConfig, nodeConfig interface{}, net chainCode, return fmt.Errorf("please set both or neither of " + "btcd.rpcuser and btcd.rpcpass") } + + switch net { + case bitcoinChain: + daemonName = "btcd" + confDir = conf.Dir + confFile = "btcd" + case litecoinChain: + daemonName = "ltcd" + confDir = conf.Dir + confFile = "ltcd" + } case *bitcoindConfig: // If all of RPCUser, RPCPass, and ZMQPath are set, we assume // those parameters are good to use. @@ -722,6 +761,10 @@ func parseRPCParams(cConfig *chainConfig, nodeConfig interface{}, net chainCode, "bitcoind.rpcuser, bitcoind.rpcpass, and " + "bitcoind.zmqpath") } + + daemonName = "bitcoind" + confDir = conf.Dir + confFile = "bitcoin" } // If we're in simnet mode, then the running btcd instance won't read @@ -733,33 +776,9 @@ func parseRPCParams(cConfig *chainConfig, nodeConfig interface{}, net chainCode, return fmt.Errorf(str, funcName) } - var daemonName, homeDir, confFile string - switch net { - case bitcoinChain: - switch cConfig.Node { - case "btcd": - daemonName = "btcd" - homeDir = btcdHomeDir - confFile = "btcd" - case "bitcoind": - daemonName = "bitcoind" - homeDir = bitcoindHomeDir - confFile = "bitcoin" - } - case litecoinChain: - switch cConfig.Node { - case "btcd": - daemonName = "ltcd" - homeDir = ltcdHomeDir - confFile = "ltcd" - case "bitcoind": - return fmt.Errorf("bitcoind mode doesn't work with Litecoin yet") - } - } - fmt.Println("Attempting automatic RPC configuration to " + daemonName) - confFile = filepath.Join(homeDir, fmt.Sprintf("%v.conf", confFile)) + confFile = filepath.Join(confDir, fmt.Sprintf("%v.conf", confFile)) switch cConfig.Node { case "btcd": nConf := nodeConfig.(*btcdConfig)