From 44bc5633961c284ed6c6999a71bf3216c5cbb738 Mon Sep 17 00:00:00 2001 From: Wilmer Paulino Date: Sat, 24 Feb 2018 15:59:22 -0500 Subject: [PATCH 1/4] config+cmd/lncli: add flag to specify the base dir for lnd In this commit, we introduce a new flag `--lnddir` that allows us to set a different base directory, other than the OS specific application data directory, for all related lnd files and directories. --- cmd/lncli/main.go | 34 ++++++++++++++++++++++++++++++---- config.go | 43 +++++++++++++++++++++++++++++-------------- 2 files changed, 59 insertions(+), 18 deletions(-) diff --git a/cmd/lncli/main.go b/cmd/lncli/main.go index 9a2a70e39..6dbff198d 100644 --- a/cmd/lncli/main.go +++ b/cmd/lncli/main.go @@ -28,9 +28,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 +59,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 +157,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 +236,7 @@ func main() { func cleanAndExpandPath(path string) string { // Expand initial ~ to OS specific home directory. if strings.HasPrefix(path, "~") { - homeDir := filepath.Dir(lndHomeDir) + homeDir := filepath.Dir(defaultLndDir) path = strings.Replace(path, "~", homeDir, 1) } diff --git a/config.go b/config.go index 4403ef620..6bdf4ad7b 100644 --- a/config.go +++ b/config.go @@ -66,15 +66,14 @@ 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") @@ -146,6 +145,7 @@ 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"` @@ -208,6 +208,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, @@ -268,9 +269,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 +295,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 +304,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 } @@ -589,7 +604,7 @@ func loadConfig() (*config, error) { func cleanAndExpandPath(path string) string { // Expand initial ~ to OS specific home directory. if strings.HasPrefix(path, "~") { - homeDir := filepath.Dir(lndHomeDir) + homeDir := filepath.Dir(defaultLndDir) path = strings.Replace(path, "~", homeDir, 1) } From 36e06cdd35f2d3e6d695da76dd8c091356651764 Mon Sep 17 00:00:00 2001 From: Wilmer Paulino Date: Sat, 24 Feb 2018 17:18:59 -0500 Subject: [PATCH 2/4] config: add flag to specify the dir for the supported backend nodes In this commit, we introduce a new flag to set a custom path for a backend node's data directory. --- config.go | 64 +++++++++++++++++++++++++++---------------------------- 1 file changed, 31 insertions(+), 33 deletions(-) diff --git a/config.go b/config.go index 6bdf4ad7b..07dfa8ac7 100644 --- a/config.go +++ b/config.go @@ -75,13 +75,13 @@ var ( 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 { @@ -111,6 +111,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"` @@ -119,6 +120,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"` @@ -225,10 +227,12 @@ func loadConfig() (*config, error) { Node: "btcd", }, BtcdMode: &btcdConfig{ + Dir: defaultBtcdDir, RPCHost: defaultRPCHost, RPCCert: defaultBtcdRPCCertFile, }, BitcoindMode: &bitcoindConfig{ + Dir: defaultBitcoindDir, RPCHost: defaultRPCHost, }, Litecoin: &chainConfig{ @@ -239,6 +243,7 @@ func loadConfig() (*config, error) { Node: "btcd", }, LtcdMode: &btcdConfig{ + Dir: defaultLtcdDir, RPCHost: defaultRPCHost, RPCCert: defaultLtcdRPCCertFile, }, @@ -708,9 +713,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 @@ -724,6 +731,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. @@ -737,6 +755,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 @@ -748,33 +770,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) From 862729809e559510af69a10ca2a5a5cb76be9ad1 Mon Sep 17 00:00:00 2001 From: Wilmer Paulino Date: Sun, 25 Feb 2018 22:41:44 -0500 Subject: [PATCH 3/4] config+cmd/lncli: ensure tilde in path is expanded to home dir Since we're now able to create a base LND directory that no longer lives under the OS specific application data directory, we modify cleanAndExpandPath to replace the `~` in a path to the current user's home directory, rather than the user's application data directory. --- cmd/lncli/main.go | 11 ++++++++++- config.go | 11 ++++++++++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/cmd/lncli/main.go b/cmd/lncli/main.go index 6dbff198d..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" @@ -236,7 +237,15 @@ func main() { func cleanAndExpandPath(path string) string { // Expand initial ~ to OS specific home directory. if strings.HasPrefix(path, "~") { - homeDir := filepath.Dir(defaultLndDir) + 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 07dfa8ac7..5030b5525 100644 --- a/config.go +++ b/config.go @@ -9,6 +9,7 @@ import ( "io/ioutil" "net" "os" + "os/user" "path" "path/filepath" "regexp" @@ -609,7 +610,15 @@ func loadConfig() (*config, error) { func cleanAndExpandPath(path string) string { // Expand initial ~ to OS specific home directory. if strings.HasPrefix(path, "~") { - homeDir := filepath.Dir(defaultLndDir) + var homeDir string + + user, err := user.Current() + if err == nil { + homeDir = user.HomeDir + } else { + homeDir = os.Getenv("HOME") + } + path = strings.Replace(path, "~", homeDir, 1) } From 9bf1b5399b29520d343804aba50b3304d868160c Mon Sep 17 00:00:00 2001 From: Wilmer Paulino Date: Sun, 25 Feb 2018 23:10:44 -0500 Subject: [PATCH 4/4] config: ensure all paths are cleaned and expanded after parsing options In this commit, we ensure all paths to directories and files related to LND are cleand and expanded before attempting to use them. For example, in POSIX systems: - ~/lnd.conf would be expanded to /home/user/lnd.conf - $HOME/lnd.conf would be expanded to /home/user/lnd.conf --- config.go | 33 +++++++++++++++------------------ 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/config.go b/config.go index 5030b5525..594f14fc0 100644 --- a/config.go +++ b/config.go @@ -151,8 +151,8 @@ type config struct { 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"` @@ -321,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 @@ -510,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))