Add LND support

This commit is contained in:
Ștefan D. Mihăilă 2019-08-05 10:44:38 +02:00
parent 4acf5cd32c
commit 9b0753135c
No known key found for this signature in database
GPG Key ID: 6220AD7846220A52
11 changed files with 261 additions and 17 deletions

View File

@ -30,6 +30,15 @@
# default nix-bitcoin nodes offer outgoing connectivity.
# services.clightning.autolisten = true;
### LND
# Disable clightning and uncomment the following line in order to enable lnd,
# a lightning implementation written in Go.
# services.lnd.enable = assert (!config.services.clightning.enable); true;
# WARNING: If you use lnd, you should manually backup your wallet mnemonic
# seed. In order to do so, you can run the following command after the
# lnd service starts:
# nixops scp --from bitcoin-node /secrets/lnd-seed-mnemonic ./secrets/lnd-seed-mnemonic
### SPARK WALLET
# Enable this module to use spark-wallet, a minimalistic wallet GUI for
# c-lightning, accessible over the web or through mobile and desktop apps.

View File

@ -193,6 +193,18 @@ in {
to stay under the specified target size in MiB)
'';
};
zmqpubrawblock = mkOption {
type = types.nullOr types.string;
default = null;
example = "tcp://127.0.0.1:28332";
description = "ZMQ address for zmqpubrawblock notifications";
};
zmqpubrawtx = mkOption {
type = types.nullOr types.string;
default = null;
example = "tcp://127.0.0.1:28333";
description = "ZMQ address for zmqpubrawtx notifications";
};
enforceTor = nix-bitcoin-services.enforceTor;
};
};
@ -239,7 +251,7 @@ in {
// (if cfg.enforceTor
then nix-bitcoin-services.allowTor
else nix-bitcoin-services.allowAnyIP
);
) // optionals config.services.lnd.enable nix-bitcoin-services.allowAnyProtocol; # FOR ZMQ
};
systemd.services.bitcoind-import-banlist = {
description = "Bitcoin daemon banlist importer";

View File

@ -11,4 +11,5 @@
nix-bitcoin-webindex = ./nix-bitcoin-webindex.nix;
spark-wallet = ./spark-wallet.nix;
recurring-donations = ./recurring-donations.nix;
lnd = ./lnd.nix;
}

128
modules/lnd.nix Normal file
View File

@ -0,0 +1,128 @@
{ config, lib, pkgs, ... }:
with lib;
let
nix-bitcoin-services = pkgs.callPackage ./nix-bitcoin-services.nix { };
cfg = config.services.lnd;
configFile = pkgs.writeText "lnd.conf" ''
datadir=${cfg.dataDir}
bitcoin.mainnet=1
tlscertpath=/secrets/lnd_cert
tlskeypath=/secrets/lnd_key
bitcoin.active=1
bitcoin.node=bitcoind
tor.active=true
tor.v3=true
tor.streamisolation=true
bitcoind.rpcuser=${config.services.bitcoind.rpcuser}
bitcoind.zmqpubrawblock=${config.services.bitcoind.zmqpubrawblock}
bitcoind.zmqpubrawtx=${config.services.bitcoind.zmqpubrawtx}
${cfg.extraConfig}
'';
init-lnd-wallet-script = pkgs.writeScript "init-lnd-wallet.sh" ''
#!/bin/sh
set -e
${pkgs.coreutils}/bin/sleep 5
if [ ! -f ${cfg.dataDir}/chain/bitcoin/mainnet/wallet.db ]
then
${pkgs.coreutils}/bin/echo creating LND wallet
${pkgs.curl}/bin/curl -s \
--cacert /secrets/lnd_cert \
-X GET https://127.0.0.1:8080/v1/genseed | ${pkgs.jq}/bin/jq -c '.cipher_seed_mnemonic' > /secrets/lnd-seed-mnemonic
${pkgs.curl}/bin/curl -s \
--cacert /secrets/lnd_cert \
-X POST -d "{\"wallet_password\": \"$(${pkgs.coreutils}/bin/cat /secrets/lnd-wallet-password | ${pkgs.coreutils}/bin/tr -d '\n' | ${pkgs.coreutils}/bin/base64 -w0)\", \
\"cipher_seed_mnemonic\": $(${pkgs.coreutils}/bin/cat /secrets/lnd-seed-mnemonic | ${pkgs.coreutils}/bin/tr -d '\n')}" \
https://127.0.0.1:8080/v1/initwallet
${pkgs.coreutils}/bin/echo wallet created
else
${pkgs.coreutils}/bin/echo unlocking wallet
${pkgs.curl}/bin/curl -s \
-H "Grpc-Metadata-macaroon: $(${pkgs.xxd}/bin/xxd -ps -u -c 1000 ${cfg.dataDir}/chain/bitcoin/mainnet/admin.macaroon)" \
--cacert /secrets/lnd_cert \
-X POST \
-d "{\"wallet_password\": \"$(${pkgs.coreutils}/bin/cat /secrets/lnd-wallet-password | ${pkgs.coreutils}/bin/tr -d '\n' | ${pkgs.coreutils}/bin/base64 -w0)\"}" \
https://127.0.0.1:8080/v1/unlockwallet
${pkgs.coreutils}/bin/echo wallet unlocked
fi
exit 0
'';
in {
options.services.lnd = {
enable = mkOption {
type = types.bool;
default = false;
description = ''
If enabled, the LND service will be installed.
'';
};
dataDir = mkOption {
type = types.path;
default = "/var/lib/lnd";
description = "The data directory for LND.";
};
extraConfig = mkOption {
type = types.lines;
default = "";
example = ''
autopilot.active=1
'';
description = "Additional configurations to be appended to <filename>lnd.conf</filename>.";
};
enforceTor = nix-bitcoin-services.enforceTor;
};
config = mkIf cfg.enable {
users.users.lnd = {
description = "LND User";
group = "lnd";
extraGroups = [ "bitcoinrpc" "keys" ];
home = cfg.dataDir;
};
users.groups.lnd = {
name = "lnd";
};
systemd.services.lnd = {
description = "Run LND";
path = [ pkgs.altcoins.bitcoind ];
wantedBy = [ "multi-user.target" ];
requires = [ "bitcoind.service" ];
after = [ "bitcoind.service" ];
preStart = ''
mkdir -m 0770 -p ${cfg.dataDir}
cp ${configFile} ${cfg.dataDir}/lnd.conf
chown -R 'lnd:lnd' '${cfg.dataDir}'
chmod u=rw,g=r,o= ${cfg.dataDir}/lnd.conf
echo "bitcoind.rpcpass=$(cat /secrets/bitcoin-rpcpassword)" >> '${cfg.dataDir}/lnd.conf'
'';
serviceConfig = {
PermissionsStartOnly = "true";
ExecStart = "${pkgs.lnd}/bin/lnd --configfile=${cfg.dataDir}/lnd.conf";
ExecStartPost = "${pkgs.bash}/bin/bash ${init-lnd-wallet-script}";
User = "lnd";
Restart = "on-failure";
RestartSec = "10s";
} // nix-bitcoin-services.defaultHardening
// (if cfg.enforceTor
then nix-bitcoin-services.allowTor
else nix-bitcoin-services.allowAnyIP
) // nix-bitcoin-services.allowAnyProtocol; # For ZMQ
};
};
}

View File

@ -15,5 +15,6 @@ in {
bitcoin = nixpkgs-unstable.bitcoin.override { miniupnpc = null; };
altcoins.bitcoind = nixpkgs-unstable.altcoins.bitcoind.override { miniupnpc = null; };
clightning = nixpkgs-unstable.clightning.override { };
lnd = nixpkgs-unstable.lnd.override { };
};
}

View File

@ -42,6 +42,3 @@ in
'';
};
}

View File

@ -28,6 +28,7 @@ in {
./onion-chef.nix
./recurring-donations.nix
./hardware-wallets.nix
./lnd.nix
];
options.services.nix-bitcoin = {
@ -46,6 +47,8 @@ in {
# Tor
services.tor.enable = true;
services.tor.client.enable = true;
# LND uses ControlPort to create onion services
services.tor.controlPort = if config.services.lnd.enable then 9051 else null;
# Tor SSH service
services.tor.hiddenServices.sshd = {
@ -64,12 +67,16 @@ in {
services.bitcoind.enforceTor = true;
services.bitcoind.port = 8333;
services.bitcoind.rpcuser = "bitcoinrpc";
services.bitcoind.zmqpubrawblock = "tcp://127.0.0.1:28332";
services.bitcoind.zmqpubrawtx = "tcp://127.0.0.1:28333";
services.bitcoind.extraConfig = ''
assumevalid=0000000000000000000726d186d6298b5054b9a5c49639752294b322a305d240
addnode=ecoc5q34tmbq54wl.onion
discover=0
addresstype=bech32
changetype=bech32
${optionalString (config.services.lnd.enable) "zmqpubrawblock=${config.services.bitcoind.zmqpubrawblock}"}
${optionalString (config.services.lnd.enable) "zmqpubrawtx=${config.services.bitcoind.zmqpubrawtx}"}
'';
services.bitcoind.prune = 0;
services.bitcoind.dbCache = 1000;
@ -96,32 +103,51 @@ in {
version = 3;
};
# lnd
services.lnd.enforceTor = true;
services.tor.hiddenServices.lnd = {
map = [{
port = 9735; toPort = 9735;
}];
version = 3;
};
# Create user operator which can use bitcoin-cli and lightning-cli
users.users.operator = {
isNormalUser = true;
extraGroups = [ config.services.bitcoind.group ]
++ (if config.services.clightning.enable then [ "clightning" ] else [ ])
++ (if config.services.lnd.enable then [ "lnd" ] else [ ])
++ (if config.services.liquidd.enable then [ config.services.liquidd.group ] else [ ])
++ (if (config.services.hardware-wallets.ledger || config.services.hardware-wallets.trezor)
then [ config.services.hardware-wallets.group ] else [ ]);
};
# Give operator access to onion hostnames
services.onion-chef.enable = true;
services.onion-chef.access.operator = [ "bitcoind" "clightning" "nginx" "liquidd" "spark-wallet" "electrs" "sshd" ];
services.onion-chef.access.operator = [ "bitcoind" "clightning" "lnd" "nginx" "liquidd" "spark-wallet" "electrs" "sshd" ];
environment.interactiveShellInit = ''
alias bitcoin-cli='bitcoin-cli -datadir=${config.services.bitcoind.dataDir}'
alias lightning-cli='sudo -u clightning lightning-cli --lightning-dir=${config.services.clightning.dataDir}'
'' + (if config.services.liquidd.enable then ''
alias elements-cli='elements-cli -datadir=${config.services.liquidd.dataDir}'
alias liquidswap-cli='liquidswap-cli -c ${config.services.liquidd.dataDir}/elements.conf'
'' else "");
${optionalString (config.services.clightning.enable) ''
alias lightning-cli='sudo -u clightning lightning-cli --lightning-dir=${config.services.clightning.dataDir}'
''}
${optionalString (config.services.lnd.enable) ''
alias lncli='sudo -u lnd lncli --tlscertpath /secrets/lnd_cert --macaroonpath ${config.services.lnd.dataDir}/chain/bitcoin/mainnet/admin.macaroon'
''}
${optionalString (config.services.liquidd.enable) ''
alias elements-cli='elements-cli -datadir=${config.services.liquidd.dataDir}'
alias liquidswap-cli='liquidswap-cli -c ${config.services.liquidd.dataDir}/elements.conf'
''}
'';
# Unfortunately c-lightning doesn't allow setting the permissions of the rpc socket
# https://github.com/ElementsProject/lightning/issues/1366
security.sudo.configFile = (
if config.services.clightning.enable then ''
operator ALL=(clightning) NOPASSWD: ALL
''
else if config.services.lnd.enable then ''
operator ALL=(lnd) NOPASSWD: ALL
''
else ""
);
@ -176,6 +202,7 @@ in {
qrencode
]
++ optionals config.services.clightning.enable [clightning]
++ optionals config.services.lnd.enable [lnd]
++ optionals config.services.lightning-charge.enable [lightning-charge]
++ optionals config.services.nanopos.enable [nanopos]
++ optionals config.services.nix-bitcoin-webindex.enable [nginx]

View File

@ -7,6 +7,13 @@ let
group = "bitcoinrpc";
permissions = "0440";
};
lnd-wallet-password = {
text = secrets.lnd-wallet-password;
destDir = "/secrets/";
user = "lnd";
group = "lnd";
permissions = "0440";
};
lightning-charge-api-token = {
text = "API_TOKEN=" + secrets.lightning-charge-api-token;
destDir = "/secrets/";
@ -50,6 +57,20 @@ let
group = "root";
permissions = "0440";
};
lnd_key = {
keyFile = ../secrets/lnd.key;
destDir = "/secrets/";
user = "lnd";
group = "lnd";
permissions = "0440";
};
lnd_cert = {
keyFile = ../secrets/lnd.cert;
destDir = "/secrets/";
user = "lnd";
group = "lnd";
permissions = "0440";
};
in {
network.description = "Bitcoin Core node";
@ -61,6 +82,7 @@ in {
deployment.keys = {
inherit bitcoin-rpcpassword;
}
// (if (config.services.lnd.enable) then { inherit lnd-wallet-password lnd_key lnd_cert; } else { })
// (if (config.services.lightning-charge.enable) then { inherit lightning-charge-api-token; } else { })
// (if (config.services.nanopos.enable) then { inherit lightning-charge-api-token-for-nanopos; } else { })
// (if (config.services.liquidd.enable) then { inherit liquid-rpcpassword; } else { })

View File

@ -2,14 +2,21 @@ set -e
set -o pipefail
BITCOIND_ONION="$(cat /var/lib/onion-chef/operator/bitcoind)"
CLIGHTNING_NODEID=$(sudo -u clightning lightning-cli --lightning-dir=/var/lib/clightning getinfo | jq -r '.id')
CLIGHTNING_ONION="$(cat /var/lib/onion-chef/operator/clightning)"
CLIGHTNING_ID="$CLIGHTNING_NODEID@$CLIGHTNING_ONION:9735"
echo BITCOIND_ONION="$BITCOIND_ONION"
echo CLIGHTNING_NODEID="$CLIGHTNING_NODEID"
echo CLIGHTNING_ONION="$CLIGHTNING_ONION"
echo CLIGHTNING_ID="$CLIGHTNING_ID"
if [ -x "$(command -v clightning)" ]; then
CLIGHTNING_NODEID=$(sudo -u clightning lightning-cli --lightning-dir=/var/lib/clightning getinfo | jq -r '.id')
CLIGHTNING_ONION="$(cat /var/lib/onion-chef/operator/clightning)"
CLIGHTNING_ID="$CLIGHTNING_NODEID@$CLIGHTNING_ONION:9735"
echo CLIGHTNING_NODEID="$CLIGHTNING_NODEID"
echo CLIGHTNING_ONION="$CLIGHTNING_ONION"
echo CLIGHTNING_ID="$CLIGHTNING_ID"
fi
if [ -x "$(command -v lncli)" ]; then
LND_NODEID=$(sudo -u lnd lncli --tlscertpath /secrets/lnd_cert --macaroonpath /var/lib/lnd/chain/bitcoin/mainnet/admin.macaroon getinfo | jq -r '.uris[0]')
echo LND_NODEID="$LND_NODEID"
fi
NGINX_ONION_FILE=/var/lib/onion-chef/operator/nginx
if [ -e "$NGINX_ONION_FILE" ]; then

View File

@ -11,6 +11,7 @@ echo Write secrets to $SECRETSFILE
{
echo \{
echo " bitcoinrpcpassword = \"$(apg -m 20 -x 20 -M Ncl -n 1)\";"
echo " lnd-wallet-password = \"$(apg -m 20 -x 20 -M Ncl -n 1)\";"
echo " lightning-charge-api-token = \"$(apg -m 20 -x 20 -M Ncl -n 1)\";"
echo " liquidrpcpassword = \"$(apg -m 20 -x 20 -M Ncl -n 1)\";"
echo " spark-wallet-password = \"$(apg -m 20 -x 20 -M Ncl -n 1)\";"
@ -24,3 +25,10 @@ openssl req -new -key secrets/nginx.key -out secrets/nginx.csr -subj "/C=KN"
openssl x509 -req -days 1825 -in secrets/nginx.csr -signkey secrets/nginx.key -out secrets/nginx.cert
rm secrets/nginx.csr
echo Done
echo Generate LND compatible TLS Cert
openssl ecparam -genkey -name prime256v1 -out secrets/lnd.key
openssl req -config secrets/openssl.cnf -new -sha256 -key secrets/lnd.key -out secrets/lnd.csr -subj '/CN=localhost/O=lnd'
openssl req -config secrets/openssl.cnf -x509 -sha256 -days 1825 -key secrets/lnd.key -in secrets/lnd.csr -out secrets/lnd.cert
rm secrets/lnd.csr
echo Done

32
secrets/openssl.cnf Normal file
View File

@ -0,0 +1,32 @@
[ req ]
#default_bits = 2048
#default_md = sha256
#default_keyfile = privkey.pem
distinguished_name = req_distinguished_name
attributes = req_attributes
x509_extensions = v3_ca
[ req_distinguished_name ]
countryName = Country Name (2 letter code)
countryName_min = 2
countryName_max = 2
stateOrProvinceName = State or Province Name (full name)
localityName = Locality Name (eg, city)
0.organizationName = Organization Name (eg, company)
organizationalUnitName = Organizational Unit Name (eg, section)
commonName = Common Name (eg, fully qualified host name)
commonName_max = 64
emailAddress = Email Address
emailAddress_max = 64
[ req_attributes ]
challengePassword = A challenge password
challengePassword_min = 4
challengePassword_max = 20
[ v3_ca ]
subjectAltName = @alt_names
[ alt_names ]
IP.1 = 127.0.0.1
DNS.1 = localhost