Merge #293: Module refactorings, onionServices

e2922eb4ce6b820fd1bf698c6aadce5d5f4d27c6 move rpc thread count setting to lightning modules (Erik Arvstedt)
352fc4e8fe6c24ff856464d9c53997b96197130b liquid: remove insecure and redundant option 'rpcpassword' (Erik Arvstedt)
757a66b9bd1784d9a20fce8cf04414f31f2c762d liquid: move rpcuser definition to module (Erik Arvstedt)
0e00c39d4784bc64e30b1fc37c684e75e08bdaa6 secure-node: improve layout (Erik Arvstedt)
5f7a7962f77057dba76954bb44813006b98cecde backups: remove redundant option 'program' (Erik Arvstedt)
04d8560f86e94fc9d2f9df1c8334e11442b75373 secure-node: remove qrencode, tor from systemPackages (Erik Arvstedt)
323a431abade32e3c5f0f98acfaada6a80edc1d0 improve nodeinfo (Erik Arvstedt)
f6b883a9acd92e3d94062b21615db14c8383f3b7 remove webindex (Erik Arvstedt)
2a240d6f4a2bc624772b2065f07b1f1a55bf4eb0 enable-tor: disable default onion services for clightning, lnd, btcpayserver (Erik Arvstedt)
18c7842e1affa87df63809cd2f7a6b068468918e modules: show warnings for obsolete options (Erik Arvstedt)
45c40c4eb94b1176216ca2b466442b8029ca8b51 versioning: simplify assertion evaluation (Erik Arvstedt)
bed00fe937a1e7e6a2496ac29ee904e4440b073e lnd: use onionServices for address announcing (Erik Arvstedt)
3980cd5a4191e96d8cf1a942b89149a8c034b31c clightning: use onionServices for address announcing (Erik Arvstedt)
bd2a46cb73de511b763d87593aadf6d0d9eefe11 spark-wallet: use onionServices (Erik Arvstedt)
87fb9f246bd448d890e3958c4be786d81f264b27 add 'enable-tor' preset (Erik Arvstedt)
05b5402bb152543ee21aec583436d35425bcc3b9 add nix-bitcoin.onionServices (Erik Arvstedt)
fffe988248fcb48fe0a58214aa96b2900c92309b onionAddresses: add readonly option 'dataDir' (Erik Arvstedt)
5f34b094d3c13978e1689e73d679190a8f0cdcbb onionAddresses: improve script (Erik Arvstedt)
b266f232515ce64354d22271ffba9dca8496a67f onionAddresses: use service 'script' option (Erik Arvstedt)
6d13b26d0a5d42821028ce999653cff3771a3cc9 onionAddresses: add more precise type for option 'access' (Erik Arvstedt)
93562f76dd0da0ccc77e71e522fb75332ed674ed onionAddresses: remove redundant option 'enable' (Erik Arvstedt)
43c247e3fe3b2a8f635373a4278ba990694b330a onionAddresses: use StateDirectory instead of tmpfiles (Erik Arvstedt)
5c6977b006d492ac7030373129ddb989e0691847 rename onion-chef -> nix-bitcoin.onionAddresses (Erik Arvstedt)
55073eee70056b2850656cb3db1b62068b726267 remove nix-bitcoin.pkgs.lib (Erik Arvstedt)
09e0042aa84b5effa746d9cf76fd06b3a97ad06c spark-wallet: add consistent address options (Erik Arvstedt)
39f16c0b4aab844b183ee4c97acebf95eb2fe1c0 liquidd: add consistent address options (Erik Arvstedt)
b5d76ba1b3cb3c0683a4b0e2feac8aa722966193 electrs: add consistent address options (Erik Arvstedt)
8fa32b7f91523ac6c499c036931d03f98b39be05 btcpayserver: add consistent address options (Erik Arvstedt)
e78a6096871ad88421f7673f24139d0c3f51d867 clightning: add consistent address options (Erik Arvstedt)
b41a720c28a426b1576a063ab74e295b70a5b13e lnd: add consistent address options (Erik Arvstedt)
dd4a0238f9bcc4148eb718933d5ab95ca211e4b9 bitcoind: group rpc options under parent option 'rpc' (Erik Arvstedt)
5b7e0d09b2e85386c16d40ad624e824f88f3c015 bitcoind: add consistent address options (Erik Arvstedt)

Pull request description:

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

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
---
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.
* Includes a [nodeinfo](modules/nodeinfo.nix) script which prints basic info about the node.
* All applications use Tor for outbound connections and support accepting inbound connections via onion services.
NixOS modules
* Application services
@ -74,9 +73,9 @@ NixOS modules
* [bitcoin-core-hwi](https://github.com/bitcoin-core/HWI)
* Helper
* [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
* [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
---

View File

@ -8,7 +8,7 @@ fetch-release > nix-bitcoin-release.nix
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
---
@ -86,10 +86,10 @@ Connect to electrs
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
@ -98,7 +98,7 @@ Connect to electrs
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
@ -107,16 +107,16 @@ Connect to electrs
Network > Proxy mode: socks5, Host: 127.0.0.1, Port: 9050
Network > Auto-connect: OFF
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
nodeinfo | grep 'SSHD_ONION'
nodeinfo | jq -r .sshd.onion_address | sed 's/:.*//'
```
2. Create a SSH key
@ -131,14 +131,14 @@ Connect to nix-bitcoin node through ssh Tor Hidden Service
# FIXME: Add your SSH pubkey
services.openssh.enable = true;
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
@ -148,12 +148,12 @@ Connect to nix-bitcoin node through ssh Tor Hidden Service
bitcoin-node =
{ config, pkgs, ... }:
{ 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
---
@ -263,7 +263,7 @@ you. If however, you want to manually initialize your wallet, follow these steps
## Run the tumbler
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

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
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).\
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
# in C.
services.clightning.enable = true;
# == TOR
# Enable this option to announce our Tor Hidden Service. By default clightning
# offers outgoing functionality, but doesn't announce the Tor Hidden Service
# under which peers can reach us.
# services.clightning.announce-tor = true;
#
# Set this to create an onion service by which clightning can accept incoming connections
# via Tor.
# The onion service is automatically announced to peers.
# nix-bitcoin.onionServices.clightning.public = true;
#
# == Plugins
# See ../docs/usage.md for the list of available plugins.
# services.clightning.plugins.prometheus.enable = true;
@ -49,13 +50,15 @@
### LND
# Uncomment the following line in order to enable lnd, a lightning
# implementation written in Go. In order to avoid collisions with clightning
# you must disable clightning or change the services.clightning.bindport or
# services.lnd.listenPort to a port other than 9735.
# you must disable clightning or change the services.clightning.port or
# services.lnd.port to a port other than 9735.
# 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
# under which peers can reach us.
# services.lnd.announce-tor = true;
#
# Set this to create an onion service by which lnd can accept incoming connections
# via Tor.
# The onion service is automatically announced to peers.
# nix-bitcoin.onionServices.lnd.public = true;
#
## WARNING
# 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
@ -93,6 +96,12 @@
# The lightning backend service automatically enabled.
# Afterwards you need to go into Store > General Settings > Lightning Nodes
# 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
# Enable this module to use Liquid, a sidechain for an inter-exchange
@ -101,11 +110,6 @@
# tool run as user operator.
# 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
# Enable this module to send recurring donations. This is EXPERIMENTAL; it's
# 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.
# 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.
nix-bitcoin.configVersion = "0.0.26";
nix-bitcoin.configVersion = "0.0.30";
}

View File

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

View File

@ -22,16 +22,18 @@ let
${optionalString (cfg.assumevalid != null) "assumevalid=${cfg.assumevalid}"}
# Connection options
${optionalString cfg.listen "bind=${cfg.bind}"}
${optionalString (cfg.port != null) "port=${toString cfg.port}"}
${optionalString cfg.listen "bind=${cfg.address}"}
port=${toString cfg.port}
${optionalString (cfg.proxy != null) "proxy=${cfg.proxy}"}
listen=${if cfg.listen then "1" else "0"}
${optionalString (cfg.discover != null) "discover=${if cfg.discover then "1" else "0"}"}
${lib.concatMapStrings (node: "addnode=${node}\n") cfg.addnodes}
# RPC server options
${optionalString (cfg.rpcthreads != null) "rpcthreads=${toString cfg.rpcthreads}"}
rpcbind=${cfg.rpc.address}
rpcport=${toString cfg.rpc.port}
rpcconnect=${cfg.rpc.address}
${optionalString (cfg.rpc.threads != null) "rpcthreads=${toString cfg.rpc.threads}"}
rpcwhitelistdefault=0
${concatMapStrings (user: ''
${optionalString (!user.passwordHMACFromFile) "rpcauth=${user.name}:${passwordHMAC}"}
@ -39,9 +41,7 @@ let
"rpcwhitelist=${user.name}:${lib.strings.concatStringsSep "," user.rpcwhitelist}"}
'') (builtins.attrValues cfg.rpc.users)
}
rpcbind=${cfg.rpcbind}
rpcconnect=${cfg.rpcbind}
${lib.concatMapStrings (rpcallowip: "rpcallowip=${rpcallowip}\n") cfg.rpcallowip}
${lib.concatMapStrings (rpcallowip: "rpcallowip=${rpcallowip}\n") cfg.rpc.allowip}
# Wallet options
${optionalString (cfg.addresstype != null) "addresstype=${cfg.addresstype}"}
@ -57,6 +57,16 @@ in {
options = {
services.bitcoind = {
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 {
type = types.package;
default = config.nix-bitcoin.pkgs.bitcoind;
@ -77,13 +87,6 @@ in {
default = "/var/lib/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 {
type = types.str;
default = "bitcoin";
@ -95,10 +98,29 @@ in {
description = "The group as which to run bitcoind.";
};
rpc = {
address = mkOption {
type = types.str;
default = "127.0.0.1";
description = ''
Address to listen for JSON-RPC connections.
'';
};
port = mkOption {
type = types.port;
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 {
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 {
type = types.bool;
default = false;
@ -176,11 +179,6 @@ in {
readOnly = true;
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 {
type = types.nullOr types.str;
default = if cfg.enforceTor then config.services.tor.client.socksListenAddress else null;

View File

@ -14,6 +14,16 @@ in {
default = nbPkgs.nbxplorer;
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 {
type = types.path;
default = "/var/lib/nbxplorer";
@ -29,16 +39,6 @@ in {
default = cfg.nbxplorer.user;
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 {
# This option is only used by netns-isolation
internal = true;
@ -49,6 +49,16 @@ in {
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 {
type = types.package;
default = nbPkgs.btcpayserver;
@ -69,16 +79,6 @@ in {
default = cfg.btcpayserver.user;
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 {
type = types.nullOr (types.enum [ "clightning" "lnd" ]);
default = null;
@ -117,9 +117,9 @@ in {
configFile = builtins.toFile "config" ''
network=${config.services.bitcoind.network}
btcrpcuser=${cfg.bitcoind.rpc.users.btcpayserver.name}
btcrpcurl=http://${config.services.bitcoind.rpcbind}:${toString cfg.bitcoind.rpc.port}
btcnodeendpoint=${config.services.bitcoind.bind}:8333
bind=${cfg.nbxplorer.bind}
btcrpcurl=http://${config.services.bitcoind.rpc.address}:${toString cfg.bitcoind.rpc.port}
btcnodeendpoint=${config.services.bitcoind.address}:${toString config.services.bitcoind.port}
bind=${cfg.nbxplorer.address}
port=${toString cfg.nbxplorer.port}
'';
in {
@ -153,9 +153,9 @@ in {
network=${config.services.bitcoind.network}
postgres=User ID=${cfg.btcpayserver.user};Host=/run/postgresql;Database=btcpaydb
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
bind=${cfg.btcpayserver.bind}
bind=${cfg.btcpayserver.address}
${optionalString (cfg.btcpayserver.rootpath != null) "rootpath=${cfg.btcpayserver.rootpath}"}
port=${toString cfg.btcpayserver.port}
'' + optionalString (cfg.btcpayserver.lightningBackend == "clightning") ''
@ -163,7 +163,7 @@ in {
'');
lndConfig =
"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;" +
"certthumbprint=";
in let self = {

View File

@ -6,15 +6,14 @@ let
cfg = config.services.clightning;
inherit (config) nix-bitcoin-services;
nbPkgs = config.nix-bitcoin.pkgs;
onion-chef-service = (if cfg.announce-tor then [ "onion-chef.service" ] else []);
network = config.services.bitcoind.makeNetworkName "bitcoin" "regtest";
configFile = pkgs.writeText "config" ''
network=${network}
bitcoin-datadir=${config.services.bitcoind.dataDir}
${optionalString (cfg.proxy != null) "proxy=${cfg.proxy}"}
always-use-proxy=${if cfg.always-use-proxy then "true" else "false"}
bind-addr=${cfg.bind-addr}:${toString cfg.bindport}
bitcoin-rpcconnect=${config.services.bitcoind.rpcbind}
bind-addr=${cfg.address}:${toString cfg.port}
bitcoin-rpcconnect=${config.services.bitcoind.rpc.address}
bitcoin-rpcport=${toString config.services.bitcoind.rpc.port}
bitcoin-rpcuser=${config.services.bitcoind.rpc.users.public.name}
rpc-file-mode=0660
@ -29,13 +28,15 @@ in {
If enabled, the clightning service will be installed.
'';
};
autolisten = mkOption {
type = types.bool;
default = false;
description = ''
Bind (and maybe announce) on IPv4 and IPv6 interfaces if no addr,
bind-addr or announce-addr options are specified.
'';
address = mkOption {
type = types.str;
default = "127.0.0.1";
description = "IP address or UNIX domain socket to listen for peer connections.";
};
port = mkOption {
type = types.port;
default = 9735;
description = "Port to listen for peer connections.";
};
proxy = mkOption {
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.
'';
};
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 {
type = types.path;
default = "/var/lib/clightning";
@ -97,11 +83,24 @@ in {
'';
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 {
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) ];
users.users.${cfg.user} = {
@ -116,21 +115,25 @@ in {
"d '${cfg.dataDir}' 0770 ${cfg.user} ${cfg.group} - -"
];
services.onion-chef.access.clightning = if cfg.announce-tor then [ "clightning" ] else [];
systemd.services.clightning = {
description = "Run clightningd";
path = [ nbPkgs.bitcoind ];
wantedBy = [ "multi-user.target" ];
requires = [ "bitcoind.service" ] ++ onion-chef-service;
after = [ "bitcoind.service" ] ++ onion-chef-service;
requires = [ "bitcoind.service" ];
after = [ "bitcoind.service" ];
preStart = ''
cp ${configFile} ${cfg.dataDir}/config
chown -R '${cfg.user}:${cfg.group}' '${cfg.dataDir}'
# The RPC socket has to be removed otherwise we might have stale sockets
rm -f ${cfg.networkDir}/lightning-rpc
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 // {
ExecStart = "${nbPkgs.clightning}/bin/lightningd --lightning-dir=${cfg.dataDir}";

View File

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

View File

@ -9,6 +9,16 @@ let
in {
options.services.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 {
type = types.path;
default = "/var/lib/electrs";
@ -31,16 +41,6 @@ in {
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 {
type = types.port;
default = 4224;
@ -95,7 +95,7 @@ in {
--daemon-dir='${bitcoind.dataDir}' \
--electrum-rpc-addr=${cfg.address}:${toString cfg.port} \
--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}
'';
User = cfg.user;

View File

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

View File

@ -17,7 +17,7 @@ let
tlscertpath=${secretsDir}/loop-cert
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.tlspath=${secretsDir}/lnd-cert

View File

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

View File

@ -8,8 +8,7 @@ let
secretsDir = config.nix-bitcoin.secretsDir;
bitcoind = config.services.bitcoind;
bitcoindRpcAddress = bitcoind.rpcbind;
onion-chef-service = (if cfg.announce-tor then [ "onion-chef.service" ] else []);
bitcoindRpcAddress = bitcoind.rpc.address;
networkDir = "${cfg.dataDir}/chain/bitcoin/${bitcoind.network}";
configFile = pkgs.writeText "lnd.conf" ''
datadir=${cfg.dataDir}
@ -17,9 +16,9 @@ let
tlscertpath=${secretsDir}/lnd-cert
tlskeypath=${secretsDir}/lnd-key
listen=${toString cfg.listen}:${toString cfg.listenPort}
rpclisten=${cfg.rpclisten}:${toString cfg.rpcPort}
restlisten=${cfg.restlisten}:${toString cfg.restPort}
listen=${toString cfg.address}:${toString cfg.port}
rpclisten=${cfg.rpcAddress}:${toString cfg.rpcPort}
restlisten=${cfg.restAddress}:${toString cfg.restPort}
bitcoin.${bitcoind.network}=1
bitcoin.active=1
@ -55,50 +54,43 @@ in {
default = networkDir;
description = "The network data directory.";
};
listen = mkOption {
type = config.nix-bitcoin.pkgs.lib.ipv4Address;
address = mkOption {
type = types.str;
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;
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;
default = "localhost";
description = ''
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.
'';
description = "Address to listen for RPC connections.";
};
rpcPort = mkOption {
type = types.port;
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 {
type = types.port;
default = 8080;
description = "Port on which to listen for REST connections.";
description = "Port to listen for REST connections.";
};
tor-socks = mkOption {
type = types.nullOr types.str;
default = if cfg.enforceTor then config.services.tor.client.socksListenAddress else null;
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 {
default = {};
type = with types; attrsOf (submodule {
@ -138,13 +130,21 @@ in {
# Switch user because lnd makes datadir contents readable by user only
''
sudo -u lnd ${cfg.package}/bin/lncli \
--rpcserver ${cfg.rpclisten}:${toString cfg.rpcPort} \
--rpcserver ${cfg.rpcAddress}:${toString cfg.rpcPort} \
--tlscertpath '${secretsDir}/lnd-cert' \
--macaroonpath '${networkDir}/admin.macaroon' "$@"
'';
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 {
@ -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) ];
@ -167,16 +172,19 @@ in {
zmqpubrawtx = "tcp://${bitcoindRpcAddress}:28333";
};
services.onion-chef.access.lnd = if cfg.announce-tor then [ "lnd" ] else [];
systemd.services.lnd = {
description = "Run LND";
wantedBy = [ "multi-user.target" ];
requires = [ "bitcoind.service" ] ++ onion-chef-service;
after = [ "bitcoind.service" ] ++ onion-chef-service;
requires = [ "bitcoind.service" ];
after = [ "bitcoind.service" ];
preStart = ''
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 // {
RuntimeDirectory = "lnd"; # Only used to store custom macaroons
@ -187,12 +195,12 @@ in {
RestartSec = "10s";
ReadWritePaths = "${cfg.dataDir}";
ExecStartPost = let
restUrl = "https://${cfg.restlisten}:${toString cfg.restPort}/v1";
restUrl = "https://${cfg.restAddress}:${toString cfg.restPort}/v1";
in [
# Run fully privileged for secrets dir write access
"+${nix-bitcoin-services.script ''
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; }
sleep 0.1
done
@ -234,7 +242,7 @@ in {
fi
# 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
done

View File

@ -24,9 +24,11 @@ with lib;
# Support features
./versioning.nix
./security.nix
./onion-addresses.nix
./onion-services.nix
./netns-isolation.nix
./nodeinfo.nix
./backups.nix
./onion-chef.nix
];
disabledModules = [ "services/networking/bitcoind.nix" ];
@ -58,11 +60,11 @@ with lib;
config = {
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 = ''
LND and clightning can't both bind to lightning port 9735. Either
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 = {
bind = netns.bitcoind.address;
rpcbind = netns.bitcoind.address;
rpcallowip = [
address = netns.bitcoind.address;
rpc.address = netns.bitcoind.address;
rpc.allowip = [
bridgeIp # For operator user
netns.bitcoind.address
] ++ map (n: netns.${n}.address) netns.bitcoind.availableNetns;
};
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 = {
listen = netns.lnd.address;
rpclisten = netns.lnd.address;
restlisten = netns.lnd.address;
address = netns.lnd.address;
rpcAddress = netns.lnd.address;
restAddress = netns.lnd.address;
};
services.liquidd = {
bind = netns.liquidd.address;
rpcbind = netns.liquidd.address;
address = netns.liquidd.address;
rpc.address = netns.liquidd.address;
rpcallowip = [
bridgeIp # For operator user
netns.liquidd.address
@ -274,14 +274,14 @@ in {
services.electrs.address = netns.electrs.address;
services.spark-wallet = {
host = netns.spark-wallet.address;
address = netns.spark-wallet.address;
extraArgs = "--no-tls";
};
services.lightning-loop.rpcAddress = netns.lightning-loop.address;
services.nbxplorer.bind = netns.nbxplorer.address;
services.btcpayserver.bind = netns.btcpayserver.address;
services.nbxplorer.address = netns.nbxplorer.address;
services.btcpayserver.address = netns.btcpayserver.address;
services.joinmarket.cliExec = mkCliExec "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, ... }:
with lib;
let
operatorName = config.nix-bitcoin.operator.name;
cfg = config.nix-bitcoin.nodeinfo;
# Services included in the output
services = {
bitcoind = mkInfo "";
clightning = mkInfo ''
info["nodeid"] = shell("lightning-cli getinfo | jq -r '.id'")
if 'onion_address' in info:
info["id"] = f"{info['nodeid']}@{info['onion_address']}"
'';
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" ''
set -eo pipefail
#!${pkgs.python3}/bin/python
BITCOIND_ONION="$(cat /var/lib/onion-chef/${operatorName}/bitcoind)"
echo BITCOIND_ONION="$BITCOIND_ONION"
import json
import subprocess
from collections import OrderedDict
if systemctl is-active --quiet clightning; then
CLIGHTNING_NODEID=$(lightning-cli getinfo | jq -r '.id')
CLIGHTNING_ONION="$(cat /var/lib/onion-chef/${operatorName}/clightning)"
CLIGHTNING_ID="$CLIGHTNING_NODEID@$CLIGHTNING_ONION:9735"
echo CLIGHTNING_NODEID="$CLIGHTNING_NODEID"
echo CLIGHTNING_ONION="$CLIGHTNING_ONION"
echo CLIGHTNING_ID="$CLIGHTNING_ID"
fi
def success(*args):
return subprocess.call(args, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) == 0
if systemctl is-active --quiet lnd; then
LND_NODEID=$(lncli getinfo | jq -r '.uris[0]')
echo LND_NODEID="$LND_NODEID"
fi
def is_active(unit):
return success("systemctl", "is-active", "--quiet", unit)
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
def is_enabled(unit):
return success("systemctl", "is-enabled", "--quiet", unit)
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
def cmd(*args):
return subprocess.run(args, stdout=subprocess.PIPE).stdout.decode('utf-8')
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
def shell(*args):
return cmd("bash", "-c", *args).strip()
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
infos = OrderedDict()
operator = "${config.nix-bitcoin.operator.name}"
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
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}"
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
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 {
options = {
programs.nodeinfo = mkOption {
readOnly = true;
default = script;
nix-bitcoin.nodeinfo = {
enable = mkEnableOption "nodeinfo";
program = mkOption {
readOnly = true;
default = script;
};
};
};
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 {
imports = [
../modules.nix
../nodeinfo.nix
../nix-bitcoin-webindex.nix
./enable-tor.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 = {
# For backwards compatibility only
nix-bitcoin.secretsDir = mkDefault "/secrets";
@ -39,99 +25,36 @@ in {
nix-bitcoin.security.hideProcessInformation = true;
# Tor
services.tor = {
enable = true;
client.enable = true;
environment.systemPackages = with pkgs; [
jq
];
hiddenServices.sshd = mkHiddenService { port = 22; };
};
# sshd
services.tor.hiddenServices.sshd = mkHiddenService { port = 22; };
nix-bitcoin.onionAddresses.access.${operatorName} = [ "sshd" ];
# bitcoind
services.bitcoind = {
enable = true;
listen = true;
dataDirReadableByGroup = mkIf cfg.electrs.high-memory true;
enforceTor = true;
port = 8333;
assumevalid = "00000000000000000000e5abc3a74fe27dc0ead9c70ea1deb456f11c15fd7bc6";
addnodes = [ "ecoc5q34tmbq54wl.onion" ];
discover = false;
addresstype = "bech32";
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 = {
rpcuser = "liquidrpc";
prune = 1000;
validatepegin = 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;
# 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" ];
};
services.backups.frequency = "daily";
# operator
nix-bitcoin.operator.enable = true;
users.users.${operatorName} = {
openssh.authorizedKeys.keys = config.users.users.root.openssh.authorizedKeys.keys;

View File

@ -5,18 +5,17 @@ with lib;
let
cfg = config.services.spark-wallet;
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
# connections through Tor
torRateProvider = "--rate-provider wasabi --proxy socks5h://${config.services.tor.client.socksListenAddress}";
startScript = ''
${optionalString cfg.onion-service ''
publicURL="--public-url http://$(cat /var/lib/onion-chef/spark-wallet/spark-wallet)"
${optionalString (cfg.getPublicAddressCmd != "") ''
publicURL="--public-url http://$(${cfg.getPublicAddressCmd})"
''}
exec ${config.nix-bitcoin.pkgs.spark-wallet}/bin/spark-wallet \
--ln-path '${config.services.clightning.networkDir}' \
--host ${cfg.host} \
--host ${cfg.address} --port ${toString cfg.port} \
--config '${config.nix-bitcoin.secretsDir}/spark-wallet-login' \
${optionalString cfg.enforceTor torRateProvider} \
$publicURL \
@ -31,24 +30,31 @@ in {
If enabled, the spark-wallet service will be installed.
'';
};
host = mkOption {
address = mkOption {
type = types.str;
default = "localhost";
description = "http(s) server listen address.";
description = "http(s) server address.";
};
onion-service = mkOption {
type = types.bool;
default = false;
description = ''
"If enabled, configures spark-wallet to be reachable through an onion service.";
'';
port = mkOption {
type = types.port;
default = 9737;
description = "http(s) server port.";
};
extraArgs = mkOption {
type = types.separatedString " ";
default = "";
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 {
@ -61,25 +67,16 @@ in {
};
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 = {
description = "Run spark-wallet";
wantedBy = [ "multi-user.target" ];
requires = [ "clightning.service" ] ++ onion-chef-service;
after = [ "clightning.service" ] ++ onion-chef-service;
requires = [ "clightning.service" ];
after = [ "clightning.service" ];
script = startScript;
serviceConfig = nix-bitcoin-services.defaultHardening // {
User = "spark-wallet";
Restart = "on-failure";
RestartSec = "10s";
ReadWritePaths = mkIf cfg.onion-service "/var/lib/onion-chef";
} // (if cfg.enforceTor
then nix-bitcoin-services.allowTor
else nix-bitcoin-services.allowAnyIP)

View File

@ -5,7 +5,19 @@ let
version = config.nix-bitcoin.configVersion;
# 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";
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
'';
}
(mkOnionServiceChange "clightning")
(mkOnionServiceChange "lnd")
(mkOnionServiceChange "btcpayserver")
];
incompatibleChanges = optionals
@ -76,6 +91,10 @@ let
lastChange = builtins.elemAt changes (builtins.length changes - 1);
in
{
imports = [
./obsolete-options.nix
];
options = {
nix-bitcoin.configVersion = mkOption {
type = with types; nullOr str;
@ -93,6 +112,6 @@ in
config = {
# 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;
lib = import ./lib.nix { inherit (pkgs) lib; };
modulesPkgs = self // self.pinned;
}; 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.lnd = cfg.lnd.enable;
services.lnd.listenPort = 9736;
services.lnd.port = 9736;
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;
# To test that unused secrets are made inaccessible by 'setup-secrets'
@ -119,6 +121,8 @@ let testEnv = rec {
services.joinmarket.enable = true;
services.backups.enable = true;
nix-bitcoin.nodeinfo.enable = true;
services.hardware-wallets = {
trezor = true;
ledger = true;
@ -130,7 +134,6 @@ let testEnv = rec {
scenarios.full
../modules/presets/secure-node.nix
];
services.nix-bitcoin-webindex.enable = true;
tests.secure-node = 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")
def _():
assert_running("onion-chef")
# 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")
assert_running("onion-addresses")
# Run this test before the following tests that shut down services