2019-08-05 01:44:38 -07:00
|
|
|
{ config, lib, pkgs, ... }:
|
|
|
|
|
|
|
|
with lib;
|
|
|
|
let
|
|
|
|
options.services.lnd = {
|
2021-12-14 10:51:23 -08:00
|
|
|
enable = mkEnableOption "Lightning Network daemon, a Lightning Network implementation in Go";
|
2021-01-14 04:24:03 -08:00
|
|
|
address = mkOption {
|
|
|
|
type = types.str;
|
2020-06-04 02:30:17 -07:00
|
|
|
default = "localhost";
|
2021-01-14 04:24:03 -08:00
|
|
|
description = "Address to listen for peer connections";
|
2020-06-04 02:30:17 -07:00
|
|
|
};
|
2021-01-14 04:24:03 -08:00
|
|
|
port = mkOption {
|
2020-08-04 00:45:02 -07:00
|
|
|
type = types.port;
|
|
|
|
default = 9735;
|
2021-01-14 04:24:03 -08:00
|
|
|
description = "Port to listen for peer connections";
|
2020-08-04 00:45:02 -07:00
|
|
|
};
|
2021-01-14 04:24:03 -08:00
|
|
|
rpcAddress = mkOption {
|
2020-10-29 13:20:33 -07:00
|
|
|
type = types.str;
|
|
|
|
default = "localhost";
|
2021-01-14 04:24:03 -08:00
|
|
|
description = "Address to listen for RPC connections.";
|
2020-06-04 02:30:17 -07:00
|
|
|
};
|
2021-01-14 04:24:03 -08:00
|
|
|
rpcPort = mkOption {
|
|
|
|
type = types.port;
|
|
|
|
default = 10009;
|
|
|
|
description = "Port to listen for gRPC connections.";
|
|
|
|
};
|
|
|
|
restAddress = mkOption {
|
2020-10-29 13:20:33 -07:00
|
|
|
type = types.str;
|
|
|
|
default = "localhost";
|
2021-09-16 03:48:21 -07:00
|
|
|
description = "Address to listen for REST connections.";
|
2020-06-04 02:30:17 -07:00
|
|
|
};
|
2020-06-02 06:20:04 -07:00
|
|
|
restPort = mkOption {
|
|
|
|
type = types.port;
|
|
|
|
default = 8080;
|
2021-01-14 04:24:03 -08:00
|
|
|
description = "Port to listen for REST connections.";
|
2020-06-02 06:20:04 -07:00
|
|
|
};
|
2021-09-16 03:48:21 -07:00
|
|
|
dataDir = mkOption {
|
|
|
|
type = types.path;
|
|
|
|
default = "/var/lib/lnd";
|
|
|
|
description = "The data directory for LND.";
|
|
|
|
};
|
|
|
|
networkDir = mkOption {
|
|
|
|
readOnly = true;
|
|
|
|
default = "${cfg.dataDir}/chain/bitcoin/${bitcoind.network}";
|
|
|
|
description = "The network data directory.";
|
|
|
|
};
|
2020-06-04 02:30:17 -07:00
|
|
|
tor-socks = mkOption {
|
|
|
|
type = types.nullOr types.str;
|
2021-11-28 12:24:49 -08:00
|
|
|
default = if cfg.tor.proxy then config.nix-bitcoin.torClientAddressWithPort else null;
|
2021-02-01 13:53:04 -08:00
|
|
|
description = "Socks proxy for connecting to Tor nodes";
|
2020-06-04 02:30:17 -07:00
|
|
|
};
|
2020-08-26 01:22:23 -07:00
|
|
|
macaroons = mkOption {
|
|
|
|
default = {};
|
|
|
|
type = with types; attrsOf (submodule {
|
|
|
|
options = {
|
|
|
|
user = mkOption {
|
|
|
|
type = types.str;
|
|
|
|
description = "User who owns the macaroon.";
|
|
|
|
};
|
|
|
|
permissions = mkOption {
|
|
|
|
type = types.str;
|
|
|
|
example = ''
|
|
|
|
{"entity":"info","action":"read"},{"entity":"onchain","action":"read"}
|
|
|
|
'';
|
|
|
|
description = "List of granted macaroon permissions.";
|
|
|
|
};
|
|
|
|
};
|
|
|
|
});
|
|
|
|
description = ''
|
|
|
|
Extra macaroon definitions.
|
|
|
|
'';
|
|
|
|
};
|
2022-07-07 07:08:27 -07:00
|
|
|
certificate = {
|
|
|
|
extraIPs = mkOption {
|
|
|
|
type = with types; listOf str;
|
|
|
|
default = [];
|
|
|
|
example = [ "60.100.0.1" ];
|
|
|
|
description = ''
|
|
|
|
Extra `subjectAltName` IPs added to the certificate.
|
|
|
|
This works the same as lnd option `tlsextraip`.
|
|
|
|
'';
|
|
|
|
};
|
|
|
|
extraDomains = mkOption {
|
|
|
|
type = with types; listOf str;
|
|
|
|
default = [];
|
|
|
|
example = [ "example.com" ];
|
|
|
|
description = ''
|
|
|
|
Extra `subjectAltName` domain names added to the certificate.
|
|
|
|
This works the same as lnd option `tlsextradomain`.
|
|
|
|
'';
|
|
|
|
};
|
|
|
|
};
|
2019-08-05 01:44:38 -07:00
|
|
|
extraConfig = mkOption {
|
2020-03-09 01:22:00 -07:00
|
|
|
type = types.lines;
|
|
|
|
default = "";
|
|
|
|
example = ''
|
|
|
|
autopilot.active=1
|
|
|
|
'';
|
2022-07-07 07:08:26 -07:00
|
|
|
description = ''
|
|
|
|
Extra lines appended to `lnd.conf`.
|
|
|
|
See here for all available options:
|
|
|
|
https://github.com/lightningnetwork/lnd/blob/master/sample-lnd.conf
|
|
|
|
'';
|
2020-03-09 01:22:00 -07:00
|
|
|
};
|
|
|
|
package = mkOption {
|
|
|
|
type = types.package;
|
2020-11-09 13:09:09 -08:00
|
|
|
default = config.nix-bitcoin.pkgs.lnd;
|
2021-12-07 19:07:28 -08:00
|
|
|
defaultText = "config.nix-bitcoin.pkgs.lnd";
|
2020-03-09 01:22:00 -07:00
|
|
|
description = "The package providing lnd binaries.";
|
|
|
|
};
|
2019-11-27 05:04:34 -08:00
|
|
|
cli = mkOption {
|
|
|
|
default = pkgs.writeScriptBin "lncli"
|
2021-09-16 03:48:21 -07:00
|
|
|
# Switch user because lnd makes datadir contents readable by user only
|
|
|
|
''
|
|
|
|
${runAsUser} ${cfg.user} ${cfg.package}/bin/lncli \
|
|
|
|
--rpcserver ${cfg.rpcAddress}:${toString cfg.rpcPort} \
|
|
|
|
--tlscertpath '${cfg.certPath}' \
|
|
|
|
--macaroonpath '${networkDir}/admin.macaroon' "$@"
|
|
|
|
'';
|
2021-12-07 19:07:28 -08:00
|
|
|
defaultText = "(See source)";
|
2019-11-27 05:04:34 -08:00
|
|
|
description = "Binary to connect with the lnd instance.";
|
|
|
|
};
|
2021-01-14 04:24:21 -08:00
|
|
|
getPublicAddressCmd = mkOption {
|
|
|
|
type = types.str;
|
|
|
|
default = "";
|
|
|
|
description = ''
|
|
|
|
Bash expression which outputs the public service address to announce to peers.
|
|
|
|
If left empty, no address is announced.
|
|
|
|
'';
|
|
|
|
};
|
2021-02-16 08:50:39 -08:00
|
|
|
user = mkOption {
|
|
|
|
type = types.str;
|
|
|
|
default = "lnd";
|
|
|
|
description = "The user as which to run LND.";
|
|
|
|
};
|
|
|
|
group = mkOption {
|
|
|
|
type = types.str;
|
|
|
|
default = cfg.user;
|
|
|
|
description = "The group as which to run LND.";
|
|
|
|
};
|
2021-09-08 08:01:13 -07:00
|
|
|
certPath = mkOption {
|
|
|
|
readOnly = true;
|
|
|
|
default = "${secretsDir}/lnd-cert";
|
|
|
|
description = "LND TLS certificate path.";
|
|
|
|
};
|
2021-11-28 12:24:49 -08:00
|
|
|
tor = nbLib.tor;
|
2019-08-05 01:44:38 -07:00
|
|
|
};
|
|
|
|
|
2021-09-13 04:40:47 -07:00
|
|
|
cfg = config.services.lnd;
|
|
|
|
nbLib = config.nix-bitcoin.lib;
|
|
|
|
secretsDir = config.nix-bitcoin.secretsDir;
|
|
|
|
runAsUser = config.nix-bitcoin.runAsUserCmd;
|
2022-03-29 12:48:57 -07:00
|
|
|
lndinit = "${config.nix-bitcoin.pkgs.lndinit}/bin/lndinit";
|
2021-09-13 04:40:47 -07:00
|
|
|
|
|
|
|
bitcoind = config.services.bitcoind;
|
2021-09-13 04:40:49 -07:00
|
|
|
|
2021-10-01 02:51:57 -07:00
|
|
|
bitcoindRpcAddress = nbLib.address bitcoind.rpc.address;
|
2021-09-16 03:48:21 -07:00
|
|
|
networkDir = cfg.networkDir;
|
2021-09-13 04:40:47 -07:00
|
|
|
configFile = pkgs.writeText "lnd.conf" ''
|
|
|
|
datadir=${cfg.dataDir}
|
|
|
|
logdir=${cfg.dataDir}/logs
|
|
|
|
tlscertpath=${cfg.certPath}
|
|
|
|
tlskeypath=${secretsDir}/lnd-key
|
|
|
|
|
|
|
|
listen=${toString cfg.address}:${toString cfg.port}
|
|
|
|
rpclisten=${cfg.rpcAddress}:${toString cfg.rpcPort}
|
|
|
|
restlisten=${cfg.restAddress}:${toString cfg.restPort}
|
|
|
|
|
|
|
|
bitcoin.${bitcoind.network}=1
|
|
|
|
bitcoin.active=1
|
|
|
|
bitcoin.node=bitcoind
|
|
|
|
|
2021-11-28 12:24:49 -08:00
|
|
|
${optionalString (cfg.tor.proxy) "tor.active=true"}
|
2021-09-13 04:40:47 -07:00
|
|
|
${optionalString (cfg.tor-socks != null) "tor.socks=${cfg.tor-socks}"}
|
|
|
|
|
|
|
|
bitcoind.rpchost=${bitcoindRpcAddress}:${toString bitcoind.rpc.port}
|
2022-10-25 13:35:31 -07:00
|
|
|
bitcoind.rpcuser=${bitcoind.rpc.users.${rpcUser}.name}
|
2021-09-13 04:40:47 -07:00
|
|
|
bitcoind.zmqpubrawblock=${bitcoind.zmqpubrawblock}
|
|
|
|
bitcoind.zmqpubrawtx=${bitcoind.zmqpubrawtx}
|
|
|
|
|
2022-07-14 14:45:24 -07:00
|
|
|
wallet-unlock-password-file=${secretsDir}/lnd-wallet-password
|
|
|
|
|
2021-09-13 04:40:47 -07:00
|
|
|
${cfg.extraConfig}
|
|
|
|
'';
|
2022-10-25 13:35:31 -07:00
|
|
|
|
|
|
|
isPruned = bitcoind.prune > 0;
|
|
|
|
# When bitcoind pruning is enabled, lnd requires non-public RPC commands `getpeerinfo`, `getnodeaddresses`
|
|
|
|
# to fetch missing blocks from peers (implemented in btcsuite/btcwallet/chain/pruned_block_dispatcher.go)
|
|
|
|
rpcUser = if isPruned then "lnd" else "public";
|
2021-09-13 04:40:47 -07:00
|
|
|
in {
|
|
|
|
|
|
|
|
inherit options;
|
|
|
|
|
2022-10-25 13:35:31 -07:00
|
|
|
config = mkIf cfg.enable (mkMerge [ {
|
2020-06-15 03:34:11 -07:00
|
|
|
assertions = [
|
2021-08-15 02:28:46 -07:00
|
|
|
{ assertion =
|
|
|
|
!(config.services ? clightning)
|
|
|
|
|| !config.services.clightning.enable
|
|
|
|
|| config.services.clightning.port != cfg.port;
|
|
|
|
message = ''
|
|
|
|
LND and clightning can't both bind to lightning port 9735. Either
|
|
|
|
disable LND/clightning or change services.clightning.port or
|
|
|
|
services.lnd.port to a port other than 9735.
|
|
|
|
'';
|
|
|
|
}
|
2020-06-15 03:34:11 -07:00
|
|
|
];
|
|
|
|
|
2021-01-14 04:24:32 -08:00
|
|
|
services.bitcoind = {
|
|
|
|
enable = true;
|
2021-02-01 13:53:22 -08:00
|
|
|
|
2021-01-14 04:24:32 -08:00
|
|
|
# Increase rpc thread count due to reports that lightning implementations fail
|
|
|
|
# under high bitcoind rpc load
|
|
|
|
rpc.threads = 16;
|
2021-02-01 13:53:22 -08:00
|
|
|
|
|
|
|
zmqpubrawblock = "tcp://${bitcoindRpcAddress}:28332";
|
|
|
|
zmqpubrawtx = "tcp://${bitcoindRpcAddress}:28333";
|
2021-01-14 04:24:32 -08:00
|
|
|
};
|
2020-10-18 05:49:20 -07:00
|
|
|
|
2020-04-07 13:47:45 -07:00
|
|
|
environment.systemPackages = [ cfg.package (hiPrio cfg.cli) ];
|
2020-05-05 07:28:30 -07:00
|
|
|
|
2020-05-06 03:43:57 -07:00
|
|
|
systemd.tmpfiles.rules = [
|
2021-02-16 08:50:39 -08:00
|
|
|
"d '${cfg.dataDir}' 0770 ${cfg.user} ${cfg.group} - -"
|
2020-05-06 03:43:57 -07:00
|
|
|
];
|
|
|
|
|
2022-07-07 07:08:27 -07:00
|
|
|
services.lnd.certificate.extraIPs = mkIf (cfg.rpcAddress != "localhost") [ "${cfg.rpcAddress}" ];
|
|
|
|
|
2019-08-05 01:44:38 -07:00
|
|
|
systemd.services.lnd = {
|
|
|
|
wantedBy = [ "multi-user.target" ];
|
2021-01-14 04:24:21 -08:00
|
|
|
requires = [ "bitcoind.service" ];
|
|
|
|
after = [ "bitcoind.service" ];
|
2019-08-05 01:44:38 -07:00
|
|
|
preStart = ''
|
2020-05-22 06:59:18 -07:00
|
|
|
install -m600 ${configFile} '${cfg.dataDir}/lnd.conf'
|
2021-01-14 04:24:21 -08:00
|
|
|
{
|
2022-10-25 13:35:31 -07:00
|
|
|
echo "bitcoind.rpcpass=$(cat ${secretsDir}/bitcoin-rpcpassword-${rpcUser})"
|
2021-01-14 04:24:21 -08:00
|
|
|
${optionalString (cfg.getPublicAddressCmd != "") ''
|
|
|
|
echo "externalip=$(${cfg.getPublicAddressCmd})"
|
|
|
|
''}
|
|
|
|
} >> '${cfg.dataDir}/lnd.conf'
|
2022-03-29 12:48:57 -07:00
|
|
|
|
|
|
|
if [[ ! -f ${networkDir}/wallet.db ]]; then
|
2022-05-17 04:18:39 -07:00
|
|
|
seed='${cfg.dataDir}/lnd-seed-mnemonic'
|
2022-03-29 12:48:57 -07:00
|
|
|
|
2022-05-17 04:18:39 -07:00
|
|
|
if [[ ! -f "$seed" ]]; then
|
2022-03-29 12:48:57 -07:00
|
|
|
echo "Create lnd seed"
|
2022-05-17 04:18:39 -07:00
|
|
|
(umask u=r,go=; ${lndinit} gen-seed > "$seed")
|
2022-03-29 12:48:57 -07:00
|
|
|
fi
|
|
|
|
|
|
|
|
echo "Create lnd wallet"
|
|
|
|
${lndinit} -v init-wallet \
|
2022-05-17 04:18:39 -07:00
|
|
|
--file.seed="$seed" \
|
2022-03-29 12:48:57 -07:00
|
|
|
--file.wallet-password='${secretsDir}/lnd-wallet-password' \
|
|
|
|
--init-file.output-wallet-dir='${cfg.networkDir}'
|
|
|
|
fi
|
2019-08-05 01:44:38 -07:00
|
|
|
'';
|
2021-02-03 13:44:41 -08:00
|
|
|
serviceConfig = nbLib.defaultHardening // {
|
2022-01-14 06:32:08 -08:00
|
|
|
Type = "notify";
|
2020-08-26 01:22:23 -07:00
|
|
|
RuntimeDirectory = "lnd"; # Only used to store custom macaroons
|
|
|
|
RuntimeDirectoryMode = "711";
|
2022-07-14 14:45:24 -07:00
|
|
|
ExecStart = "${cfg.package}/bin/lnd --configfile='${cfg.dataDir}/lnd.conf'";
|
2021-02-16 08:50:39 -08:00
|
|
|
User = cfg.user;
|
2021-10-12 12:56:15 -07:00
|
|
|
TimeoutSec = "15min";
|
2019-08-05 01:44:38 -07:00
|
|
|
Restart = "on-failure";
|
|
|
|
RestartSec = "10s";
|
2022-05-07 11:34:21 -07:00
|
|
|
ReadWritePaths = [ cfg.dataDir ];
|
2020-06-02 06:20:04 -07:00
|
|
|
ExecStartPost = let
|
2022-05-17 04:18:38 -07:00
|
|
|
curl = "${pkgs.curl}/bin/curl -fsS --cacert ${cfg.certPath}";
|
2021-10-01 02:51:57 -07:00
|
|
|
restUrl = "https://${nbLib.addressWithPort cfg.restAddress cfg.restPort}/v1";
|
2022-03-29 12:48:57 -07:00
|
|
|
in
|
2022-01-14 06:32:08 -08:00
|
|
|
# Setting macaroon permissions for other users needs root permissions
|
2022-03-29 12:48:57 -07:00
|
|
|
nbLib.rootScript "lnd-create-macaroons" ''
|
2020-08-26 01:22:23 -07:00
|
|
|
umask ug=r,o=
|
|
|
|
${lib.concatMapStrings (macaroon: ''
|
|
|
|
echo "Create custom macaroon ${macaroon}"
|
|
|
|
macaroonPath="$RUNTIME_DIRECTORY/${macaroon}.macaroon"
|
2021-03-16 04:45:22 -07:00
|
|
|
${curl} \
|
2020-10-16 08:43:07 -07:00
|
|
|
-H "Grpc-Metadata-macaroon: $(${pkgs.xxd}/bin/xxd -ps -u -c 99999 '${networkDir}/admin.macaroon')" \
|
2020-08-26 01:22:23 -07:00
|
|
|
-X POST \
|
|
|
|
-d '{"permissions":[${cfg.macaroons.${macaroon}.permissions}]}' \
|
2020-10-29 13:20:30 -07:00
|
|
|
${restUrl}/macaroon |\
|
2020-08-26 01:22:23 -07:00
|
|
|
${pkgs.jq}/bin/jq -c '.macaroon' | ${pkgs.xxd}/bin/xxd -p -r > "$macaroonPath"
|
|
|
|
chown ${cfg.macaroons.${macaroon}.user}: "$macaroonPath"
|
|
|
|
'') (attrNames cfg.macaroons)}
|
2022-03-29 12:48:57 -07:00
|
|
|
'';
|
2021-11-28 12:24:49 -08:00
|
|
|
} // nbLib.allowedIPAddresses cfg.tor.enforce;
|
2019-08-05 01:44:38 -07:00
|
|
|
};
|
2020-09-28 04:09:03 -07:00
|
|
|
|
2021-02-16 08:50:39 -08:00
|
|
|
users.users.${cfg.user} = {
|
2021-08-04 15:48:59 -07:00
|
|
|
isSystemUser = true;
|
2021-02-16 08:50:39 -08:00
|
|
|
group = cfg.group;
|
2021-02-18 02:42:21 -08:00
|
|
|
extraGroups = [ "bitcoinrpc-public" ];
|
2020-04-19 05:44:03 -07:00
|
|
|
home = cfg.dataDir; # lnd creates .lnd dir in HOME
|
2020-01-12 11:52:37 -08:00
|
|
|
};
|
2021-02-16 08:50:39 -08:00
|
|
|
users.groups.${cfg.group} = {};
|
2020-09-28 04:09:03 -07:00
|
|
|
nix-bitcoin.operator = {
|
2021-02-16 08:50:39 -08:00
|
|
|
groups = [ cfg.group ];
|
|
|
|
allowRunAsUsers = [ cfg.user ];
|
2020-09-28 04:09:03 -07:00
|
|
|
};
|
|
|
|
|
2020-01-12 11:52:38 -08:00
|
|
|
nix-bitcoin.secrets = {
|
2021-02-16 08:50:39 -08:00
|
|
|
lnd-wallet-password.user = cfg.user;
|
|
|
|
lnd-key.user = cfg.user;
|
|
|
|
lnd-cert.user = cfg.user;
|
2021-09-08 08:01:18 -07:00
|
|
|
lnd-cert.permissions = "444"; # world readable
|
2020-01-12 11:52:38 -08:00
|
|
|
};
|
2021-09-08 08:01:18 -07:00
|
|
|
# Advantages of manually pre-generating certs:
|
|
|
|
# - Reduces dynamic state
|
|
|
|
# - Enables deployment of a mesh of server plus client nodes with predefined certs
|
|
|
|
nix-bitcoin.generateSecretsCmds.lnd = ''
|
|
|
|
makePasswordSecret lnd-wallet-password
|
2022-07-07 07:08:27 -07:00
|
|
|
makeCert lnd '${nbLib.mkCertExtraAltNames cfg.certificate}'
|
2021-09-08 08:01:18 -07:00
|
|
|
'';
|
2022-10-25 13:35:31 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
(mkIf isPruned {
|
|
|
|
services.bitcoind.rpc.users.lnd = {
|
|
|
|
passwordHMACFromFile = true;
|
|
|
|
rpcwhitelist = bitcoind.rpc.users.public.rpcwhitelist ++ [
|
|
|
|
"getpeerinfo"
|
|
|
|
"getnodeaddresses"
|
|
|
|
];
|
|
|
|
};
|
|
|
|
nix-bitcoin.secrets = {
|
|
|
|
bitcoin-rpcpassword-lnd.user = cfg.user;
|
|
|
|
bitcoin-HMAC-lnd.user = bitcoind.user;
|
|
|
|
};
|
|
|
|
nix-bitcoin.generateSecretsCmds.lndBitcoinRPC = ''
|
|
|
|
makeBitcoinRPCPassword lnd
|
|
|
|
'';
|
|
|
|
}) ]);
|
2019-08-05 01:44:38 -07:00
|
|
|
}
|