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: ACKe2922eb4ce
jonasnick: ACKe2922eb4ce
Tree-SHA512: a85b33efe66048f06699b3997f83c9427f70f278fa66d30ee9a29c91f50723ff8bd1ffb9d968d7f08818742c8c6afb0b40dbfc14b95a4b8c3302caf9bede4198
This commit is contained in:
commit
c6c14889eb
@ -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
|
||||
---
|
||||
|
@ -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
|
||||
|
||||
|
@ -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.\
|
||||
|
@ -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";
|
||||
}
|
||||
|
@ -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 ];
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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 = {
|
||||
|
@ -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}";
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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@@
|
||||
|
@ -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
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
||||
|
@ -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.
|
||||
'';
|
||||
}
|
||||
];
|
||||
|
@ -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";
|
||||
|
@ -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
|
||||
);
|
||||
};
|
||||
};
|
||||
}
|
@ -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;
|
||||
};
|
||||
}
|
||||
|
28
modules/obsolete-options.nix
Normal file
28
modules/obsolete-options.nix
Normal 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")
|
||||
];
|
||||
}
|
76
modules/onion-addresses.nix
Normal file
76
modules/onion-addresses.nix
Normal 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)
|
||||
}
|
||||
'';
|
||||
};
|
||||
};
|
||||
}
|
@ -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
121
modules/onion-services.nix
Normal 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;
|
||||
};
|
||||
};
|
||||
}
|
||||
];
|
||||
}
|
32
modules/presets/enable-tor.nix
Normal file
32
modules/presets/enable-tor.nix
Normal 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;
|
||||
};
|
||||
}
|
@ -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;
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
};
|
||||
}
|
||||
|
@ -20,7 +20,5 @@ let self = {
|
||||
|
||||
pinned = import ./pinned.nix;
|
||||
|
||||
lib = import ./lib.nix { inherit (pkgs) lib; };
|
||||
|
||||
modulesPkgs = self // self.pinned;
|
||||
}; in self
|
||||
|
@ -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);
|
||||
}
|
@ -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;
|
||||
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user