Merge #293: Module refactorings, onionServices

e2922eb4ce move rpc thread count setting to lightning modules (Erik Arvstedt)
352fc4e8fe liquid: remove insecure and redundant option 'rpcpassword' (Erik Arvstedt)
757a66b9bd liquid: move rpcuser definition to module (Erik Arvstedt)
0e00c39d47 secure-node: improve layout (Erik Arvstedt)
5f7a7962f7 backups: remove redundant option 'program' (Erik Arvstedt)
04d8560f86 secure-node: remove qrencode, tor from systemPackages (Erik Arvstedt)
323a431aba improve nodeinfo (Erik Arvstedt)
f6b883a9ac remove webindex (Erik Arvstedt)
2a240d6f4a enable-tor: disable default onion services for clightning, lnd, btcpayserver (Erik Arvstedt)
18c7842e1a modules: show warnings for obsolete options (Erik Arvstedt)
45c40c4eb9 versioning: simplify assertion evaluation (Erik Arvstedt)
bed00fe937 lnd: use onionServices for address announcing (Erik Arvstedt)
3980cd5a41 clightning: use onionServices for address announcing (Erik Arvstedt)
bd2a46cb73 spark-wallet: use onionServices (Erik Arvstedt)
87fb9f246b add 'enable-tor' preset (Erik Arvstedt)
05b5402bb1 add nix-bitcoin.onionServices (Erik Arvstedt)
fffe988248 onionAddresses: add readonly option 'dataDir' (Erik Arvstedt)
5f34b094d3 onionAddresses: improve script (Erik Arvstedt)
b266f23251 onionAddresses: use service 'script' option (Erik Arvstedt)
6d13b26d0a onionAddresses: add more precise type for option 'access' (Erik Arvstedt)
93562f76dd onionAddresses: remove redundant option 'enable' (Erik Arvstedt)
43c247e3fe onionAddresses: use StateDirectory instead of tmpfiles (Erik Arvstedt)
5c6977b006 rename onion-chef -> nix-bitcoin.onionAddresses (Erik Arvstedt)
55073eee70 remove nix-bitcoin.pkgs.lib (Erik Arvstedt)
09e0042aa8 spark-wallet: add consistent address options (Erik Arvstedt)
39f16c0b4a liquidd: add consistent address options (Erik Arvstedt)
b5d76ba1b3 electrs: add consistent address options (Erik Arvstedt)
8fa32b7f91 btcpayserver: add consistent address options (Erik Arvstedt)
e78a609687 clightning: add consistent address options (Erik Arvstedt)
b41a720c28 lnd: add consistent address options (Erik Arvstedt)
dd4a0238f9 bitcoind: group rpc options under parent option 'rpc' (Erik Arvstedt)
5b7e0d09b2 bitcoind: add consistent address options (Erik Arvstedt)

Pull request description:

ACKs for top commit:
  nixbitcoin:
    ACK e2922eb4ce
  jonasnick:
    ACK e2922eb4ce

Tree-SHA512: a85b33efe66048f06699b3997f83c9427f70f278fa66d30ee9a29c91f50723ff8bd1ffb9d968d7f08818742c8c6afb0b40dbfc14b95a4b8c3302caf9bede4198
This commit is contained in:
Jonas Nick 2021-01-14 20:42:02 +00:00
commit c6c14889eb
No known key found for this signature in database
GPG Key ID: 4861DBF262123605
30 changed files with 659 additions and 625 deletions

View File

@ -48,8 +48,7 @@ See the [examples directory](examples/README.md).
Features Features
--- ---
A [configuration preset](modules/presets/secure-node.nix) for setting up a secure node A [configuration preset](modules/presets/secure-node.nix) for setting up a secure node
* All applications use Tor for outbound connections and accept inbound connections via onion services. * All applications use Tor for outbound connections and support accepting inbound connections via onion services.
* Includes a [nodeinfo](modules/nodeinfo.nix) script which prints basic info about the node.
NixOS modules NixOS modules
* Application services * Application services
@ -74,9 +73,9 @@ NixOS modules
* [bitcoin-core-hwi](https://github.com/bitcoin-core/HWI) * [bitcoin-core-hwi](https://github.com/bitcoin-core/HWI)
* Helper * Helper
* [netns-isolation](modules/netns-isolation.nix): isolates applications on the network-level via network namespaces * [netns-isolation](modules/netns-isolation.nix): isolates applications on the network-level via network namespaces
* [nodeinfo](modules/nodeinfo.nix): script which prints info about the node's services
* [backups](modules/backups.nix): daily duplicity backups of all your node's important files * [backups](modules/backups.nix): daily duplicity backups of all your node's important files
* [operator](modules/operator.nix): adds non-root user `operator` who has access to client tools (e.g. `bitcoin-cli`, `lightning-cli`) * [operator](modules/operator.nix): adds non-root user `operator` who has access to client tools (e.g. `bitcoin-cli`, `lightning-cli`)
* [nix-bitcoin webindex](modules/nix-bitcoin-webindex.nix): a local website to display node information
Security Security
--- ---

View File

@ -8,7 +8,7 @@ fetch-release > nix-bitcoin-release.nix
Nodeinfo Nodeinfo
--- ---
Run `nodeinfo` to see your onion addresses for the webindex, spark, etc. if they are enabled. Run `nodeinfo` to see onion addresses and local addresses for enabled services.
Connect to spark-wallet Connect to spark-wallet
--- ---
@ -86,10 +86,10 @@ Connect to electrs
nixops deploy -d bitcoin-node nixops deploy -d bitcoin-node
``` ```
3. Get electrs onion address 3. Get electrs onion address with format `<onion-address>:<port>`
``` ```
nodeinfo | grep 'ELECTRS_ONION' nodeinfo | jq -r .electrs.onion_address
``` ```
4. Connect to electrs 4. Connect to electrs
@ -98,7 +98,7 @@ Connect to electrs
On Desktop On Desktop
``` ```
electrum --oneserver -1 -s "<ELECTRS_ONION>:50001:t" -p socks5:localhost:9050 electrum --oneserver -1 -s "<electrs onion address>:t" -p socks5:localhost:9050
``` ```
On Android On Android
@ -107,16 +107,16 @@ Connect to electrs
Network > Proxy mode: socks5, Host: 127.0.0.1, Port: 9050 Network > Proxy mode: socks5, Host: 127.0.0.1, Port: 9050
Network > Auto-connect: OFF Network > Auto-connect: OFF
Network > One-server mode: ON Network > One-server mode: ON
Network > Server: <ELECTRS_ONION>:50001:t Network > Server: <electrs onion address>:t
``` ```
Connect to nix-bitcoin node through ssh Tor Hidden Service Connect to nix-bitcoin node through the SSH onion service
--- ---
1. Run `nodeinfo` on your nix-bitcoin node and note the `SSHD_ONION` 1. Get the SSH onion address (excluding the port suffix)
``` ```
nixops ssh operator@bitcoin-node nixops ssh operator@bitcoin-node
nodeinfo | grep 'SSHD_ONION' nodeinfo | jq -r .sshd.onion_address | sed 's/:.*//'
``` ```
2. Create a SSH key 2. Create a SSH key
@ -131,14 +131,14 @@ Connect to nix-bitcoin node through ssh Tor Hidden Service
# FIXME: Add your SSH pubkey # FIXME: Add your SSH pubkey
services.openssh.enable = true; services.openssh.enable = true;
users.users.root = { users.users.root = {
openssh.authorizedKeys.keys = [ "[contents of ~/.ssh/id_ed25519.pub]" ]; openssh.authorizedKeys.keys = [ "<contents of ~/.ssh/id_ed25519.pub>" ];
}; };
``` ```
4. Connect to your nix-bitcoin node's ssh Tor Hidden Service, forwarding a local port to the nix-bitcoin node's ssh server 4. Connect to your nix-bitcoin node's SSH onion service, forwarding a local port to the nix-bitcoin node's SSH server
``` ```
ssh -i ~/.ssh/id_ed25519 -L [random port of your choosing]:localhost:22 root@[your SSHD_ONION] ssh -i ~/.ssh/id_ed25519 -L <random port of your choosing>:localhost:22 root@<SSH onion address>
``` ```
5. Edit your `network-nixos.nix` to look like this 5. Edit your `network-nixos.nix` to look like this
@ -148,12 +148,12 @@ Connect to nix-bitcoin node through ssh Tor Hidden Service
bitcoin-node = bitcoin-node =
{ config, pkgs, ... }: { config, pkgs, ... }:
{ deployment.targetHost = "127.0.0.1"; { deployment.targetHost = "127.0.0.1";
deployment.targetPort = [random port of your choosing]; deployment.targetPort = <random port of your choosing>;
}; };
} }
``` ```
6. Now you can run `nixops deploy -d bitcoin-node` and it will connect through the ssh tunnel you established in step iv. This also allows you to do more complex ssh setups that `nixops ssh` doesn't support. An example would be authenticating with [Trezor's ssh agent](https://github.com/romanz/trezor-agent), which provides extra security. 6. Now you can run `nixops deploy -d bitcoin-node` and it will connect through the SSH tunnel you established in step iv. This also allows you to do more complex SSH setups that `nixops ssh` doesn't support. An example would be authenticating with [Trezor's SSH agent](https://github.com/romanz/trezor-agent), which provides extra security.
Initialize a Trezor for Bitcoin Core's Hardware Wallet Interface Initialize a Trezor for Bitcoin Core's Hardware Wallet Interface
--- ---
@ -263,7 +263,7 @@ you. If however, you want to manually initialize your wallet, follow these steps
## Run the tumbler ## Run the tumbler
The tumbler needs to be able to run in the background for a long time, use screen The tumbler needs to be able to run in the background for a long time, use screen
to run it accross ssh sessions. You can also use tmux in the same fashion. to run it accross SSH sessions. You can also use tmux in the same fashion.
1. Add screen to your `environment.systemPackages`, for example 1. Add screen to your `environment.systemPackages`, for example

View File

@ -11,7 +11,7 @@ nix-shell
The following example scripts set up a nix-bitcoin node according to [`configuration.nix`](configuration.nix) and then The following example scripts set up a nix-bitcoin node according to [`configuration.nix`](configuration.nix) and then
shut down immediately. They leave no traces (outside of `/nix/store`) on the host system.\ shut down immediately. They leave no traces (outside of `/nix/store`) on the host system.\
By default, [`configuration.nix`](configuration.nix) enables `bitcoind` and `clightning` (with an onion service). By default, [`configuration.nix`](configuration.nix) enables `bitcoind` and `clightning`.
- [`./deploy-container.sh`](deploy-container.sh) creates a [NixOS container](https://github.com/erikarvstedt/extra-container).\ - [`./deploy-container.sh`](deploy-container.sh) creates a [NixOS container](https://github.com/erikarvstedt/extra-container).\
This is the fastest way to set up a node.\ This is the fastest way to set up a node.\

View File

@ -37,11 +37,12 @@
# Enable this module to use clightning, a Lightning Network implementation # Enable this module to use clightning, a Lightning Network implementation
# in C. # in C.
services.clightning.enable = true; services.clightning.enable = true;
# == TOR #
# Enable this option to announce our Tor Hidden Service. By default clightning # Set this to create an onion service by which clightning can accept incoming connections
# offers outgoing functionality, but doesn't announce the Tor Hidden Service # via Tor.
# under which peers can reach us. # The onion service is automatically announced to peers.
# services.clightning.announce-tor = true; # nix-bitcoin.onionServices.clightning.public = true;
#
# == Plugins # == Plugins
# See ../docs/usage.md for the list of available plugins. # See ../docs/usage.md for the list of available plugins.
# services.clightning.plugins.prometheus.enable = true; # services.clightning.plugins.prometheus.enable = true;
@ -49,13 +50,15 @@
### LND ### LND
# Uncomment the following line in order to enable lnd, a lightning # Uncomment the following line in order to enable lnd, a lightning
# implementation written in Go. In order to avoid collisions with clightning # implementation written in Go. In order to avoid collisions with clightning
# you must disable clightning or change the services.clightning.bindport or # you must disable clightning or change the services.clightning.port or
# services.lnd.listenPort to a port other than 9735. # services.lnd.port to a port other than 9735.
# services.lnd.enable = true; # services.lnd.enable = true;
# Enable this option to announce our Tor Hidden Service. By default lnd #
# offers outgoing functionality, but doesn't announce the Tor Hidden Service # Set this to create an onion service by which lnd can accept incoming connections
# under which peers can reach us. # via Tor.
# services.lnd.announce-tor = true; # The onion service is automatically announced to peers.
# nix-bitcoin.onionServices.lnd.public = true;
#
## WARNING ## WARNING
# If you use lnd, you should manually backup your wallet mnemonic # If you use lnd, you should manually backup your wallet mnemonic
# seed. This will allow you to recover on-chain funds. You can run the # seed. This will allow you to recover on-chain funds. You can run the
@ -93,6 +96,12 @@
# The lightning backend service automatically enabled. # The lightning backend service automatically enabled.
# Afterwards you need to go into Store > General Settings > Lightning Nodes # Afterwards you need to go into Store > General Settings > Lightning Nodes
# and click to use "the internal lightning node of this BTCPay Server". # and click to use "the internal lightning node of this BTCPay Server".
#
# Set this to create an onion service to make the btcpayserver web interface
# accessible via Tor.
# Security WARNING: Create a btcpayserver administrator account before allowing
# public access to the web interface.
# nix-bitcoin.onionServices.btcpayserver.enable = true;
### LIQUIDD ### LIQUIDD
# Enable this module to use Liquid, a sidechain for an inter-exchange # Enable this module to use Liquid, a sidechain for an inter-exchange
@ -101,11 +110,6 @@
# tool run as user operator. # tool run as user operator.
# services.liquidd.enable = true; # services.liquidd.enable = true;
### WEBINDEX
# Enable this module to use the nix-bitcoin-webindex, a simple website
# displaying your node information. Only available if clightning is enabled.
# services.nix-bitcoin-webindex.enable = true;
### RECURRING-DONATIONS ### RECURRING-DONATIONS
# Enable this module to send recurring donations. This is EXPERIMENTAL; it's # Enable this module to send recurring donations. This is EXPERIMENTAL; it's
# not guaranteed that payments are succeeding or that you will notice payment # not guaranteed that payments are succeeding or that you will notice payment
@ -203,5 +207,5 @@
# The nix-bitcoin release version that your config is compatible with. # The nix-bitcoin release version that your config is compatible with.
# When upgrading to a backwards-incompatible release, nix-bitcoin will display an # When upgrading to a backwards-incompatible release, nix-bitcoin will display an
# an error and provide hints for migrating your config to the new release. # an error and provide hints for migrating your config to the new release.
nix-bitcoin.configVersion = "0.0.26"; nix-bitcoin.configVersion = "0.0.30";
} }

View File

@ -31,13 +31,6 @@ let
in { in {
options.services.backups = { options.services.backups = {
enable = mkEnableOption "Backups service"; enable = mkEnableOption "Backups service";
program = mkOption {
type = types.enum [ "duplicity" ];
default = "duplicity";
description = ''
Program with which to do backups.
'';
};
with-bulk-data = mkOption { with-bulk-data = mkOption {
type = types.bool; type = types.bool;
default = false; default = false;
@ -69,7 +62,7 @@ in {
}; };
}; };
config = mkIf (cfg.enable && cfg.program == "duplicity") (mkMerge [ config = mkIf cfg.enable (mkMerge [
{ {
environment.systemPackages = [ pkgs.duplicity ]; environment.systemPackages = [ pkgs.duplicity ];

View File

@ -22,16 +22,18 @@ let
${optionalString (cfg.assumevalid != null) "assumevalid=${cfg.assumevalid}"} ${optionalString (cfg.assumevalid != null) "assumevalid=${cfg.assumevalid}"}
# Connection options # Connection options
${optionalString cfg.listen "bind=${cfg.bind}"} ${optionalString cfg.listen "bind=${cfg.address}"}
${optionalString (cfg.port != null) "port=${toString cfg.port}"} port=${toString cfg.port}
${optionalString (cfg.proxy != null) "proxy=${cfg.proxy}"} ${optionalString (cfg.proxy != null) "proxy=${cfg.proxy}"}
listen=${if cfg.listen then "1" else "0"} listen=${if cfg.listen then "1" else "0"}
${optionalString (cfg.discover != null) "discover=${if cfg.discover then "1" else "0"}"} ${optionalString (cfg.discover != null) "discover=${if cfg.discover then "1" else "0"}"}
${lib.concatMapStrings (node: "addnode=${node}\n") cfg.addnodes} ${lib.concatMapStrings (node: "addnode=${node}\n") cfg.addnodes}
# RPC server options # RPC server options
${optionalString (cfg.rpcthreads != null) "rpcthreads=${toString cfg.rpcthreads}"} rpcbind=${cfg.rpc.address}
rpcport=${toString cfg.rpc.port} rpcport=${toString cfg.rpc.port}
rpcconnect=${cfg.rpc.address}
${optionalString (cfg.rpc.threads != null) "rpcthreads=${toString cfg.rpc.threads}"}
rpcwhitelistdefault=0 rpcwhitelistdefault=0
${concatMapStrings (user: '' ${concatMapStrings (user: ''
${optionalString (!user.passwordHMACFromFile) "rpcauth=${user.name}:${passwordHMAC}"} ${optionalString (!user.passwordHMACFromFile) "rpcauth=${user.name}:${passwordHMAC}"}
@ -39,9 +41,7 @@ let
"rpcwhitelist=${user.name}:${lib.strings.concatStringsSep "," user.rpcwhitelist}"} "rpcwhitelist=${user.name}:${lib.strings.concatStringsSep "," user.rpcwhitelist}"}
'') (builtins.attrValues cfg.rpc.users) '') (builtins.attrValues cfg.rpc.users)
} }
rpcbind=${cfg.rpcbind} ${lib.concatMapStrings (rpcallowip: "rpcallowip=${rpcallowip}\n") cfg.rpc.allowip}
rpcconnect=${cfg.rpcbind}
${lib.concatMapStrings (rpcallowip: "rpcallowip=${rpcallowip}\n") cfg.rpcallowip}
# Wallet options # Wallet options
${optionalString (cfg.addresstype != null) "addresstype=${cfg.addresstype}"} ${optionalString (cfg.addresstype != null) "addresstype=${cfg.addresstype}"}
@ -57,6 +57,16 @@ in {
options = { options = {
services.bitcoind = { services.bitcoind = {
enable = mkEnableOption "Bitcoin daemon"; enable = mkEnableOption "Bitcoin daemon";
address = mkOption {
type = types.str;
default = "127.0.0.1";
description = "Address to listen for peer connections.";
};
port = mkOption {
type = types.port;
default = 8333;
description = "Port to listen for peer connections.";
};
package = mkOption { package = mkOption {
type = types.package; type = types.package;
default = config.nix-bitcoin.pkgs.bitcoind; default = config.nix-bitcoin.pkgs.bitcoind;
@ -77,13 +87,6 @@ in {
default = "/var/lib/bitcoind"; default = "/var/lib/bitcoind";
description = "The data directory for bitcoind."; description = "The data directory for bitcoind.";
}; };
bind = mkOption {
type = types.str;
default = "127.0.0.1";
description = ''
Bind to given address and always listen on it.
'';
};
user = mkOption { user = mkOption {
type = types.str; type = types.str;
default = "bitcoin"; default = "bitcoin";
@ -95,10 +98,29 @@ in {
description = "The group as which to run bitcoind."; description = "The group as which to run bitcoind.";
}; };
rpc = { rpc = {
address = mkOption {
type = types.str;
default = "127.0.0.1";
description = ''
Address to listen for JSON-RPC connections.
'';
};
port = mkOption { port = mkOption {
type = types.port; type = types.port;
default = 8332; default = 8332;
description = "Port on which to listen for JSON-RPC connections."; description = "Port to listen for JSON-RPC connections.";
};
threads = mkOption {
type = types.nullOr types.ints.u16;
default = null;
description = "The number of threads to service RPC calls.";
};
allowip = mkOption {
type = types.listOf types.str;
default = [ "127.0.0.1" ];
description = ''
Allow JSON-RPC connections from specified sources.
'';
}; };
users = mkOption { users = mkOption {
default = {}; default = {};
@ -144,25 +166,6 @@ in {
''; '';
}; };
}; };
rpcthreads = mkOption {
type = types.nullOr types.ints.u16;
default = null;
description = "Set the number of threads to service RPC calls";
};
rpcbind = mkOption {
type = types.str;
default = "127.0.0.1";
description = ''
Bind to given address to listen for JSON-RPC connections.
'';
};
rpcallowip = mkOption {
type = types.listOf types.str;
default = [ "127.0.0.1" ];
description = ''
Allow JSON-RPC connections from specified source.
'';
};
regtest = mkOption { regtest = mkOption {
type = types.bool; type = types.bool;
default = false; default = false;
@ -176,11 +179,6 @@ in {
readOnly = true; readOnly = true;
default = mainnet: regtest: if cfg.regtest then regtest else mainnet; default = mainnet: regtest: if cfg.regtest then regtest else mainnet;
}; };
port = mkOption {
type = types.nullOr types.port;
default = null;
description = "Override the default port on which to listen for connections.";
};
proxy = mkOption { proxy = mkOption {
type = types.nullOr types.str; type = types.nullOr types.str;
default = if cfg.enforceTor then config.services.tor.client.socksListenAddress else null; default = if cfg.enforceTor then config.services.tor.client.socksListenAddress else null;

View File

@ -14,6 +14,16 @@ in {
default = nbPkgs.nbxplorer; default = nbPkgs.nbxplorer;
description = "The package providing nbxplorer binaries."; description = "The package providing nbxplorer binaries.";
}; };
address = mkOption {
type = types.str;
default = "127.0.0.1";
description = "Address to listen on.";
};
port = mkOption {
type = types.port;
default = 24444;
description = "Port to listen on.";
};
dataDir = mkOption { dataDir = mkOption {
type = types.path; type = types.path;
default = "/var/lib/nbxplorer"; default = "/var/lib/nbxplorer";
@ -29,16 +39,6 @@ in {
default = cfg.nbxplorer.user; default = cfg.nbxplorer.user;
description = "The group as which to run nbxplorer."; description = "The group as which to run nbxplorer.";
}; };
bind = mkOption {
type = types.str;
default = "127.0.0.1";
description = "The address on which to bind.";
};
port = mkOption {
type = types.port;
default = 24444;
description = "Port on which to bind.";
};
enable = mkOption { enable = mkOption {
# This option is only used by netns-isolation # This option is only used by netns-isolation
internal = true; internal = true;
@ -49,6 +49,16 @@ in {
btcpayserver = { btcpayserver = {
enable = mkEnableOption "btcpayserver"; enable = mkEnableOption "btcpayserver";
address = mkOption {
type = types.str;
default = "127.0.0.1";
description = "Address to listen on.";
};
port = mkOption {
type = types.port;
default = 23000;
description = "Port to listen on.";
};
package = mkOption { package = mkOption {
type = types.package; type = types.package;
default = nbPkgs.btcpayserver; default = nbPkgs.btcpayserver;
@ -69,16 +79,6 @@ in {
default = cfg.btcpayserver.user; default = cfg.btcpayserver.user;
description = "The group as which to run btcpayserver."; description = "The group as which to run btcpayserver.";
}; };
bind = mkOption {
type = types.str;
default = "127.0.0.1";
description = "The address on which to bind.";
};
port = mkOption {
type = types.port;
default = 23000;
description = "Port on which to bind.";
};
lightningBackend = mkOption { lightningBackend = mkOption {
type = types.nullOr (types.enum [ "clightning" "lnd" ]); type = types.nullOr (types.enum [ "clightning" "lnd" ]);
default = null; default = null;
@ -117,9 +117,9 @@ in {
configFile = builtins.toFile "config" '' configFile = builtins.toFile "config" ''
network=${config.services.bitcoind.network} network=${config.services.bitcoind.network}
btcrpcuser=${cfg.bitcoind.rpc.users.btcpayserver.name} btcrpcuser=${cfg.bitcoind.rpc.users.btcpayserver.name}
btcrpcurl=http://${config.services.bitcoind.rpcbind}:${toString cfg.bitcoind.rpc.port} btcrpcurl=http://${config.services.bitcoind.rpc.address}:${toString cfg.bitcoind.rpc.port}
btcnodeendpoint=${config.services.bitcoind.bind}:8333 btcnodeendpoint=${config.services.bitcoind.address}:${toString config.services.bitcoind.port}
bind=${cfg.nbxplorer.bind} bind=${cfg.nbxplorer.address}
port=${toString cfg.nbxplorer.port} port=${toString cfg.nbxplorer.port}
''; '';
in { in {
@ -153,9 +153,9 @@ in {
network=${config.services.bitcoind.network} network=${config.services.bitcoind.network}
postgres=User ID=${cfg.btcpayserver.user};Host=/run/postgresql;Database=btcpaydb postgres=User ID=${cfg.btcpayserver.user};Host=/run/postgresql;Database=btcpaydb
socksendpoint=${cfg.tor.client.socksListenAddress} socksendpoint=${cfg.tor.client.socksListenAddress}
btcexplorerurl=http://${cfg.nbxplorer.bind}:${toString cfg.nbxplorer.port}/ btcexplorerurl=http://${cfg.nbxplorer.address}:${toString cfg.nbxplorer.port}/
btcexplorercookiefile=${cfg.nbxplorer.dataDir}/${config.services.bitcoind.makeNetworkName "Main" "RegTest"}/.cookie btcexplorercookiefile=${cfg.nbxplorer.dataDir}/${config.services.bitcoind.makeNetworkName "Main" "RegTest"}/.cookie
bind=${cfg.btcpayserver.bind} bind=${cfg.btcpayserver.address}
${optionalString (cfg.btcpayserver.rootpath != null) "rootpath=${cfg.btcpayserver.rootpath}"} ${optionalString (cfg.btcpayserver.rootpath != null) "rootpath=${cfg.btcpayserver.rootpath}"}
port=${toString cfg.btcpayserver.port} port=${toString cfg.btcpayserver.port}
'' + optionalString (cfg.btcpayserver.lightningBackend == "clightning") '' '' + optionalString (cfg.btcpayserver.lightningBackend == "clightning") ''
@ -163,7 +163,7 @@ in {
''); '');
lndConfig = lndConfig =
"btclightning=type=lnd-rest;" + "btclightning=type=lnd-rest;" +
"server=https://${toString cfg.lnd.listen}:${toString cfg.lnd.restPort}/;" + "server=https://${cfg.lnd.restAddress}:${toString cfg.lnd.restPort}/;" +
"macaroonfilepath=/run/lnd/btcpayserver.macaroon;" + "macaroonfilepath=/run/lnd/btcpayserver.macaroon;" +
"certthumbprint="; "certthumbprint=";
in let self = { in let self = {

View File

@ -6,15 +6,14 @@ let
cfg = config.services.clightning; cfg = config.services.clightning;
inherit (config) nix-bitcoin-services; inherit (config) nix-bitcoin-services;
nbPkgs = config.nix-bitcoin.pkgs; nbPkgs = config.nix-bitcoin.pkgs;
onion-chef-service = (if cfg.announce-tor then [ "onion-chef.service" ] else []);
network = config.services.bitcoind.makeNetworkName "bitcoin" "regtest"; network = config.services.bitcoind.makeNetworkName "bitcoin" "regtest";
configFile = pkgs.writeText "config" '' configFile = pkgs.writeText "config" ''
network=${network} network=${network}
bitcoin-datadir=${config.services.bitcoind.dataDir} bitcoin-datadir=${config.services.bitcoind.dataDir}
${optionalString (cfg.proxy != null) "proxy=${cfg.proxy}"} ${optionalString (cfg.proxy != null) "proxy=${cfg.proxy}"}
always-use-proxy=${if cfg.always-use-proxy then "true" else "false"} always-use-proxy=${if cfg.always-use-proxy then "true" else "false"}
bind-addr=${cfg.bind-addr}:${toString cfg.bindport} bind-addr=${cfg.address}:${toString cfg.port}
bitcoin-rpcconnect=${config.services.bitcoind.rpcbind} bitcoin-rpcconnect=${config.services.bitcoind.rpc.address}
bitcoin-rpcport=${toString config.services.bitcoind.rpc.port} bitcoin-rpcport=${toString config.services.bitcoind.rpc.port}
bitcoin-rpcuser=${config.services.bitcoind.rpc.users.public.name} bitcoin-rpcuser=${config.services.bitcoind.rpc.users.public.name}
rpc-file-mode=0660 rpc-file-mode=0660
@ -29,13 +28,15 @@ in {
If enabled, the clightning service will be installed. If enabled, the clightning service will be installed.
''; '';
}; };
autolisten = mkOption { address = mkOption {
type = types.bool; type = types.str;
default = false; default = "127.0.0.1";
description = '' description = "IP address or UNIX domain socket to listen for peer connections.";
Bind (and maybe announce) on IPv4 and IPv6 interfaces if no addr, };
bind-addr or announce-addr options are specified. port = mkOption {
''; type = types.port;
default = 9735;
description = "Port to listen for peer connections.";
}; };
proxy = mkOption { proxy = mkOption {
type = types.nullOr types.str; type = types.nullOr types.str;
@ -49,21 +50,6 @@ in {
Always use the *proxy*, even to connect to normal IP addresses (you can still connect to Unix domain sockets manually). This also disables all DNS lookups, to avoid leaking information. Always use the *proxy*, even to connect to normal IP addresses (you can still connect to Unix domain sockets manually). This also disables all DNS lookups, to avoid leaking information.
''; '';
}; };
bind-addr = mkOption {
type = nbPkgs.lib.ipv4Address;
default = "127.0.0.1";
description = "Set an IP address or UNIX domain socket to listen to";
};
bindport = mkOption {
type = types.port;
default = 9735;
description = "Set a Port to listen to locally";
};
announce-tor = mkOption {
type = types.bool;
default = false;
description = "Announce clightning Tor Hidden Service";
};
dataDir = mkOption { dataDir = mkOption {
type = types.path; type = types.path;
default = "/var/lib/clightning"; default = "/var/lib/clightning";
@ -97,11 +83,24 @@ in {
''; '';
description = "Binary to connect with the clightning instance."; description = "Binary to connect with the clightning instance.";
}; };
enforceTor = nix-bitcoin-services.enforceTor; 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.
'';
};
inherit (nix-bitcoin-services) enforceTor;
}; };
config = mkIf cfg.enable { config = mkIf cfg.enable {
services.bitcoind.enable = true; services.bitcoind = {
enable = true;
# Increase rpc thread count due to reports that lightning implementations fail
# under high bitcoind rpc load
rpc.threads = 16;
};
environment.systemPackages = [ nbPkgs.clightning (hiPrio cfg.cli) ]; environment.systemPackages = [ nbPkgs.clightning (hiPrio cfg.cli) ];
users.users.${cfg.user} = { users.users.${cfg.user} = {
@ -116,21 +115,25 @@ in {
"d '${cfg.dataDir}' 0770 ${cfg.user} ${cfg.group} - -" "d '${cfg.dataDir}' 0770 ${cfg.user} ${cfg.group} - -"
]; ];
services.onion-chef.access.clightning = if cfg.announce-tor then [ "clightning" ] else [];
systemd.services.clightning = { systemd.services.clightning = {
description = "Run clightningd"; description = "Run clightningd";
path = [ nbPkgs.bitcoind ]; path = [ nbPkgs.bitcoind ];
wantedBy = [ "multi-user.target" ]; wantedBy = [ "multi-user.target" ];
requires = [ "bitcoind.service" ] ++ onion-chef-service; requires = [ "bitcoind.service" ];
after = [ "bitcoind.service" ] ++ onion-chef-service; after = [ "bitcoind.service" ];
preStart = '' preStart = ''
cp ${configFile} ${cfg.dataDir}/config cp ${configFile} ${cfg.dataDir}/config
chown -R '${cfg.user}:${cfg.group}' '${cfg.dataDir}' chown -R '${cfg.user}:${cfg.group}' '${cfg.dataDir}'
# The RPC socket has to be removed otherwise we might have stale sockets # The RPC socket has to be removed otherwise we might have stale sockets
rm -f ${cfg.networkDir}/lightning-rpc rm -f ${cfg.networkDir}/lightning-rpc
chmod 640 ${cfg.dataDir}/config chmod 640 ${cfg.dataDir}/config
echo "bitcoin-rpcpassword=$(cat ${config.nix-bitcoin.secretsDir}/bitcoin-rpcpassword-public)" >> '${cfg.dataDir}/config' {
${optionalString cfg.announce-tor "echo announce-addr=$(cat /var/lib/onion-chef/clightning/clightning) >> '${cfg.dataDir}/config'"} echo "bitcoin-rpcpassword=$(cat ${config.nix-bitcoin.secretsDir}/bitcoin-rpcpassword-public)"
${optionalString (cfg.getPublicAddressCmd != "") ''
echo "announce-addr=$(${cfg.getPublicAddressCmd})"
''}
} >> '${cfg.dataDir}/config'
''; '';
serviceConfig = nix-bitcoin-services.defaultHardening // { serviceConfig = nix-bitcoin-services.defaultHardening // {
ExecStart = "${nbPkgs.clightning}/bin/lightningd --lightning-dir=${cfg.dataDir}"; ExecStart = "${nbPkgs.clightning}/bin/lightningd --lightning-dir=${cfg.dataDir}";

View File

@ -6,7 +6,6 @@
electrs = ./electrs.nix; electrs = ./electrs.nix;
liquid = ./liquid.nix; liquid = ./liquid.nix;
presets.secure-node = ./presets/secure-node.nix; presets.secure-node = ./presets/secure-node.nix;
nix-bitcoin-webindex = ./nix-bitcoin-webindex.nix;
spark-wallet = ./spark-wallet.nix; spark-wallet = ./spark-wallet.nix;
recurring-donations = ./recurring-donations.nix; recurring-donations = ./recurring-donations.nix;
lnd = ./lnd.nix; lnd = ./lnd.nix;

View File

@ -9,6 +9,16 @@ let
in { in {
options.services.electrs = { options.services.electrs = {
enable = mkEnableOption "electrs"; enable = mkEnableOption "electrs";
address = mkOption {
type = types.str;
default = "127.0.0.1";
description = "Address to listen for RPC connections.";
};
port = mkOption {
type = types.port;
default = 50001;
description = "RPC port.";
};
dataDir = mkOption { dataDir = mkOption {
type = types.path; type = types.path;
default = "/var/lib/electrs"; default = "/var/lib/electrs";
@ -31,16 +41,6 @@ in {
If enabled, the electrs service will sync faster on high-memory systems ( 8GB). If enabled, the electrs service will sync faster on high-memory systems ( 8GB).
''; '';
}; };
address = mkOption {
type = types.str;
default = "127.0.0.1";
description = "RPC and monitoring listening address.";
};
port = mkOption {
type = types.port;
default = 50001;
description = "RPC port.";
};
monitoringPort = mkOption { monitoringPort = mkOption {
type = types.port; type = types.port;
default = 4224; default = 4224;
@ -95,7 +95,7 @@ in {
--daemon-dir='${bitcoind.dataDir}' \ --daemon-dir='${bitcoind.dataDir}' \
--electrum-rpc-addr=${cfg.address}:${toString cfg.port} \ --electrum-rpc-addr=${cfg.address}:${toString cfg.port} \
--monitoring-addr=${cfg.address}:${toString cfg.monitoringPort} \ --monitoring-addr=${cfg.address}:${toString cfg.monitoringPort} \
--daemon-rpc-addr=${bitcoind.rpcbind}:${toString bitcoind.rpc.port} \ --daemon-rpc-addr=${bitcoind.rpc.address}:${toString bitcoind.rpc.port} \
${cfg.extraArgs} ${cfg.extraArgs}
''; '';
User = cfg.user; User = cfg.user;

View File

@ -21,7 +21,7 @@ let
[BLOCKCHAIN] [BLOCKCHAIN]
blockchain_source = bitcoin-rpc blockchain_source = bitcoin-rpc
network = ${bitcoind.network} network = ${bitcoind.network}
rpc_host = ${bitcoind.rpcbind} rpc_host = ${bitcoind.rpc.address}
rpc_port = ${toString bitcoind.rpc.port} rpc_port = ${toString bitcoind.rpc.port}
rpc_user = ${bitcoind.rpc.users.privileged.name} rpc_user = ${bitcoind.rpc.users.privileged.name}
@@RPC_PASSWORD@@ @@RPC_PASSWORD@@

View File

@ -17,7 +17,7 @@ let
tlscertpath=${secretsDir}/loop-cert tlscertpath=${secretsDir}/loop-cert
tlskeypath=${secretsDir}/loop-key tlskeypath=${secretsDir}/loop-key
lnd.host=${config.services.lnd.rpclisten}:${toString config.services.lnd.rpcPort} lnd.host=${config.services.lnd.rpcAddress}:${toString config.services.lnd.rpcPort}
lnd.macaroondir=${config.services.lnd.networkDir} lnd.macaroondir=${config.services.lnd.networkDir}
lnd.tlspath=${secretsDir}/lnd-cert lnd.tlspath=${secretsDir}/lnd-cert

View File

@ -16,23 +16,22 @@ let
${optionalString (cfg.validatepegin != null) "validatepegin=${if cfg.validatepegin then "1" else "0"}"} ${optionalString (cfg.validatepegin != null) "validatepegin=${if cfg.validatepegin then "1" else "0"}"}
# Connection options # Connection options
${optionalString cfg.listen "bind=${cfg.bind}"} ${optionalString cfg.listen "bind=${cfg.address}"}
${optionalString (cfg.port != null) "port=${toString cfg.port}"} port=${toString cfg.port}
${optionalString (cfg.proxy != null) "proxy=${cfg.proxy}"} ${optionalString (cfg.proxy != null) "proxy=${cfg.proxy}"}
listen=${if cfg.listen then "1" else "0"} listen=${if cfg.listen then "1" else "0"}
# RPC server options # RPC server options
${optionalString (cfg.rpc.port != null) "rpcport=${toString cfg.rpc.port}"} rpcport=${toString cfg.rpc.port}
${concatMapStringsSep "\n" ${concatMapStringsSep "\n"
(rpcUser: "rpcauth=${rpcUser.name}:${rpcUser.passwordHMAC}") (rpcUser: "rpcauth=${rpcUser.name}:${rpcUser.passwordHMAC}")
(attrValues cfg.rpc.users) (attrValues cfg.rpc.users)
} }
rpcbind=${cfg.rpcbind} rpcbind=${cfg.rpc.address}
rpcconnect=${cfg.rpcbind} rpcconnect=${cfg.rpc.address}
${lib.concatMapStrings (rpcallowip: "rpcallowip=${rpcallowip}\n") cfg.rpcallowip} ${lib.concatMapStrings (rpcallowip: "rpcallowip=${rpcallowip}\n") cfg.rpcallowip}
${optionalString (cfg.rpcuser != null) "rpcuser=${cfg.rpcuser}"} rpcuser=${cfg.rpcuser}
${optionalString (cfg.rpcpassword != null) "rpcpassword=${cfg.rpcpassword}"} mainchainrpchost=${config.services.bitcoind.rpc.address}
mainchainrpchost=${config.services.bitcoind.rpcbind}
mainchainrpcport=${toString config.services.bitcoind.rpc.port} mainchainrpcport=${toString config.services.bitcoind.rpc.port}
mainchainrpcuser=${config.services.bitcoind.rpc.users.public.name} mainchainrpcuser=${config.services.bitcoind.rpc.users.public.name}
@ -71,7 +70,16 @@ in {
services.liquidd = { services.liquidd = {
enable = mkEnableOption "Liquid sidechain"; enable = mkEnableOption "Liquid sidechain";
address = mkOption {
type = types.str;
default = "127.0.0.1";
description = "Address to listen for peer connections.";
};
port = mkOption {
type = types.port;
default = 7042;
description = "Override the default port on which to listen for connections.";
};
extraConfig = mkOption { extraConfig = mkOption {
type = types.lines; type = types.lines;
default = ""; default = "";
@ -88,14 +96,6 @@ in {
default = "/var/lib/liquidd"; default = "/var/lib/liquidd";
description = "The data directory for liquidd."; description = "The data directory for liquidd.";
}; };
bind = mkOption {
type = types.str;
default = "127.0.0.1";
description = ''
Bind to given address and always listen on it.
'';
};
user = mkOption { user = mkOption {
type = types.str; type = types.str;
default = "liquid"; default = "liquid";
@ -106,12 +106,16 @@ in {
default = cfg.user; default = cfg.user;
description = "The group as which to run liquidd."; description = "The group as which to run liquidd.";
}; };
rpc = { rpc = {
address = mkOption {
type = types.str;
default = "127.0.0.1";
description = "Address to listen for JSON-RPC connections.";
};
port = mkOption { port = mkOption {
type = types.nullOr types.port; type = types.port;
default = null; default = 7041;
description = "Override the default port on which to listen for JSON-RPC connections."; description = "Port to listen for JSON-RPC connections.";
}; };
users = mkOption { users = mkOption {
default = {}; default = {};
@ -125,14 +129,6 @@ in {
''; '';
}; };
}; };
rpcbind = mkOption {
type = types.str;
default = "127.0.0.1";
description = ''
Bind to given address to listen for JSON-RPC connections.
'';
};
rpcallowip = mkOption { rpcallowip = mkOption {
type = types.listOf types.str; type = types.listOf types.str;
default = [ "127.0.0.1" ]; default = [ "127.0.0.1" ];
@ -141,25 +137,15 @@ in {
''; '';
}; };
rpcuser = mkOption { rpcuser = mkOption {
type = types.nullOr types.str; type = types.str;
default = null; default = "liquidrpc";
description = "Username for JSON-RPC connections"; description = "Username for JSON-RPC connections";
}; };
rpcpassword = mkOption {
type = types.nullOr types.str;
default = null;
description = "Password for JSON-RPC connections";
};
testnet = mkOption { testnet = mkOption {
type = types.bool; type = types.bool;
default = false; default = false;
description = "Whether to use the test chain."; description = "Whether to use the test chain.";
}; };
port = mkOption {
type = types.nullOr types.port;
default = null;
description = "Override the default port on which to listen for connections.";
};
proxy = mkOption { proxy = mkOption {
type = types.nullOr types.str; type = types.nullOr types.str;
default = if cfg.enforceTor then config.services.tor.client.socksListenAddress else null; default = if cfg.enforceTor then config.services.tor.client.socksListenAddress else null;

View File

@ -8,8 +8,7 @@ let
secretsDir = config.nix-bitcoin.secretsDir; secretsDir = config.nix-bitcoin.secretsDir;
bitcoind = config.services.bitcoind; bitcoind = config.services.bitcoind;
bitcoindRpcAddress = bitcoind.rpcbind; bitcoindRpcAddress = bitcoind.rpc.address;
onion-chef-service = (if cfg.announce-tor then [ "onion-chef.service" ] else []);
networkDir = "${cfg.dataDir}/chain/bitcoin/${bitcoind.network}"; networkDir = "${cfg.dataDir}/chain/bitcoin/${bitcoind.network}";
configFile = pkgs.writeText "lnd.conf" '' configFile = pkgs.writeText "lnd.conf" ''
datadir=${cfg.dataDir} datadir=${cfg.dataDir}
@ -17,9 +16,9 @@ let
tlscertpath=${secretsDir}/lnd-cert tlscertpath=${secretsDir}/lnd-cert
tlskeypath=${secretsDir}/lnd-key tlskeypath=${secretsDir}/lnd-key
listen=${toString cfg.listen}:${toString cfg.listenPort} listen=${toString cfg.address}:${toString cfg.port}
rpclisten=${cfg.rpclisten}:${toString cfg.rpcPort} rpclisten=${cfg.rpcAddress}:${toString cfg.rpcPort}
restlisten=${cfg.restlisten}:${toString cfg.restPort} restlisten=${cfg.restAddress}:${toString cfg.restPort}
bitcoin.${bitcoind.network}=1 bitcoin.${bitcoind.network}=1
bitcoin.active=1 bitcoin.active=1
@ -55,50 +54,43 @@ in {
default = networkDir; default = networkDir;
description = "The network data directory."; description = "The network data directory.";
}; };
listen = mkOption { address = mkOption {
type = config.nix-bitcoin.pkgs.lib.ipv4Address; type = types.str;
default = "localhost"; default = "localhost";
description = "Bind to given address to listen to peer connections"; description = "Address to listen for peer connections";
}; };
listenPort = mkOption { port = mkOption {
type = types.port; type = types.port;
default = 9735; default = 9735;
description = "Bind to given port to listen to peer connections"; description = "Port to listen for peer connections";
}; };
rpclisten = mkOption { rpcAddress = mkOption {
type = types.str; type = types.str;
default = "localhost"; default = "localhost";
description = '' description = "Address to listen for RPC connections.";
Bind to given address to listen to RPC connections.
'';
};
restlisten = mkOption {
type = types.str;
default = "localhost";
description = ''
Bind to given address to listen to REST connections.
'';
}; };
rpcPort = mkOption { rpcPort = mkOption {
type = types.port; type = types.port;
default = 10009; default = 10009;
description = "Port on which to listen for gRPC connections."; description = "Port to listen for gRPC connections.";
};
restAddress = mkOption {
type = types.str;
default = "localhost";
description = ''
Address to listen for REST connections.
'';
}; };
restPort = mkOption { restPort = mkOption {
type = types.port; type = types.port;
default = 8080; default = 8080;
description = "Port on which to listen for REST connections."; description = "Port to listen for REST connections.";
}; };
tor-socks = mkOption { tor-socks = mkOption {
type = types.nullOr types.str; type = types.nullOr types.str;
default = if cfg.enforceTor then config.services.tor.client.socksListenAddress else null; default = if cfg.enforceTor then config.services.tor.client.socksListenAddress else null;
description = "Set a socks proxy to use to connect to Tor nodes"; description = "Set a socks proxy to use to connect to Tor nodes";
}; };
announce-tor = mkOption {
type = types.bool;
default = false;
description = "Announce LND Tor Hidden Service";
};
macaroons = mkOption { macaroons = mkOption {
default = {}; default = {};
type = with types; attrsOf (submodule { type = with types; attrsOf (submodule {
@ -138,13 +130,21 @@ in {
# Switch user because lnd makes datadir contents readable by user only # Switch user because lnd makes datadir contents readable by user only
'' ''
sudo -u lnd ${cfg.package}/bin/lncli \ sudo -u lnd ${cfg.package}/bin/lncli \
--rpcserver ${cfg.rpclisten}:${toString cfg.rpcPort} \ --rpcserver ${cfg.rpcAddress}:${toString cfg.rpcPort} \
--tlscertpath '${secretsDir}/lnd-cert' \ --tlscertpath '${secretsDir}/lnd-cert' \
--macaroonpath '${networkDir}/admin.macaroon' "$@" --macaroonpath '${networkDir}/admin.macaroon' "$@"
''; '';
description = "Binary to connect with the lnd instance."; description = "Binary to connect with the lnd instance.";
}; };
enforceTor = nix-bitcoin-services.enforceTor; 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.
'';
};
inherit (nix-bitcoin-services) enforceTor;
}; };
config = mkIf cfg.enable { config = mkIf cfg.enable {
@ -154,7 +154,12 @@ in {
} }
]; ];
services.bitcoind.enable = true; services.bitcoind = {
enable = true;
# Increase rpc thread count due to reports that lightning implementations fail
# under high bitcoind rpc load
rpc.threads = 16;
};
environment.systemPackages = [ cfg.package (hiPrio cfg.cli) ]; environment.systemPackages = [ cfg.package (hiPrio cfg.cli) ];
@ -167,16 +172,19 @@ in {
zmqpubrawtx = "tcp://${bitcoindRpcAddress}:28333"; zmqpubrawtx = "tcp://${bitcoindRpcAddress}:28333";
}; };
services.onion-chef.access.lnd = if cfg.announce-tor then [ "lnd" ] else [];
systemd.services.lnd = { systemd.services.lnd = {
description = "Run LND"; description = "Run LND";
wantedBy = [ "multi-user.target" ]; wantedBy = [ "multi-user.target" ];
requires = [ "bitcoind.service" ] ++ onion-chef-service; requires = [ "bitcoind.service" ];
after = [ "bitcoind.service" ] ++ onion-chef-service; after = [ "bitcoind.service" ];
preStart = '' preStart = ''
install -m600 ${configFile} '${cfg.dataDir}/lnd.conf' install -m600 ${configFile} '${cfg.dataDir}/lnd.conf'
echo "bitcoind.rpcpass=$(cat ${secretsDir}/bitcoin-rpcpassword-public)" >> '${cfg.dataDir}/lnd.conf' {
${optionalString cfg.announce-tor "echo externalip=$(cat /var/lib/onion-chef/lnd/lnd) >> '${cfg.dataDir}/lnd.conf'"} echo "bitcoind.rpcpass=$(cat ${secretsDir}/bitcoin-rpcpassword-public)"
${optionalString (cfg.getPublicAddressCmd != "") ''
echo "externalip=$(${cfg.getPublicAddressCmd})"
''}
} >> '${cfg.dataDir}/lnd.conf'
''; '';
serviceConfig = nix-bitcoin-services.defaultHardening // { serviceConfig = nix-bitcoin-services.defaultHardening // {
RuntimeDirectory = "lnd"; # Only used to store custom macaroons RuntimeDirectory = "lnd"; # Only used to store custom macaroons
@ -187,12 +195,12 @@ in {
RestartSec = "10s"; RestartSec = "10s";
ReadWritePaths = "${cfg.dataDir}"; ReadWritePaths = "${cfg.dataDir}";
ExecStartPost = let ExecStartPost = let
restUrl = "https://${cfg.restlisten}:${toString cfg.restPort}/v1"; restUrl = "https://${cfg.restAddress}:${toString cfg.restPort}/v1";
in [ in [
# Run fully privileged for secrets dir write access # Run fully privileged for secrets dir write access
"+${nix-bitcoin-services.script '' "+${nix-bitcoin-services.script ''
attempts=250 attempts=250
while ! { exec 3>/dev/tcp/${cfg.restlisten}/${toString cfg.restPort} && exec 3>&-; } &>/dev/null; do while ! { exec 3>/dev/tcp/${cfg.restAddress}/${toString cfg.restPort} && exec 3>&-; } &>/dev/null; do
((attempts-- == 0)) && { echo "lnd REST service unreachable"; exit 1; } ((attempts-- == 0)) && { echo "lnd REST service unreachable"; exit 1; }
sleep 0.1 sleep 0.1
done done
@ -234,7 +242,7 @@ in {
fi fi
# Wait until the RPC port is open # Wait until the RPC port is open
while ! { exec 3>/dev/tcp/${cfg.rpclisten}/${toString cfg.rpcPort}; } &>/dev/null; do while ! { exec 3>/dev/tcp/${cfg.rpcAddress}/${toString cfg.rpcPort}; } &>/dev/null; do
sleep 0.1 sleep 0.1
done done

View File

@ -24,9 +24,11 @@ with lib;
# Support features # Support features
./versioning.nix ./versioning.nix
./security.nix ./security.nix
./onion-addresses.nix
./onion-services.nix
./netns-isolation.nix ./netns-isolation.nix
./nodeinfo.nix
./backups.nix ./backups.nix
./onion-chef.nix
]; ];
disabledModules = [ "services/networking/bitcoind.nix" ]; disabledModules = [ "services/networking/bitcoind.nix" ];
@ -58,11 +60,11 @@ with lib;
config = { config = {
assertions = [ assertions = [
{ assertion = (config.services.lnd.enable -> ( !config.services.clightning.enable || config.services.clightning.bindport != config.services.lnd.listenPort)); { assertion = (config.services.lnd.enable -> ( !config.services.clightning.enable || config.services.clightning.port != config.services.lnd.port));
message = '' message = ''
LND and clightning can't both bind to lightning port 9735. Either LND and clightning can't both bind to lightning port 9735. Either
disable LND/clightning or change services.clightning.bindPort or disable LND/clightning or change services.clightning.bindPort or
services.lnd.listenPort to a port other than 9735. services.lnd.port to a port other than 9735.
''; '';
} }
]; ];

View File

@ -245,26 +245,26 @@ in {
}; };
services.bitcoind = { services.bitcoind = {
bind = netns.bitcoind.address; address = netns.bitcoind.address;
rpcbind = netns.bitcoind.address; rpc.address = netns.bitcoind.address;
rpcallowip = [ rpc.allowip = [
bridgeIp # For operator user bridgeIp # For operator user
netns.bitcoind.address netns.bitcoind.address
] ++ map (n: netns.${n}.address) netns.bitcoind.availableNetns; ] ++ map (n: netns.${n}.address) netns.bitcoind.availableNetns;
}; };
systemd.services.bitcoind-import-banlist.serviceConfig.NetworkNamespacePath = "/var/run/netns/nb-bitcoind"; systemd.services.bitcoind-import-banlist.serviceConfig.NetworkNamespacePath = "/var/run/netns/nb-bitcoind";
services.clightning.bind-addr = netns.clightning.address; services.clightning.address = netns.clightning.address;
services.lnd = { services.lnd = {
listen = netns.lnd.address; address = netns.lnd.address;
rpclisten = netns.lnd.address; rpcAddress = netns.lnd.address;
restlisten = netns.lnd.address; restAddress = netns.lnd.address;
}; };
services.liquidd = { services.liquidd = {
bind = netns.liquidd.address; address = netns.liquidd.address;
rpcbind = netns.liquidd.address; rpc.address = netns.liquidd.address;
rpcallowip = [ rpcallowip = [
bridgeIp # For operator user bridgeIp # For operator user
netns.liquidd.address netns.liquidd.address
@ -274,14 +274,14 @@ in {
services.electrs.address = netns.electrs.address; services.electrs.address = netns.electrs.address;
services.spark-wallet = { services.spark-wallet = {
host = netns.spark-wallet.address; address = netns.spark-wallet.address;
extraArgs = "--no-tls"; extraArgs = "--no-tls";
}; };
services.lightning-loop.rpcAddress = netns.lightning-loop.address; services.lightning-loop.rpcAddress = netns.lightning-loop.address;
services.nbxplorer.bind = netns.nbxplorer.address; services.nbxplorer.address = netns.nbxplorer.address;
services.btcpayserver.bind = netns.btcpayserver.address; services.btcpayserver.address = netns.btcpayserver.address;
services.joinmarket.cliExec = mkCliExec "joinmarket"; services.joinmarket.cliExec = mkCliExec "joinmarket";
systemd.services.joinmarket-yieldgenerator.serviceConfig.NetworkNamespacePath = "/var/run/netns/nb-joinmarket"; systemd.services.joinmarket-yieldgenerator.serviceConfig.NetworkNamespacePath = "/var/run/netns/nb-joinmarket";

View File

@ -1,105 +0,0 @@
{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.services.nix-bitcoin-webindex;
inherit (config) nix-bitcoin-services;
indexFile = pkgs.writeText "index.html" ''
<html>
<body>
<p>
<h1>
nix-bitcoin
</h1>
</p>
<p>
<h3>
lightning node: CLIGHTNING_ID
</h3>
</p>
</body>
</html>
'';
createWebIndex = pkgs.writeText "make-index.sh" ''
set -e
cp ${indexFile} /var/www/index.html
chown -R nginx:nginx /var/www/
nodeinfo
. <(nodeinfo)
sed -i "s/CLIGHTNING_ID/$CLIGHTNING_ID/g" /var/www/index.html
'';
in {
options.services.nix-bitcoin-webindex = {
enable = mkOption {
type = types.bool;
default = false;
description = ''
If enabled, the webindex service will be installed.
'';
};
host = mkOption {
type = types.str;
default = if config.nix-bitcoin.netns-isolation.enable then
config.nix-bitcoin.netns-isolation.netns.nginx.address
else
"localhost";
description = "HTTP server listen address.";
};
enforceTor = nix-bitcoin-services.enforceTor;
};
config = mkIf cfg.enable {
assertions = [
{ assertion = config.services.clightning.enable;
message = "nix-bitcoin-webindex requires clightning.";
}
];
systemd.tmpfiles.rules = [
"d /var/www 0755 nginx nginx - -"
];
services.nginx = {
enable = true;
virtualHosts."_" = {
root = "/var/www";
};
};
services.tor.hiddenServices.nginx = {
map = [{
port = 80; toHost = cfg.host;
} {
port = 443; toHost = cfg.host;
}];
version = 3;
};
# create-web-index
systemd.services.create-web-index = {
description = "Get node info";
wantedBy = [ "multi-user.target" ];
path = with pkgs; [
config.programs.nodeinfo
jq
sudo
] ++ optional config.services.lnd.enable config.services.lnd.cli
++ optional config.services.clightning.enable config.services.clightning.cli;
serviceConfig = nix-bitcoin-services.defaultHardening // {
ExecStart="${pkgs.bash}/bin/bash ${createWebIndex}";
User = "root";
Type = "simple";
RemainAfterExit="yes";
Restart = "on-failure";
RestartSec = "10s";
PrivateNetwork = "true"; # This service needs no network access
PrivateUsers = "false";
ReadWritePaths = "/var/www";
CapabilityBoundingSet = "CAP_SETUID CAP_SETGID CAP_SETPCAP CAP_SYS_ADMIN CAP_CHOWN CAP_FSETID CAP_SETFCAP CAP_DAC_OVERRIDE CAP_DAC_READ_SEARCH CAP_FOWNER CAP_IPC_OWNER";
} // (if cfg.enforceTor
then nix-bitcoin-services.allowTor
else nix-bitcoin-services.allowAnyIP
);
};
};
}

View File

@ -1,74 +1,117 @@
{ config, lib, pkgs, ... }: { config, lib, pkgs, ... }:
with lib; with lib;
let let
operatorName = config.nix-bitcoin.operator.name; cfg = config.nix-bitcoin.nodeinfo;
script = pkgs.writeScriptBin "nodeinfo" ''
set -eo pipefail
BITCOIND_ONION="$(cat /var/lib/onion-chef/${operatorName}/bitcoind)" # Services included in the output
echo BITCOIND_ONION="$BITCOIND_ONION" services = {
bitcoind = mkInfo "";
if systemctl is-active --quiet clightning; then clightning = mkInfo ''
CLIGHTNING_NODEID=$(lightning-cli getinfo | jq -r '.id') info["nodeid"] = shell("lightning-cli getinfo | jq -r '.id'")
CLIGHTNING_ONION="$(cat /var/lib/onion-chef/${operatorName}/clightning)" if 'onion_address' in info:
CLIGHTNING_ID="$CLIGHTNING_NODEID@$CLIGHTNING_ONION:9735" info["id"] = f"{info['nodeid']}@{info['onion_address']}"
echo CLIGHTNING_NODEID="$CLIGHTNING_NODEID"
echo CLIGHTNING_ONION="$CLIGHTNING_ONION"
echo CLIGHTNING_ID="$CLIGHTNING_ID"
fi
if systemctl is-active --quiet lnd; then
LND_NODEID=$(lncli getinfo | jq -r '.uris[0]')
echo LND_NODEID="$LND_NODEID"
fi
NGINX_ONION_FILE=/var/lib/onion-chef/${operatorName}/nginx
if [ -e "$NGINX_ONION_FILE" ]; then
NGINX_ONION="$(cat $NGINX_ONION_FILE)"
echo NGINX_ONION="$NGINX_ONION"
fi
LIQUIDD_ONION_FILE=/var/lib/onion-chef/${operatorName}/liquidd
if [ -e "$LIQUIDD_ONION_FILE" ]; then
LIQUIDD_ONION="$(cat $LIQUIDD_ONION_FILE)"
echo LIQUIDD_ONION="$LIQUIDD_ONION"
fi
SPARKWALLET_ONION_FILE=/var/lib/onion-chef/${operatorName}/spark-wallet
if [ -e "$SPARKWALLET_ONION_FILE" ]; then
SPARKWALLET_ONION="$(cat $SPARKWALLET_ONION_FILE)"
echo SPARKWALLET_ONION="http://$SPARKWALLET_ONION"
fi
ELECTRS_ONION_FILE=/var/lib/onion-chef/${operatorName}/electrs
if [ -e "$ELECTRS_ONION_FILE" ]; then
ELECTRS_ONION="$(cat $ELECTRS_ONION_FILE)"
echo ELECTRS_ONION="$ELECTRS_ONION"
fi
BTCPAYSERVER_ONION_FILE=/var/lib/onion-chef/${operatorName}/btcpayserver
if [ -e "$BTCPAYSERVER_ONION_FILE" ]; then
BTCPAYSERVER_ONION="$(cat $BTCPAYSERVER_ONION_FILE)"
echo BTCPAYSERVER_ONION="$BTCPAYSERVER_ONION"
fi
SSHD_ONION_FILE=/var/lib/onion-chef/${operatorName}/sshd
if [ -e "$SSHD_ONION_FILE" ]; then
SSHD_ONION="$(cat $SSHD_ONION_FILE)"
echo SSHD_ONION="$SSHD_ONION"
fi
''; '';
lnd = mkInfo ''
info["nodeid"] = shell("lightning-cli getinfo | jq -r '.id'")
'';
electrs = mkInfo "";
spark-wallet = mkInfo "";
btcpayserver = mkInfo "";
liquidd = mkInfo "";
# Only add sshd when it has an onion service
sshd = name: cfg: mkIfOnionPort "sshd" (onionPort: ''
add_service("sshd", """set_onion_address(info, "sshd", ${onionPort})""")
'');
};
script = pkgs.writeScriptBin "nodeinfo" ''
#!${pkgs.python3}/bin/python
import json
import subprocess
from collections import OrderedDict
def success(*args):
return subprocess.call(args, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) == 0
def is_active(unit):
return success("systemctl", "is-active", "--quiet", unit)
def is_enabled(unit):
return success("systemctl", "is-enabled", "--quiet", unit)
def cmd(*args):
return subprocess.run(args, stdout=subprocess.PIPE).stdout.decode('utf-8')
def shell(*args):
return cmd("bash", "-c", *args).strip()
infos = OrderedDict()
operator = "${config.nix-bitcoin.operator.name}"
def set_onion_address(info, name, port):
path = f"/var/lib/onion-addresses/{operator}/{name}"
try:
with open(path, "r") as f:
onion_address = f.read().strip()
except OSError:
print(f"error reading file {path}", file=sys.stderr)
return
info["onion_address"] = f"{onion_address}:{port}"
def add_service(service, make_info):
if not is_active(service):
infos[service] = "service is not running"
else:
info = OrderedDict()
exec(make_info, globals(), locals())
infos[service] = info
if is_enabled("onion-adresses") and not is_active("onion-adresses"):
print("error: service 'onion-adresses' is not running")
exit(1)
${concatStrings infos}
print(json.dumps(infos, indent=2))
'';
infos = map (service:
let cfg = config.services.${service};
in optionalString cfg.enable (services.${service} service cfg)
) (builtins.attrNames services);
mkInfo = extraCode: name: cfg:
''
add_service("${name}", """
info["local_address"] = "${cfg.address}:${toString cfg.port}"
'' + mkIfOnionPort name (onionPort: ''
set_onion_address(info, "${name}", ${onionPort})
'') + extraCode + ''
""")
'';
mkIfOnionPort = name: fn:
if hiddenServices ? ${name} then
fn (toString (builtins.elemAt hiddenServices.${name}.map 0).port)
else
"";
inherit (config.services.tor) hiddenServices;
in { in {
options = { options = {
programs.nodeinfo = mkOption { nix-bitcoin.nodeinfo = {
enable = mkEnableOption "nodeinfo";
program = mkOption {
readOnly = true; readOnly = true;
default = script; default = script;
}; };
}; };
};
config = { config = {
environment.systemPackages = [ script ]; environment.systemPackages = optional cfg.enable script;
}; };
} }

View File

@ -0,0 +1,28 @@
{ lib, ... }:
with lib;
let
mkRenamedAnnounceTorOption = service:
# use mkRemovedOptionModule because mkRenamedOptionModule fails with an infinite recursion error
mkRemovedOptionModule [ "services" service "announce-tor" ] ''
Use option `nix-bitcoin.onionServices.${service}.public` instead.
'';
in {
imports = [
(mkRenamedOptionModule [ "services" "bitcoind" "bind" ] [ "services" "bitcoind" "address" ])
(mkRenamedOptionModule [ "services" "bitcoind" "rpcallowip" ] [ "services" "bitcoind" "rpc" "allowip" ])
(mkRenamedOptionModule [ "services" "bitcoind" "rpcthreads" ] [ "services" "bitcoind" "rpc" "threads" ])
(mkRenamedOptionModule [ "services" "clightning" "bind-addr" ] [ "services" "clightning" "address" ])
(mkRenamedOptionModule [ "services" "clightning" "bindport" ] [ "services" "clightning" "port" ])
(mkRenamedOptionModule [ "services" "spark-wallet" "host" ] [ "services" "spark-wallet" "address" ])
(mkRenamedOptionModule [ "services" "lnd" "rpclisten" ] [ "services" "lnd" "rpcAddress" ])
(mkRenamedOptionModule [ "services" "lnd" "listen" ] [ "services" "lnd" "address" ])
(mkRenamedOptionModule [ "services" "lnd" "listenPort" ] [ "services" "lnd" "port" ])
(mkRenamedOptionModule [ "services" "btcpayserver" "bind" ] [ "services" "btcpayserver" "address" ])
(mkRenamedOptionModule [ "services" "liquidd" "bind" ] [ "services" "liquidd" "address" ])
(mkRenamedOptionModule [ "services" "liquidd" "rpcbind" ] [ "services" "liquidd" "rpc" "address" ])
(mkRenamedAnnounceTorOption "clightning")
(mkRenamedAnnounceTorOption "lnd")
];
}

View File

@ -0,0 +1,76 @@
# This module enables unprivileged users to read onion addresses.
# By default, onion addresses in /var/lib/tor/onion are only readable by the
# tor user.
# The included service copies onion addresses to /var/lib/onion-addresses/<user>/
# and sets permissions according to option 'access'.
{ config, lib, ... }:
with lib;
let
cfg = config.nix-bitcoin.onionAddresses;
inherit (config) nix-bitcoin-services;
in {
options.nix-bitcoin.onionAddresses = {
access = mkOption {
type = with types; attrsOf (listOf str);
default = {};
description = ''
This option controls who is allowed to access onion addresses.
For example, the following allows user 'myuser' to access bitcoind
and clightning onion addresses:
{
"myuser" = [ "bitcoind" "clightning" ];
};
The onion hostnames can then be read from
/var/lib/onion-addresses/myuser.
'';
};
dataDir = mkOption {
readOnly = true;
default = "/var/lib/onion-addresses";
};
};
config = mkIf (cfg.access != {}) {
systemd.services.onion-addresses = {
wantedBy = [ "tor.service" ];
bindsTo = [ "tor.service" ];
after = [ "tor.service" ];
serviceConfig = nix-bitcoin-services.defaultHardening // {
Type = "oneshot";
RemainAfterExit = true;
StateDirectory = "onion-addresses";
PrivateNetwork = "true"; # This service needs no network access
PrivateUsers = "false";
CapabilityBoundingSet = "CAP_CHOWN CAP_FSETID CAP_SETFCAP CAP_DAC_OVERRIDE CAP_DAC_READ_SEARCH CAP_FOWNER CAP_IPC_OWNER";
};
script = ''
# Wait until tor is up
until [[ -e /var/lib/tor/state ]]; do sleep 0.1; done
cd ${cfg.dataDir}
rm -rf *
${concatMapStrings
(user: ''
mkdir -p -m 0700 ${user}
chown ${user} ${user}
${concatMapStrings
(service: ''
onionFile=/var/lib/tor/onion/${service}/hostname
if [[ -e $onionFile ]]; then
cp $onionFile ${user}/${service}
chown ${user} ${user}/${service}
fi
'')
cfg.access.${user}
}
'')
(builtins.attrNames cfg.access)
}
'';
};
};
}

View File

@ -1,90 +0,0 @@
# The onion chef module allows unprivileged users to read onion hostnames.
# By default the onion hostnames in /var/lib/tor/onion are only readable by the
# tor user. The onion chef copies the onion hostnames into into
# /var/lib/onion-chef and sets permissions according to the access option.
{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.services.onion-chef;
inherit (config) nix-bitcoin-services;
dataDir = "/var/lib/onion-chef/";
onion-chef-script = pkgs.writeScript "onion-chef.sh" ''
# wait until tor is up
until ls -l /var/lib/tor/state; do sleep 1; done
cd ${dataDir}
# Create directory for every user and set permissions
${ builtins.foldl'
(x: user: x +
''
mkdir -p -m 0700 ${user}
chown ${user} ${user}
# Copy onion hostnames into the user's directory
${ builtins.foldl'
(x: onion: x +
''
ONION_FILE=/var/lib/tor/onion/${onion}/hostname
if [ -e "$ONION_FILE" ]; then
cp $ONION_FILE ${user}/${onion}
chown ${user} ${user}/${onion}
fi
'')
""
(builtins.getAttr user cfg.access)
}
'')
""
(builtins.attrNames cfg.access)
}
'';
in {
options.services.onion-chef = {
enable = mkOption {
type = types.bool;
default = false;
description = ''
If enabled, the onion-chef service will be installed.
'';
};
access = mkOption {
type = types.attrs;
default = {};
description = ''
This option controls who is allowed to access onion hostnames. For
example the following allows the user operator to access the bitcoind
and clightning onion.
{
"operator" = [ "bitcoind" "clightning" ];
};
The onion hostnames can then be read from
/var/lib/onion-chef/<user>.
'';
};
};
config = mkIf cfg.enable {
systemd.tmpfiles.rules = [
"d '${dataDir}' 0755 root root - -"
];
systemd.services.onion-chef = {
description = "Run onion-chef";
wantedBy = [ "tor.service" ];
bindsTo = [ "tor.service" ];
after = [ "tor.service" ];
serviceConfig = nix-bitcoin-services.defaultHardening // {
ExecStart = "${pkgs.bash}/bin/bash ${onion-chef-script}";
Type = "oneshot";
RemainAfterExit = true;
PrivateNetwork = "true"; # This service needs no network access
PrivateUsers = "false";
ReadWritePaths = "${dataDir}";
CapabilityBoundingSet = "CAP_CHOWN CAP_FSETID CAP_SETFCAP CAP_DAC_OVERRIDE CAP_DAC_READ_SEARCH CAP_FOWNER CAP_IPC_OWNER";
};
};
};
}

121
modules/onion-services.nix Normal file
View File

@ -0,0 +1,121 @@
# This module creates onion-services for NixOS services.
# An onion service can be enabled for every service that defines
# options 'address', 'port' and optionally 'getPublicAddressCmd'.
#
# See it in use at ./presets/enable-tor.nix
{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.nix-bitcoin.onionServices;
services = builtins.attrNames cfg;
activeServices = builtins.filter (service:
config.services.${service}.enable && cfg.${service}.enable
) services;
publicServices = builtins.filter (service: cfg.${service}.public) activeServices;
in {
options.nix-bitcoin.onionServices = mkOption {
default = {};
type = with types; attrsOf (submodule (
{ config, ... }: {
options = {
enable = mkOption {
type = types.bool;
default = config.public;
description = ''
Create an onion service for the given service.
The service must define options 'address' and 'port'.
'';
};
public = mkOption {
type = types.bool;
default = false;
description = ''
Make the onion address accessible to the service.
If enabled, the onion service is automatically enabled.
Only available for services that define option `getPublicAddressCmd`.
'';
};
externalPort = mkOption {
type = types.nullOr types.port;
default = null;
description = "Override the external port of the onion service.";
};
};
}
));
};
config = mkMerge [
(mkIf (cfg != {}) {
# Define hidden services
services.tor = {
enable = true;
hiddenServices = genAttrs activeServices (name:
let
service = config.services.${name};
inherit (cfg.${name}) externalPort;
in {
map = [{
port = if externalPort != null then externalPort else service.port;
toPort = service.port;
toHost = if service.address == "0.0.0.0" then "127.0.0.1" else service.address;
}];
version = 3;
}
);
};
# Enable public services to access their own onion addresses
nix-bitcoin.onionAddresses.access = (
genAttrs publicServices singleton
) // {
# Allow the operator user to access onion addresses for all active services
${config.nix-bitcoin.operator.name} = mkIf config.nix-bitcoin.operator.enable activeServices;
};
systemd.services = let
onionAddresses = [ "onion-addresses.service" ];
in genAttrs publicServices (service: {
requires = onionAddresses;
after = onionAddresses;
});
})
# Set getPublicAddressCmd for public services
{
services = let
# publicServices' doesn't depend on config.services.*.enable,
# so we can use it to define config.services without causing infinite recursion
publicServices' = builtins.filter (service:
let srv = cfg.${service};
in srv.public && srv.enable
) services;
in genAttrs publicServices' (service: {
getPublicAddressCmd = "cat ${config.nix-bitcoin.onionAddresses.dataDir}/${service}/${service}";
});
}
# Set sensible defaults for some services
{
nix-bitcoin.onionServices = {
spark-wallet = {
externalPort = 80;
# Enable 'public' by default, but don't auto-enable the onion service.
# When the onion service is enabled, 'public' lets spark-wallet generate
# a QR code for accessing the web interface.
public = true;
# Low priority so we can override this with mkDefault in ./presets/enable-tor.nix
enable = mkOverride 1400 false;
};
btcpayserver = {
externalPort = 80;
};
};
}
];
}

View File

@ -0,0 +1,32 @@
{ lib, ... }:
let
defaultTrue = lib.mkDefault true;
in {
services.tor = {
enable = true;
client.enable = true;
};
# Use Tor for all outgoing connections
services = {
bitcoind.enforceTor = true;
clightning.enforceTor = true;
lnd.enforceTor = true;
lightning-loop.enforceTor = true;
liquidd.enforceTor = true;
electrs.enforceTor = true;
# disable Tor enforcement until btcpayserver can fetch rates over Tor
# btcpayserver.enforceTor = true;
nbxplorer.enforceTor = true;
spark-wallet.enforceTor = true;
recurring-donations.enforceTor = true;
};
# Add onion services for incoming connections
nix-bitcoin.onionServices = {
bitcoind.enable = defaultTrue;
liquidd.enable = defaultTrue;
electrs.enable = defaultTrue;
spark-wallet.enable = defaultTrue;
};
}

View File

@ -14,23 +14,9 @@ let
in { in {
imports = [ imports = [
../modules.nix ../modules.nix
../nodeinfo.nix ./enable-tor.nix
../nix-bitcoin-webindex.nix
]; ];
options = {
services.clightning.onionport = mkOption {
type = types.port;
default = 9735;
description = "Port on which to listen for tor client connections.";
};
services.lnd.onionport = mkOption {
type = types.ints.u16;
default = 9735;
description = "Port on which to listen for tor client connections.";
};
};
config = { config = {
# For backwards compatibility only # For backwards compatibility only
nix-bitcoin.secretsDir = mkDefault "/secrets"; nix-bitcoin.secretsDir = mkDefault "/secrets";
@ -39,99 +25,36 @@ in {
nix-bitcoin.security.hideProcessInformation = true; nix-bitcoin.security.hideProcessInformation = true;
# Tor environment.systemPackages = with pkgs; [
services.tor = { jq
enable = true; ];
client.enable = true;
hiddenServices.sshd = mkHiddenService { port = 22; }; # sshd
}; services.tor.hiddenServices.sshd = mkHiddenService { port = 22; };
nix-bitcoin.onionAddresses.access.${operatorName} = [ "sshd" ];
# bitcoind
services.bitcoind = { services.bitcoind = {
enable = true; enable = true;
listen = true; listen = true;
dataDirReadableByGroup = mkIf cfg.electrs.high-memory true; dataDirReadableByGroup = mkIf cfg.electrs.high-memory true;
enforceTor = true;
port = 8333;
assumevalid = "00000000000000000000e5abc3a74fe27dc0ead9c70ea1deb456f11c15fd7bc6"; assumevalid = "00000000000000000000e5abc3a74fe27dc0ead9c70ea1deb456f11c15fd7bc6";
addnodes = [ "ecoc5q34tmbq54wl.onion" ]; addnodes = [ "ecoc5q34tmbq54wl.onion" ];
discover = false; discover = false;
addresstype = "bech32"; addresstype = "bech32";
dbCache = 1000; dbCache = 1000;
# higher rpcthread count due to reports that lightning implementations fail
# under high bitcoind rpc load
rpcthreads = 16;
}; };
services.tor.hiddenServices.bitcoind = mkHiddenService { port = cfg.bitcoind.port; toHost = cfg.bitcoind.bind; };
# clightning
services.clightning.enforceTor = true;
services.tor.hiddenServices.clightning = mkIf cfg.clightning.enable (mkHiddenService {
port = cfg.clightning.onionport;
toHost = cfg.clightning.bind-addr;
toPort = cfg.clightning.bindport;
});
# lnd
services.lnd.enforceTor = true;
services.tor.hiddenServices.lnd = mkIf cfg.lnd.enable (mkHiddenService { port = cfg.lnd.onionport; toHost = cfg.lnd.listen; toPort = cfg.lnd.listenPort; });
# lightning-loop
services.lightning-loop.enforceTor = true;
# liquidd
services.liquidd = { services.liquidd = {
rpcuser = "liquidrpc";
prune = 1000; prune = 1000;
validatepegin = true; validatepegin = true;
listen = true; listen = true;
enforceTor = true;
port = 7042;
};
services.tor.hiddenServices.liquidd = mkIf cfg.liquidd.enable (mkHiddenService { port = cfg.liquidd.port; toHost = cfg.liquidd.bind; });
# electrs
services.electrs = {
port = 50001;
enforceTor = true;
};
services.tor.hiddenServices.electrs = mkIf cfg.electrs.enable (mkHiddenService {
port = cfg.electrs.port; toHost = cfg.electrs.address;
});
# btcpayserver
# disable tor enforcement until btcpayserver can fetch rates over Tor
services.btcpayserver.enforceTor = false;
services.nbxplorer.enforceTor = true;
services.tor.hiddenServices.btcpayserver = mkIf cfg.btcpayserver.enable (mkHiddenService { port = 80; toPort = 23000; toHost = cfg.btcpayserver.bind; });
services.spark-wallet = {
onion-service = true;
enforceTor = true;
}; };
services.recurring-donations.enforceTor = true; nix-bitcoin.nodeinfo.enable = true;
services.nix-bitcoin-webindex.enforceTor = true; services.backups.frequency = "daily";
# Backups
services.backups = {
program = "duplicity";
frequency = "daily";
};
environment.systemPackages = with pkgs; [
tor
jq
qrencode
];
services.onion-chef = {
enable = true;
access.${operatorName} = [ "bitcoind" "clightning" "nginx" "liquidd" "spark-wallet" "electrs" "btcpayserver" "sshd" ];
};
# operator
nix-bitcoin.operator.enable = true; nix-bitcoin.operator.enable = true;
users.users.${operatorName} = { users.users.${operatorName} = {
openssh.authorizedKeys.keys = config.users.users.root.openssh.authorizedKeys.keys; openssh.authorizedKeys.keys = config.users.users.root.openssh.authorizedKeys.keys;

View File

@ -5,18 +5,17 @@ with lib;
let let
cfg = config.services.spark-wallet; cfg = config.services.spark-wallet;
inherit (config) nix-bitcoin-services; inherit (config) nix-bitcoin-services;
onion-chef-service = (if cfg.onion-service then [ "onion-chef.service" ] else []);
# Use wasabi rate provider because the default (bitstamp) doesn't accept # Use wasabi rate provider because the default (bitstamp) doesn't accept
# connections through Tor # connections through Tor
torRateProvider = "--rate-provider wasabi --proxy socks5h://${config.services.tor.client.socksListenAddress}"; torRateProvider = "--rate-provider wasabi --proxy socks5h://${config.services.tor.client.socksListenAddress}";
startScript = '' startScript = ''
${optionalString cfg.onion-service '' ${optionalString (cfg.getPublicAddressCmd != "") ''
publicURL="--public-url http://$(cat /var/lib/onion-chef/spark-wallet/spark-wallet)" publicURL="--public-url http://$(${cfg.getPublicAddressCmd})"
''} ''}
exec ${config.nix-bitcoin.pkgs.spark-wallet}/bin/spark-wallet \ exec ${config.nix-bitcoin.pkgs.spark-wallet}/bin/spark-wallet \
--ln-path '${config.services.clightning.networkDir}' \ --ln-path '${config.services.clightning.networkDir}' \
--host ${cfg.host} \ --host ${cfg.address} --port ${toString cfg.port} \
--config '${config.nix-bitcoin.secretsDir}/spark-wallet-login' \ --config '${config.nix-bitcoin.secretsDir}/spark-wallet-login' \
${optionalString cfg.enforceTor torRateProvider} \ ${optionalString cfg.enforceTor torRateProvider} \
$publicURL \ $publicURL \
@ -31,24 +30,31 @@ in {
If enabled, the spark-wallet service will be installed. If enabled, the spark-wallet service will be installed.
''; '';
}; };
host = mkOption { address = mkOption {
type = types.str; type = types.str;
default = "localhost"; default = "localhost";
description = "http(s) server listen address."; description = "http(s) server address.";
}; };
onion-service = mkOption { port = mkOption {
type = types.bool; type = types.port;
default = false; default = 9737;
description = '' description = "http(s) server port.";
"If enabled, configures spark-wallet to be reachable through an onion service.";
'';
}; };
extraArgs = mkOption { extraArgs = mkOption {
type = types.separatedString " "; type = types.separatedString " ";
default = ""; default = "";
description = "Extra command line arguments passed to spark-wallet."; description = "Extra command line arguments passed to spark-wallet.";
}; };
enforceTor = nix-bitcoin-services.enforceTor; getPublicAddressCmd = mkOption {
type = types.str;
default = "";
description = ''
Bash expression which outputs the public service address.
If set, spark-wallet prints a QR code to the systemd journal which
encodes an URL for accessing the web interface.
'';
};
inherit (nix-bitcoin-services) enforceTor;
}; };
config = mkIf cfg.enable { config = mkIf cfg.enable {
@ -61,25 +67,16 @@ in {
}; };
users.groups.spark-wallet = {}; users.groups.spark-wallet = {};
services.tor.hiddenServices.spark-wallet = mkIf cfg.onion-service {
map = [{
port = 80; toPort = 9737; toHost = cfg.host;
}];
version = 3;
};
services.onion-chef.enable = cfg.onion-service;
services.onion-chef.access.spark-wallet = if cfg.onion-service then [ "spark-wallet" ] else [];
systemd.services.spark-wallet = { systemd.services.spark-wallet = {
description = "Run spark-wallet"; description = "Run spark-wallet";
wantedBy = [ "multi-user.target" ]; wantedBy = [ "multi-user.target" ];
requires = [ "clightning.service" ] ++ onion-chef-service; requires = [ "clightning.service" ];
after = [ "clightning.service" ] ++ onion-chef-service; after = [ "clightning.service" ];
script = startScript; script = startScript;
serviceConfig = nix-bitcoin-services.defaultHardening // { serviceConfig = nix-bitcoin-services.defaultHardening // {
User = "spark-wallet"; User = "spark-wallet";
Restart = "on-failure"; Restart = "on-failure";
RestartSec = "10s"; RestartSec = "10s";
ReadWritePaths = mkIf cfg.onion-service "/var/lib/onion-chef";
} // (if cfg.enforceTor } // (if cfg.enforceTor
then nix-bitcoin-services.allowTor then nix-bitcoin-services.allowTor
else nix-bitcoin-services.allowAnyIP) else nix-bitcoin-services.allowAnyIP)

View File

@ -5,7 +5,19 @@ let
version = config.nix-bitcoin.configVersion; version = config.nix-bitcoin.configVersion;
# Sorted by increasing version numbers # Sorted by increasing version numbers
changes = [ changes = let
mkOnionServiceChange = service: {
version = "0.0.30";
condition = config.services.${service}.enable;
message = ''
The onion service for ${service} has been disabled in the default
configuration (`secure-node.nix`).
To enable the onion service, add the following to your configuration:
nix-bitcon.onionServices.${service}.enable = true;
'';
};
in [
{ {
version = "0.0.26"; version = "0.0.26";
condition = config.services.joinmarket.enable; condition = config.services.joinmarket.enable;
@ -54,6 +66,9 @@ let
https://github.com/JoinMarket-Org/joinmarket-clientserver/blob/v0.8.0/docs/NATIVE-SEGWIT-UPGRADE.md https://github.com/JoinMarket-Org/joinmarket-clientserver/blob/v0.8.0/docs/NATIVE-SEGWIT-UPGRADE.md
''; '';
} }
(mkOnionServiceChange "clightning")
(mkOnionServiceChange "lnd")
(mkOnionServiceChange "btcpayserver")
]; ];
incompatibleChanges = optionals incompatibleChanges = optionals
@ -76,6 +91,10 @@ let
lastChange = builtins.elemAt changes (builtins.length changes - 1); lastChange = builtins.elemAt changes (builtins.length changes - 1);
in in
{ {
imports = [
./obsolete-options.nix
];
options = { options = {
nix-bitcoin.configVersion = mkOption { nix-bitcoin.configVersion = mkOption {
type = with types; nullOr str; type = with types; nullOr str;
@ -93,6 +112,6 @@ in
config = { config = {
# Force evaluation. An actual option value is never assigned # Force evaluation. An actual option value is never assigned
system.extraDependencies = optional (builtins.length incompatibleChanges > 0) (builtins.throw errorMsg); system = optionalAttrs (builtins.length incompatibleChanges > 0) (builtins.throw errorMsg);
}; };
} }

View File

@ -20,7 +20,5 @@ let self = {
pinned = import ./pinned.nix; pinned = import ./pinned.nix;
lib = import ./lib.nix { inherit (pkgs) lib; };
modulesPkgs = self // self.pinned; modulesPkgs = self // self.pinned;
}; in self }; in self

View File

@ -1,5 +0,0 @@
{ lib }:
{
# An address type that checks that there's no port
ipv4Address = lib.types.addCheck lib.types.str (s: builtins.length (builtins.split ":" s) == 1);
}

View File

@ -44,7 +44,7 @@ let testEnv = rec {
tests.spark-wallet = cfg.spark-wallet.enable; tests.spark-wallet = cfg.spark-wallet.enable;
tests.lnd = cfg.lnd.enable; tests.lnd = cfg.lnd.enable;
services.lnd.listenPort = 9736; services.lnd.port = 9736;
tests.lightning-loop = cfg.lightning-loop.enable; tests.lightning-loop = cfg.lightning-loop.enable;
@ -68,6 +68,8 @@ let testEnv = rec {
''; '';
}; };
tests.nodeinfo = config.nix-bitcoin.nodeinfo.enable;
tests.backups = cfg.backups.enable; tests.backups = cfg.backups.enable;
# To test that unused secrets are made inaccessible by 'setup-secrets' # To test that unused secrets are made inaccessible by 'setup-secrets'
@ -119,6 +121,8 @@ let testEnv = rec {
services.joinmarket.enable = true; services.joinmarket.enable = true;
services.backups.enable = true; services.backups.enable = true;
nix-bitcoin.nodeinfo.enable = true;
services.hardware-wallets = { services.hardware-wallets = {
trezor = true; trezor = true;
ledger = true; ledger = true;
@ -130,7 +134,6 @@ let testEnv = rec {
scenarios.full scenarios.full
../modules/presets/secure-node.nix ../modules/presets/secure-node.nix
]; ];
services.nix-bitcoin-webindex.enable = true;
tests.secure-node = true; tests.secure-node = true;
tests.banlist-and-restart = true; tests.banlist-and-restart = true;

View File

@ -216,17 +216,19 @@ def _():
) )
@test("nodeinfo")
def _():
status, _ = machine.execute("systemctl is-enabled --quiet onion-addresses 2> /dev/null")
if status == 0:
machine.wait_for_unit("onion-addresses")
json_info = succeed("sudo -u operator nodeinfo")
info = json.loads(json_info)
assert info["bitcoind"]["local_address"]
@test("secure-node") @test("secure-node")
def _(): def _():
assert_running("onion-chef") assert_running("onion-addresses")
# FIXME: use 'wait_for_unit' because 'create-web-index' always fails during startup due
# to incomplete unit dependencies.
# 'create-web-index' implicitly tests 'nodeinfo'.
machine.wait_for_unit("create-web-index")
assert_running("nginx")
wait_for_open_port(ip("nginx"), 80)
assert_matches(f"curl {ip('nginx')}", "nix-bitcoin")
# Run this test before the following tests that shut down services # Run this test before the following tests that shut down services