From d61b185c3a03c67218a5b09a2e221978f82434ac Mon Sep 17 00:00:00 2001 From: Erik Arvstedt Date: Wed, 27 Nov 2019 14:04:15 +0100 Subject: [PATCH 01/42] simplify user and group definitions --- modules/bitcoind.nix | 5 +---- modules/clightning.nix | 4 +--- modules/electrs.nix | 5 +---- modules/liquid.nix | 5 +---- modules/lnd.nix | 4 +--- modules/nanopos.nix | 7 ++----- 6 files changed, 7 insertions(+), 23 deletions(-) diff --git a/modules/bitcoind.nix b/modules/bitcoind.nix index b3baf19..3903290 100644 --- a/modules/bitcoind.nix +++ b/modules/bitcoind.nix @@ -295,14 +295,11 @@ in { }; users.users.${cfg.user} = { - name = cfg.user; group = cfg.group; extraGroups = [ "keys" ]; description = "Bitcoin daemon user"; home = cfg.dataDir; }; - users.groups.${cfg.group} = { - name = cfg.group; - }; + users.groups.${cfg.group} = {}; }; } diff --git a/modules/clightning.nix b/modules/clightning.nix index 042187b..4fc1e89 100644 --- a/modules/clightning.nix +++ b/modules/clightning.nix @@ -67,9 +67,7 @@ in { extraGroups = [ "bitcoinrpc" "keys" ]; home = cfg.dataDir; }; - users.groups.clightning = { - name = "clightning"; - }; + users.groups.clightning = {}; systemd.services.clightning = { description = "Run clightningd"; diff --git a/modules/electrs.nix b/modules/electrs.nix index b29ae3c..2d6ee20 100644 --- a/modules/electrs.nix +++ b/modules/electrs.nix @@ -58,15 +58,12 @@ in { config = mkIf cfg.enable { users.users.${cfg.user} = { - name = cfg.user; description = "electrs User"; group = cfg.group; extraGroups = [ "bitcoinrpc" "keys" "bitcoin"]; home = cfg.dataDir; }; - users.groups.electrs = { - name = cfg.group; - }; + users.groups.${cfg.group} = {}; systemd.services.electrs = { description = "Run electrs"; diff --git a/modules/liquid.nix b/modules/liquid.nix index 968618d..c558a6a 100644 --- a/modules/liquid.nix +++ b/modules/liquid.nix @@ -214,14 +214,11 @@ in { ); }; users.users.${cfg.user} = { - name = cfg.user; group = cfg.group; extraGroups = [ "keys" ]; description = "Liquid sidechain user"; home = cfg.dataDir; }; - users.groups.${cfg.group} = { - name = cfg.group; - }; + users.groups.${cfg.group} = {}; }; } diff --git a/modules/lnd.nix b/modules/lnd.nix index 5131834..496eb11 100644 --- a/modules/lnd.nix +++ b/modules/lnd.nix @@ -98,9 +98,7 @@ in { extraGroups = [ "bitcoinrpc" "keys" ]; home = cfg.dataDir; }; - users.groups.lnd = { - name = "lnd"; - }; + users.groups.lnd = {}; systemd.services.lnd = { description = "Run LND"; diff --git a/modules/nanopos.nix b/modules/nanopos.nix index 300da4b..2dc7364 100644 --- a/modules/nanopos.nix +++ b/modules/nanopos.nix @@ -52,15 +52,12 @@ in { }; config = mkIf cfg.enable { - users.users.nanopos = - { + users.users.nanopos = { description = "nanopos User"; group = "nanopos"; extraGroups = [ "keys" ]; }; - users.groups.nanopos = { - name = "nanopos"; - }; + users.groups.nanopos = {}; systemd.services.nanopos = { description = "Run nanopos"; From 07dc3e04ac8a56b5200de64dab34ed8aac39e45e Mon Sep 17 00:00:00 2001 From: Erik Arvstedt Date: Wed, 27 Nov 2019 14:04:16 +0100 Subject: [PATCH 02/42] move bitcoinrpc group definition to bitcoind services.bitcoind has a strict dependency on the 'bitcoinrpc' group via the 'bitcoin-rpcpassword' secret. --- modules/bitcoind.nix | 1 + modules/nix-bitcoin.nix | 3 --- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/modules/bitcoind.nix b/modules/bitcoind.nix index 3903290..31ad358 100644 --- a/modules/bitcoind.nix +++ b/modules/bitcoind.nix @@ -301,5 +301,6 @@ in { home = cfg.dataDir; }; users.groups.${cfg.group} = {}; + users.groups.bitcoinrpc = {}; }; } diff --git a/modules/nix-bitcoin.nix b/modules/nix-bitcoin.nix index 541448f..351bb72 100644 --- a/modules/nix-bitcoin.nix +++ b/modules/nix-bitcoin.nix @@ -85,9 +85,6 @@ in { version = 3; }; - # Add bitcoinrpc group - users.groups.bitcoinrpc = {}; - # clightning services.clightning.bitcoin-rpcuser = config.services.bitcoind.rpcuser; services.clightning.proxy = config.services.tor.client.socksListenAddress; From 7e021a26295e345c6676f598448baaadc9ff33f0 Mon Sep 17 00:00:00 2001 From: Erik Arvstedt Date: Wed, 27 Nov 2019 14:04:17 +0100 Subject: [PATCH 03/42] simplify overlay.nix Move pkg definitions to pkgs/default.nix. This allows us to just import the pkgs in overlay.nix and get rid of the filtering to exclude the modules. --- default.nix | 15 ++------------- overlay.nix | 22 +--------------------- pkgs/default.nix | 12 ++++++++++++ 3 files changed, 15 insertions(+), 34 deletions(-) create mode 100644 pkgs/default.nix diff --git a/default.nix b/default.nix index f5c5399..14030d9 100644 --- a/default.nix +++ b/default.nix @@ -1,16 +1,5 @@ { pkgs ? import {} }: -{ - # 'lib', 'modules' and 'overlays' are special, see - # https://github.com/nix-community/NUR for more. - modules = import ./modules; # NixOS modules - nodeinfo = pkgs.callPackage ./pkgs/nodeinfo { }; - lightning-charge = pkgs.callPackage ./pkgs/lightning-charge { }; - nanopos = pkgs.callPackage ./pkgs/nanopos { }; - spark-wallet = pkgs.callPackage ./pkgs/spark-wallet { }; - electrs = (pkgs.callPackage ./pkgs/electrs { }).rootCrate.build; - elementsd = pkgs.callPackage ./pkgs/elementsd { withGui = false; }; - hwi = pkgs.callPackage ./pkgs/hwi { }; - pylightning = pkgs.python3Packages.callPackage ./pkgs/pylightning { }; - liquid-swap = pkgs.python3Packages.callPackage ./pkgs/liquid-swap { }; +(import ./pkgs { inherit pkgs; }) // { + modules = import ./modules; } diff --git a/overlay.nix b/overlay.nix index 082b038..ca61067 100644 --- a/overlay.nix +++ b/overlay.nix @@ -1,21 +1 @@ -# You can use this file as a nixpkgs overlay. -# It's useful in the case where you don't want to add the whole NUR namespace -# to your configuration. - -self: super: - -let filterSet = - (f: g: s: builtins.listToAttrs - (map - (n: { name = n; value = builtins.getAttr n s; }) - (builtins.filter - (n: f n && g (builtins.getAttr n s)) - (builtins.attrNames s) - ) - ) - ); -in filterSet - (n: !(n=="lib"||n=="overlays"||n=="modules")) # filter out non-packages - (p: true) # all packages are ok - (import ./default.nix { pkgs = super; }) - +self: super: import ./pkgs { pkgs = super; } diff --git a/pkgs/default.nix b/pkgs/default.nix new file mode 100644 index 0000000..cdde65d --- /dev/null +++ b/pkgs/default.nix @@ -0,0 +1,12 @@ +{ pkgs ? import {} }: +{ + nodeinfo = pkgs.callPackage ./nodeinfo { }; + lightning-charge = pkgs.callPackage ./lightning-charge { }; + nanopos = pkgs.callPackage ./nanopos { }; + spark-wallet = pkgs.callPackage ./spark-wallet { }; + electrs = (pkgs.callPackage ./electrs { }).rootCrate.build; + elementsd = pkgs.callPackage ./elementsd { withGui = false; }; + hwi = pkgs.callPackage ./hwi { }; + pylightning = pkgs.python3Packages.callPackage ./pylightning { }; + liquid-swap = pkgs.python3Packages.callPackage ./liquid-swap { }; +} From bbf2bbc04a80f951768860d7eba5b37fc067921d Mon Sep 17 00:00:00 2001 From: Erik Arvstedt Date: Wed, 27 Nov 2019 14:04:18 +0100 Subject: [PATCH 04/42] network.nix: simplify import of main config --- network/network.nix | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/network/network.nix b/network/network.nix index 96997ca..9a35e9d 100644 --- a/network/network.nix +++ b/network/network.nix @@ -75,10 +75,9 @@ in { network.description = "Bitcoin Core node"; bitcoin-node = - { config, pkgs, ... }: - let - bitcoin-node = import ../configuration.nix; - in { + { config, pkgs, ... }: { + imports = [ ../configuration.nix ]; + deployment.keys = { inherit bitcoin-rpcpassword; } @@ -88,5 +87,5 @@ in { // (if (config.services.liquidd.enable) then { inherit liquid-rpcpassword; } else { }) // (if (config.services.spark-wallet.enable) then { inherit spark-wallet-login; } else { }) // (if (config.services.electrs.enable) then { inherit nginx_key nginx_cert; } else { }); - } // (bitcoin-node { inherit config pkgs; }); + }; } From 3b842e5fe773b9031b15ea2d0ae05749df079d02 Mon Sep 17 00:00:00 2001 From: Erik Arvstedt Date: Wed, 27 Nov 2019 14:04:19 +0100 Subject: [PATCH 05/42] add nix-bitcoin-secrets.target Remove use of nixops-specific 'keys' group and key services. Instead: - Add nix-bitcoin-secrets.target, which should be required by all units that depend on secrets. (To keep it simple, it's okay to meet the secrets dependency indirectly by e.g. depending on bitcoind.) Various secret deployment methods can use this target by setting up the secrets before activating the target. In case of nixops we just specify that nixops' keys.target comes before nix-bitcoin-secrets.target. If the target is left undefined in the case of manual secrets deployment, systemd will simply ignore unit dependencies on the target. - Allow all users to access the secrets dir. The access protection for the individual secret files is unchanged. This allows us to drop the unit dependency on the nixops 'keys' group. --- modules/bitcoind.nix | 5 ++--- modules/clightning.nix | 2 +- modules/electrs.nix | 6 +++++- modules/liquid.nix | 5 ++--- modules/lnd.nix | 2 +- modules/nanopos.nix | 1 - network/network.nix | 19 ++++++++++++++++++- 7 files changed, 29 insertions(+), 11 deletions(-) diff --git a/modules/bitcoind.nix b/modules/bitcoind.nix index 31ad358..c1abaff 100644 --- a/modules/bitcoind.nix +++ b/modules/bitcoind.nix @@ -225,8 +225,8 @@ in { environment.systemPackages = [ cfg.package ]; systemd.services.bitcoind = { description = "Bitcoin daemon"; - requires = [ "bitcoin-rpcpassword-key.service" ]; - after = [ "network.target" "bitcoin-rpcpassword-key.service" ]; + requires = [ "nix-bitcoin-secrets.target" ]; + after = [ "network.target" "nix-bitcoin-secrets.target" ]; wantedBy = [ "multi-user.target" ]; preStart = '' if ! test -e ${cfg.dataDir}; then @@ -296,7 +296,6 @@ in { users.users.${cfg.user} = { group = cfg.group; - extraGroups = [ "keys" ]; description = "Bitcoin daemon user"; home = cfg.dataDir; }; diff --git a/modules/clightning.nix b/modules/clightning.nix index 4fc1e89..a01ff7c 100644 --- a/modules/clightning.nix +++ b/modules/clightning.nix @@ -64,7 +64,7 @@ in { users.users.clightning = { description = "clightning User"; group = "clightning"; - extraGroups = [ "bitcoinrpc" "keys" ]; + extraGroups = [ "bitcoinrpc" ]; home = cfg.dataDir; }; users.groups.clightning = {}; diff --git a/modules/electrs.nix b/modules/electrs.nix index 2d6ee20..77d8944 100644 --- a/modules/electrs.nix +++ b/modules/electrs.nix @@ -60,7 +60,7 @@ in { users.users.${cfg.user} = { description = "electrs User"; group = cfg.group; - extraGroups = [ "bitcoinrpc" "keys" "bitcoin"]; + extraGroups = [ "bitcoinrpc" "bitcoin"]; home = cfg.dataDir; }; users.groups.${cfg.group} = {}; @@ -113,5 +113,9 @@ in { } ''; }; + systemd.services.nginx = { + requires = [ "nix-bitcoin-secrets.target" ]; + after = [ "nix-bitcoin-secrets.target" ]; + }; }; } diff --git a/modules/liquid.nix b/modules/liquid.nix index c558a6a..5ce72e2 100644 --- a/modules/liquid.nix +++ b/modules/liquid.nix @@ -183,8 +183,8 @@ in { environment.systemPackages = [ pkgs.elementsd ]; systemd.services.liquidd = { description = "Elements daemon providing access to the Liquid sidechain"; - requires = [ "liquid-rpcpassword-key.service" ]; - after = [ "network.target" "liquid-rpcpassword-key.service" ]; + requires = [ "bitcoind.service" ]; + after = [ "bitcoind.service" ]; wantedBy = [ "multi-user.target" ]; preStart = '' if ! test -e ${cfg.dataDir}; then @@ -215,7 +215,6 @@ in { }; users.users.${cfg.user} = { group = cfg.group; - extraGroups = [ "keys" ]; description = "Liquid sidechain user"; home = cfg.dataDir; }; diff --git a/modules/lnd.nix b/modules/lnd.nix index 496eb11..3806477 100644 --- a/modules/lnd.nix +++ b/modules/lnd.nix @@ -95,7 +95,7 @@ in { users.users.lnd = { description = "LND User"; group = "lnd"; - extraGroups = [ "bitcoinrpc" "keys" ]; + extraGroups = [ "bitcoinrpc" ]; home = cfg.dataDir; }; users.groups.lnd = {}; diff --git a/modules/nanopos.nix b/modules/nanopos.nix index 2dc7364..9fb5337 100644 --- a/modules/nanopos.nix +++ b/modules/nanopos.nix @@ -55,7 +55,6 @@ in { users.users.nanopos = { description = "nanopos User"; group = "nanopos"; - extraGroups = [ "keys" ]; }; users.groups.nanopos = {}; diff --git a/network/network.nix b/network/network.nix index 9a35e9d..f8ae92b 100644 --- a/network/network.nix +++ b/network/network.nix @@ -75,7 +75,7 @@ in { network.description = "Bitcoin Core node"; bitcoin-node = - { config, pkgs, ... }: { + { config, pkgs, lib, ... }: { imports = [ ../configuration.nix ]; deployment.keys = { @@ -87,5 +87,22 @@ in { // (if (config.services.liquidd.enable) then { inherit liquid-rpcpassword; } else { }) // (if (config.services.spark-wallet.enable) then { inherit spark-wallet-login; } else { }) // (if (config.services.electrs.enable) then { inherit nginx_key nginx_cert; } else { }); + + # nixops makes the secrets directory accessible only for users with group 'key'. + # For compatibility with other deployment methods besides nixops, we forego the + # use of the 'key' group and make the secrets dir world-readable instead. + # This is safe because all containing files have their specific private + # permissions set. + systemd.services.allowSecretsDirAccess = { + requires = [ "keys.target" ]; + after = [ "keys.target" ]; + script = "chmod o+x /secrets"; + serviceConfig.Type = "oneshot"; + }; + + systemd.targets.nix-bitcoin-secrets = { + requires = [ "allowSecretsDirAccess.service" ]; + after = [ "allowSecretsDirAccess.service" ]; + }; }; } From 6def181dbc28bbab431c132a475b931df4aac16a Mon Sep 17 00:00:00 2001 From: Erik Arvstedt Date: Wed, 27 Nov 2019 14:04:20 +0100 Subject: [PATCH 06/42] add modules.nix Importing modules.nix enables the stand-alone use of the modules, without the config presets of nix-bitcoin.nix. --- modules/default.nix | 2 +- modules/modules.nix | 31 +++++++++++++++++++++++++++++++ modules/nix-bitcoin-pkgs.nix | 17 ----------------- modules/nix-bitcoin.nix | 17 +---------------- 4 files changed, 33 insertions(+), 34 deletions(-) create mode 100644 modules/modules.nix delete mode 100644 modules/nix-bitcoin-pkgs.nix diff --git a/modules/default.nix b/modules/default.nix index 621ab8f..6664741 100644 --- a/modules/default.nix +++ b/modules/default.nix @@ -1,4 +1,5 @@ { + modules = ./modules.nix; bitcoind = ./bitcoind.nix; clightning = ./clightning.nix; default = ./default.nix; @@ -7,7 +8,6 @@ liquid = ./liquid.nix; nanopos = ./nanopos.nix; nix-bitcoin = ./nix-bitcoin.nix; - nix-bitcoin-pkgs = ./nix-bitcoin-pkgs.nix; nix-bitcoin-webindex = ./nix-bitcoin-webindex.nix; spark-wallet = ./spark-wallet.nix; recurring-donations = ./recurring-donations.nix; diff --git a/modules/modules.nix b/modules/modules.nix new file mode 100644 index 0000000..7dba3c6 --- /dev/null +++ b/modules/modules.nix @@ -0,0 +1,31 @@ +{ config, pkgs, ... }: +let + nixpkgs-pinned = import ../pkgs/nixpkgs-pinned.nix; + unstable = import nixpkgs-pinned.nixpkgs-unstable {}; + + allPackages = pkgs: (import ../pkgs { inherit pkgs; }) // { + bitcoin = unstable.bitcoin.override { miniupnpc = null; }; + bitcoind = unstable.bitcoind.override { miniupnpc = null; }; + clightning = unstable.clightning; + lnd = unstable.lnd; + }; +in { + imports = [ + ./bitcoind.nix + ./clightning.nix + ./lightning-charge.nix + ./nanopos.nix + ./nix-bitcoin-webindex.nix + ./liquid.nix + ./spark-wallet.nix + ./electrs.nix + ./onion-chef.nix + ./recurring-donations.nix + ./hardware-wallets.nix + ./lnd.nix + ]; + + disabledModules = [ "services/networking/bitcoind.nix" ]; + + nixpkgs.overlays = [ (self: super: allPackages super) ]; +} diff --git a/modules/nix-bitcoin-pkgs.nix b/modules/nix-bitcoin-pkgs.nix deleted file mode 100644 index d5c8bd5..0000000 --- a/modules/nix-bitcoin-pkgs.nix +++ /dev/null @@ -1,17 +0,0 @@ -{ config, pkgs, ... }: -let - nixpkgs-pinned = import ../pkgs/nixpkgs-pinned.nix; - nixpkgs-unstable = import nixpkgs-pinned.nixpkgs-unstable { }; -in { - disabledModules = [ "services/networking/bitcoind.nix" ]; - - nixpkgs.overlays = [ (import ../overlay.nix) ]; - - nixpkgs.config.packageOverrides = pkgs: { - # Use bitcoin and clightning from unstable - bitcoin = nixpkgs-unstable.bitcoin.override { miniupnpc = null; }; - blockchains.bitcoind = nixpkgs-unstable.bitcoind.override { miniupnpc = null; }; - clightning = nixpkgs-unstable.clightning.override { }; - lnd = nixpkgs-unstable.lnd.override { }; - }; -} diff --git a/modules/nix-bitcoin.nix b/modules/nix-bitcoin.nix index 351bb72..2ae3ac4 100644 --- a/modules/nix-bitcoin.nix +++ b/modules/nix-bitcoin.nix @@ -15,21 +15,7 @@ let chown -R operator ${config.users.users.operator.home}/.ssh ''; in { - imports = [ - ./nix-bitcoin-pkgs.nix - ./bitcoind.nix - ./clightning.nix - ./lightning-charge.nix - ./nanopos.nix - ./nix-bitcoin-webindex.nix - ./liquid.nix - ./spark-wallet.nix - ./electrs.nix - ./onion-chef.nix - ./recurring-donations.nix - ./hardware-wallets.nix - ./lnd.nix - ]; + imports = [ ./modules.nix ]; options.services.nix-bitcoin = { enable = mkOption { @@ -207,4 +193,3 @@ in { ]; }; } - From 760da232e0145fed1016e1efa9bd7dca49d22631 Mon Sep 17 00:00:00 2001 From: Erik Arvstedt Date: Wed, 27 Nov 2019 14:04:21 +0100 Subject: [PATCH 07/42] add nix-bitcoin pkgs namespace Not polluting the main pkgs namespace with internal pkgs makes it easier to integrate the nix-bitcoin modules into a larger config. Also, by overriding the nix-bitcoin namespace, users can now easily set the packages used by services that offer no explicit `package` option, like `clightning`. --- modules/bitcoind.nix | 2 +- modules/clightning.nix | 4 ++-- modules/electrs.nix | 2 +- modules/lightning-charge.nix | 2 +- modules/liquid.nix | 4 ++-- modules/lnd.nix | 4 ++-- modules/modules.nix | 4 +++- modules/nanopos.nix | 2 +- modules/nix-bitcoin-webindex.nix | 2 +- modules/nix-bitcoin.nix | 4 ++-- modules/recurring-donations.nix | 2 +- modules/spark-wallet.nix | 2 +- 12 files changed, 18 insertions(+), 16 deletions(-) diff --git a/modules/bitcoind.nix b/modules/bitcoind.nix index c1abaff..55840c9 100644 --- a/modules/bitcoind.nix +++ b/modules/bitcoind.nix @@ -69,7 +69,7 @@ in { package = mkOption { type = types.package; - default = pkgs.blockchains.bitcoind; + default = pkgs.nix-bitcoin.bitcoind; defaultText = "pkgs.blockchains.bitcoind"; description = "The package providing bitcoin binaries."; }; diff --git a/modules/clightning.nix b/modules/clightning.nix index a01ff7c..4e68d8b 100644 --- a/modules/clightning.nix +++ b/modules/clightning.nix @@ -71,7 +71,7 @@ in { systemd.services.clightning = { description = "Run clightningd"; - path = [ pkgs.blockchains.bitcoind ]; + path = [ pkgs.nix-bitcoin.bitcoind ]; wantedBy = [ "multi-user.target" ]; requires = [ "bitcoind.service" ]; after = [ "bitcoind.service" ]; @@ -87,7 +87,7 @@ in { ''; serviceConfig = { PermissionsStartOnly = "true"; - ExecStart = "${pkgs.clightning}/bin/lightningd --lightning-dir=${cfg.dataDir}"; + ExecStart = "${pkgs.nix-bitcoin.clightning}/bin/lightningd --lightning-dir=${cfg.dataDir}"; User = "clightning"; Restart = "on-failure"; RestartSec = "10s"; diff --git a/modules/electrs.nix b/modules/electrs.nix index 77d8944..1ad1f09 100644 --- a/modules/electrs.nix +++ b/modules/electrs.nix @@ -74,7 +74,7 @@ in { preStart = '' mkdir -m 0770 -p ${cfg.dataDir} chown -R '${cfg.user}:${cfg.group}' ${cfg.dataDir} - echo "${pkgs.electrs}/bin/electrs -vvv ${index-batch-size} ${jsonrpc-import} --timestamp --db-dir ${cfg.dataDir} --daemon-dir /var/lib/bitcoind --cookie=${config.services.bitcoind.rpcuser}:$(cat /secrets/bitcoin-rpcpassword) --electrum-rpc-addr=127.0.0.1:${toString cfg.port}" > /run/electrs/startscript.sh + echo "${pkgs.nix-bitcoin.electrs}/bin/electrs -vvv ${index-batch-size} ${jsonrpc-import} --timestamp --db-dir ${cfg.dataDir} --daemon-dir /var/lib/bitcoind --cookie=${config.services.bitcoind.rpcuser}:$(cat /secrets/bitcoin-rpcpassword) --electrum-rpc-addr=127.0.0.1:${toString cfg.port}" > /run/electrs/startscript.sh ''; serviceConfig = rec { RuntimeDirectory = "electrs"; diff --git a/modules/lightning-charge.nix b/modules/lightning-charge.nix index 003a1b3..d22e7ac 100644 --- a/modules/lightning-charge.nix +++ b/modules/lightning-charge.nix @@ -31,7 +31,7 @@ in { after = [ "clightning.service" ]; serviceConfig = { EnvironmentFile = "/secrets/lightning-charge-api-token"; - ExecStart = "${pkgs.lightning-charge}/bin/charged -l ${config.services.clightning.dataDir} -d ${config.services.clightning.dataDir}/lightning-charge.db"; + ExecStart = "${pkgs.nix-bitcoin.lightning-charge}/bin/charged -l ${config.services.clightning.dataDir} -d ${config.services.clightning.dataDir}/lightning-charge.db"; # Unfortunately c-lightning doesn't allow setting the permissions of the rpc socket, # so this must run as the clightning user # https://github.com/ElementsProject/lightning/issues/1366 diff --git a/modules/liquid.nix b/modules/liquid.nix index 5ce72e2..0298413 100644 --- a/modules/liquid.nix +++ b/modules/liquid.nix @@ -180,7 +180,7 @@ in { }; config = mkIf cfg.enable { - environment.systemPackages = [ pkgs.elementsd ]; + environment.systemPackages = [ pkgs.nix-bitcoin.elementsd ]; systemd.services.liquidd = { description = "Elements daemon providing access to the Liquid sidechain"; requires = [ "bitcoind.service" ]; @@ -200,7 +200,7 @@ in { Type = "simple"; User = "${cfg.user}"; Group = "${cfg.group}"; - ExecStart = "${pkgs.elementsd}/bin/elementsd ${cmdlineOptions}"; + ExecStart = "${pkgs.nix-bitcoin.elementsd}/bin/elementsd ${cmdlineOptions}"; StateDirectory = "liquidd"; PIDFile = "${pidFile}"; Restart = "on-failure"; diff --git a/modules/lnd.nix b/modules/lnd.nix index 3806477..f7794de 100644 --- a/modules/lnd.nix +++ b/modules/lnd.nix @@ -102,7 +102,7 @@ in { systemd.services.lnd = { description = "Run LND"; - path = [ pkgs.blockchains.bitcoind ]; + path = [ pkgs.nix-bitcoin.bitcoind ]; wantedBy = [ "multi-user.target" ]; requires = [ "bitcoind.service" ]; after = [ "bitcoind.service" ]; @@ -115,7 +115,7 @@ in { ''; serviceConfig = { PermissionsStartOnly = "true"; - ExecStart = "${pkgs.lnd}/bin/lnd --configfile=${cfg.dataDir}/lnd.conf"; + ExecStart = "${pkgs.nix-bitcoin.lnd}/bin/lnd --configfile=${cfg.dataDir}/lnd.conf"; ExecStartPost = "${pkgs.bash}/bin/bash ${init-lnd-wallet-script}"; User = "lnd"; Restart = "on-failure"; diff --git a/modules/modules.nix b/modules/modules.nix index 7dba3c6..1955528 100644 --- a/modules/modules.nix +++ b/modules/modules.nix @@ -27,5 +27,7 @@ in { disabledModules = [ "services/networking/bitcoind.nix" ]; - nixpkgs.overlays = [ (self: super: allPackages super) ]; + nixpkgs.overlays = [ (self: super: { + nix-bitcoin = allPackages super; + }) ]; } diff --git a/modules/nanopos.nix b/modules/nanopos.nix index 9fb5337..f21b1a0 100644 --- a/modules/nanopos.nix +++ b/modules/nanopos.nix @@ -65,7 +65,7 @@ in { after = [ "lightning-charge.service" ]; serviceConfig = { EnvironmentFile = "/secrets/lightning-charge-api-token-for-nanopos"; - ExecStart = "${pkgs.nanopos}/bin/nanopos -y ${cfg.itemsFile} -p ${toString cfg.port} --show-bolt11"; + ExecStart = "${pkgs.nix-bitcoin.nanopos}/bin/nanopos -y ${cfg.itemsFile} -p ${toString cfg.port} --show-bolt11"; User = "nanopos"; Restart = "on-failure"; diff --git a/modules/nix-bitcoin-webindex.nix b/modules/nix-bitcoin-webindex.nix index 96849bf..b69e1ff 100644 --- a/modules/nix-bitcoin-webindex.nix +++ b/modules/nix-bitcoin-webindex.nix @@ -74,7 +74,7 @@ in { description = "Get node info"; wantedBy = [ "multi-user.target" ]; after = [ "nodeinfo.service" ]; - path = [ pkgs.nodeinfo pkgs.clightning pkgs.jq pkgs.sudo ]; + path = with pkgs; [ nix-bitcoin.nodeinfo nix-bitcoin.clightning jq sudo ]; serviceConfig = { ExecStart="${pkgs.bash}/bin/bash ${createWebIndex}"; User = "root"; diff --git a/modules/nix-bitcoin.nix b/modules/nix-bitcoin.nix index 2ae3ac4..9013726 100644 --- a/modules/nix-bitcoin.nix +++ b/modules/nix-bitcoin.nix @@ -167,9 +167,9 @@ in { }]; version = 3; }; - environment.systemPackages = with pkgs; [ + environment.systemPackages = with pkgs; with nix-bitcoin; [ tor - blockchains.bitcoind + bitcoind (hiPrio config.services.bitcoind.cli) nodeinfo jq diff --git a/modules/recurring-donations.nix b/modules/recurring-donations.nix index 1f9e491..c258bff 100644 --- a/modules/recurring-donations.nix +++ b/modules/recurring-donations.nix @@ -82,7 +82,7 @@ in { description = "Run recurring-donations"; requires = [ "clightning.service" ]; after = [ "clightning.service" ]; - path = [ pkgs.clightning pkgs.curl pkgs.torsocks pkgs.sudo pkgs.jq ]; + path = with pkgs; [ nix-bitcoin.clightning curl torsocks sudo jq ]; serviceConfig = { ExecStart = "${pkgs.bash}/bin/bash ${recurring-donations-script}"; # TODO: would be better if this was operator, but I don't get sudo diff --git a/modules/spark-wallet.nix b/modules/spark-wallet.nix index e978c7c..3de3249 100644 --- a/modules/spark-wallet.nix +++ b/modules/spark-wallet.nix @@ -8,7 +8,7 @@ let dataDir = "/var/lib/spark-wallet/"; onion-chef-service = (if cfg.onion-service then [ "onion-chef.service" ] else []); run-spark-wallet = pkgs.writeScript "run-spark-wallet" '' - CMD="${pkgs.spark-wallet}/bin/spark-wallet --ln-path ${cfg.ln-path} -Q -k -c /secrets/spark-wallet-login" + CMD="${pkgs.nix-bitcoin.spark-wallet}/bin/spark-wallet --ln-path ${cfg.ln-path} -Q -k -c /secrets/spark-wallet-login" ${optionalString cfg.onion-service '' echo Getting onion hostname From 7aaf30501c570c31971f4a04bdfd7a9fa9217aeb Mon Sep 17 00:00:00 2001 From: Erik Arvstedt Date: Wed, 27 Nov 2019 14:04:22 +0100 Subject: [PATCH 08/42] nix-bitcoin-services: simplify formatting --- modules/nix-bitcoin-services.nix | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/modules/nix-bitcoin-services.nix b/modules/nix-bitcoin-services.nix index da401e6..e25311d 100644 --- a/modules/nix-bitcoin-services.nix +++ b/modules/nix-bitcoin-services.nix @@ -4,8 +4,7 @@ { config, lib, pkgs, ... }: with lib; - -let +{ defaultHardening = { PrivateTmp = "true"; ProtectSystem = "full"; @@ -23,9 +22,7 @@ let SystemCallFilter= "accept accept4 access adjtimex alarm bind brk capget capset chdir chmod chown chown32 clock_getres clock_gettime clock_nanosleep close connect copy_file_range creat dup dup2 dup3 epoll_create epoll_create1 epoll_ctl epoll_ctl_old epoll_pwait epoll_wait epoll_wait_old eventfd eventfd2 execve execveat exit exit_group faccessat fadvise64 fadvise64_64 fallocate fanotify_mark fchdir fchmod fchmodat fchown fchown32 fchownat fcntl fcntl64 fdatasync fgetxattr flistxattr flock fork fremovexattr fsetxattr fstat fstat64 fstatat64 fstatfs fstatfs64 fsync ftruncate ftruncate64 futex futimesat getcpu getcwd getdents getdents64 getegid getegid32 geteuid geteuid32 getgid getgid32 getgroups getgroups32 getitimer getpeername getpgid getpgrp getpid getppid getpriority getrandom getresgid getresgid32 getresuid getresuid32 getrlimit get_robust_list getrusage getsid getsockname getsockopt get_thread_area gettid gettimeofday getuid getuid32 getxattr inotify_add_watch inotify_init inotify_init1 inotify_rm_watch io_cancel ioctl io_destroy io_getevents io_pgetevents ioprio_get ioprio_set io_setup io_submit ipc kill lchown lchown32 lgetxattr link linkat listen listxattr llistxattr _llseek lremovexattr lseek lsetxattr lstat lstat64 madvise memfd_create mincore mkdir mkdirat mknod mknodat mlock mlock2 mlockall mmap mmap2 mprotect mq_getsetattr mq_notify mq_open mq_timedreceive mq_timedsend mq_unlink mremap msgctl msgget msgrcv msgsnd msync munlock munlockall munmap nanosleep newfstatat _newselect open openat pause pipe pipe2 poll ppoll prctl pread64 preadv preadv2 prlimit64 pselect6 pwrite64 pwritev pwritev2 read readahead readlink readlinkat readv recv recvfrom recvmmsg recvmsg remap_file_pages removexattr rename renameat renameat2 restart_syscall rmdir rt_sigaction rt_sigpending rt_sigprocmask rt_sigqueueinfo rt_sigreturn rt_sigsuspend rt_sigtimedwait rt_tgsigqueueinfo sched_getaffinity sched_getattr sched_getparam sched_get_priority_max sched_get_priority_min sched_getscheduler sched_rr_get_interval sched_setaffinity sched_setattr sched_setparam sched_setscheduler sched_yield seccomp select semctl semget semop semtimedop send sendfile sendfile64 sendmmsg sendmsg sendto setfsgid setfsgid32 setfsuid setfsuid32 setgid setgid32 setgroups setgroups32 setitimer setpgid setpriority setregid setregid32 setresgid setresgid32 setresuid setresuid32 setreuid setreuid32 setrlimit set_robust_list setsid setsockopt set_thread_area set_tid_address setuid setuid32 setxattr shmat shmctl shmdt shmget shutdown sigaltstack signalfd signalfd4 sigreturn socket socketcall socketpair splice stat stat64 statfs statfs64 statx symlink symlinkat sync sync_file_range syncfs sysinfo tee tgkill time timer_create timer_delete timerfd_create timerfd_gettime timerfd_settime timer_getoverrun timer_gettime timer_settime times tkill truncate truncate64 ugetrlimit umask uname unlink unlinkat utime utimensat utimes vfork vmsplice wait4 waitid waitpid write writev arm_fadvise64_64 arm_sync_file_range sync_file_range2 breakpoint cacheflush set_tls arch_prctl modify_ldt clone"; SystemCallArchitectures= "native"; }; -in -{ - inherit defaultHardening; + # nodejs applications apparently rely on memory write execute nodejs = { MemoryDenyWriteExecute = "false"; }; # Allow tor traffic. Allow takes precedence over Deny. From f0a36fe0c7415272db92f772733dc4d97b57cc58 Mon Sep 17 00:00:00 2001 From: Erik Arvstedt Date: Wed, 27 Nov 2019 14:04:23 +0100 Subject: [PATCH 09/42] add 'nix-bitcoin-services' option 1. Makes the content easily accessible for module users 2. Avoids needlessly recalculating the attrset in every client module --- modules/bitcoind.nix | 2 +- modules/clightning.nix | 2 +- modules/electrs.nix | 2 +- modules/lightning-charge.nix | 2 +- modules/liquid.nix | 2 +- modules/lnd.nix | 2 +- modules/modules.nix | 17 +++++++++++++---- modules/nanopos.nix | 2 +- modules/nix-bitcoin-services.nix | 2 +- modules/nix-bitcoin-webindex.nix | 2 +- modules/onion-chef.nix | 2 +- modules/recurring-donations.nix | 2 +- modules/spark-wallet.nix | 2 +- 13 files changed, 25 insertions(+), 16 deletions(-) diff --git a/modules/bitcoind.nix b/modules/bitcoind.nix index 55840c9..eab5b40 100644 --- a/modules/bitcoind.nix +++ b/modules/bitcoind.nix @@ -3,8 +3,8 @@ with lib; let - nix-bitcoin-services = pkgs.callPackage ./nix-bitcoin-services.nix { }; cfg = config.services.bitcoind; + inherit (config) nix-bitcoin-services; pidFile = "${cfg.dataDir}/bitcoind.pid"; configFile = pkgs.writeText "bitcoin.conf" '' ${optionalString cfg.testnet "testnet=1"} diff --git a/modules/clightning.nix b/modules/clightning.nix index 4e68d8b..07457b5 100644 --- a/modules/clightning.nix +++ b/modules/clightning.nix @@ -3,8 +3,8 @@ with lib; let - nix-bitcoin-services = pkgs.callPackage ./nix-bitcoin-services.nix { }; cfg = config.services.clightning; + inherit (config) nix-bitcoin-services; configFile = pkgs.writeText "config" '' autolisten=${if cfg.autolisten then "true" else "false"} network=bitcoin diff --git a/modules/electrs.nix b/modules/electrs.nix index 1ad1f09..fcb87db 100644 --- a/modules/electrs.nix +++ b/modules/electrs.nix @@ -3,8 +3,8 @@ with lib; let - nix-bitcoin-services = pkgs.callPackage ./nix-bitcoin-services.nix { }; cfg = config.services.electrs; + inherit (config) nix-bitcoin-services; index-batch-size = "${if cfg.high-memory then "" else "--index-batch-size=10"}"; jsonrpc-import = "${if cfg.high-memory then "" else "--jsonrpc-import"}"; in { diff --git a/modules/lightning-charge.nix b/modules/lightning-charge.nix index d22e7ac..e3eeafc 100644 --- a/modules/lightning-charge.nix +++ b/modules/lightning-charge.nix @@ -3,8 +3,8 @@ with lib; let - nix-bitcoin-services = pkgs.callPackage ./nix-bitcoin-services.nix { }; cfg = config.services.lightning-charge; + inherit (config) nix-bitcoin-services; in { options.services.lightning-charge = { enable = mkOption { diff --git a/modules/liquid.nix b/modules/liquid.nix index 0298413..042db0e 100644 --- a/modules/liquid.nix +++ b/modules/liquid.nix @@ -3,8 +3,8 @@ with lib; let - nix-bitcoin-services = pkgs.callPackage ./nix-bitcoin-services.nix { }; cfg = config.services.liquidd; + inherit (config) nix-bitcoin-services; pidFile = "${cfg.dataDir}/liquidd.pid"; configFile = pkgs.writeText "elements.conf" '' chain=liquidv1 diff --git a/modules/lnd.nix b/modules/lnd.nix index f7794de..b41c21e 100644 --- a/modules/lnd.nix +++ b/modules/lnd.nix @@ -3,8 +3,8 @@ with lib; let - nix-bitcoin-services = pkgs.callPackage ./nix-bitcoin-services.nix { }; cfg = config.services.lnd; + inherit (config) nix-bitcoin-services; configFile = pkgs.writeText "lnd.conf" '' datadir=${cfg.dataDir} logdir=${cfg.dataDir}/logs diff --git a/modules/modules.nix b/modules/modules.nix index 1955528..9509e5b 100644 --- a/modules/modules.nix +++ b/modules/modules.nix @@ -1,4 +1,4 @@ -{ config, pkgs, ... }: +{ config, pkgs, lib, ... }: let nixpkgs-pinned = import ../pkgs/nixpkgs-pinned.nix; unstable = import nixpkgs-pinned.nixpkgs-unstable {}; @@ -27,7 +27,16 @@ in { disabledModules = [ "services/networking/bitcoind.nix" ]; - nixpkgs.overlays = [ (self: super: { - nix-bitcoin = allPackages super; - }) ]; + options = { + nix-bitcoin-services = lib.mkOption { + readOnly = true; + default = import ./nix-bitcoin-services.nix lib; + }; + }; + + config = { + nixpkgs.overlays = [ (self: super: { + nix-bitcoin = allPackages super; + }) ]; + }; } diff --git a/modules/nanopos.nix b/modules/nanopos.nix index f21b1a0..cbfdc0c 100644 --- a/modules/nanopos.nix +++ b/modules/nanopos.nix @@ -3,8 +3,8 @@ with lib; let - nix-bitcoin-services = pkgs.callPackage ./nix-bitcoin-services.nix { }; cfg = config.services.nanopos; + inherit (config) nix-bitcoin-services; defaultItemsFile = pkgs.writeText "items.yaml" '' tea: price: 0.02 # denominated in the currency specified by --currency diff --git a/modules/nix-bitcoin-services.nix b/modules/nix-bitcoin-services.nix index e25311d..c04e14c 100644 --- a/modules/nix-bitcoin-services.nix +++ b/modules/nix-bitcoin-services.nix @@ -1,7 +1,7 @@ # See `man systemd.exec` and `man systemd.resource-control` for an explanation # of the various systemd options available through this module. -{ config, lib, pkgs, ... }: +lib: with lib; { diff --git a/modules/nix-bitcoin-webindex.nix b/modules/nix-bitcoin-webindex.nix index b69e1ff..1f2815b 100644 --- a/modules/nix-bitcoin-webindex.nix +++ b/modules/nix-bitcoin-webindex.nix @@ -3,8 +3,8 @@ with lib; let - nix-bitcoin-services = pkgs.callPackage ./nix-bitcoin-services.nix { }; cfg = config.services.nix-bitcoin-webindex; + inherit (config) nix-bitcoin-services; indexFile = pkgs.writeText "index.html" '' diff --git a/modules/onion-chef.nix b/modules/onion-chef.nix index c222015..0ec05c0 100644 --- a/modules/onion-chef.nix +++ b/modules/onion-chef.nix @@ -8,8 +8,8 @@ with lib; let - nix-bitcoin-services = pkgs.callPackage ./nix-bitcoin-services.nix { }; 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 diff --git a/modules/recurring-donations.nix b/modules/recurring-donations.nix index c258bff..f1ef5f9 100644 --- a/modules/recurring-donations.nix +++ b/modules/recurring-donations.nix @@ -3,8 +3,8 @@ with lib; let - nix-bitcoin-services = pkgs.callPackage ./nix-bitcoin-services.nix { }; cfg = config.services.recurring-donations; + inherit (config) nix-bitcoin-services; recurring-donations-script = pkgs.writeScript "recurring-donations.sh" '' LNCLI="lightning-cli --lightning-dir=${config.services.clightning.dataDir}" pay_tallycoin() { diff --git a/modules/spark-wallet.nix b/modules/spark-wallet.nix index 3de3249..7717404 100644 --- a/modules/spark-wallet.nix +++ b/modules/spark-wallet.nix @@ -3,8 +3,8 @@ with lib; let - nix-bitcoin-services = pkgs.callPackage ./nix-bitcoin-services.nix { }; cfg = config.services.spark-wallet; + inherit (config) nix-bitcoin-services; dataDir = "/var/lib/spark-wallet/"; onion-chef-service = (if cfg.onion-service then [ "onion-chef.service" ] else []); run-spark-wallet = pkgs.writeScript "run-spark-wallet" '' From cd0fd6926ba6f1223a3dc3d41dc49380853fb752 Mon Sep 17 00:00:00 2001 From: Erik Arvstedt Date: Wed, 27 Nov 2019 14:04:24 +0100 Subject: [PATCH 10/42] don't copy secret files to store during nixops deployment --- network/network.nix | 8 ++++---- pkgs/nixops/release.nix.patch | 11 +++++++++++ 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/network/network.nix b/network/network.nix index f8ae92b..4e2b0c2 100644 --- a/network/network.nix +++ b/network/network.nix @@ -44,28 +44,28 @@ let permissions = "0440"; }; nginx_key = { - keyFile = ../secrets/nginx.key; + keyFile = toString ../../secrets/nginx.key; destDir = "/secrets/"; user = "nginx"; group = "root"; permissions = "0440"; }; nginx_cert = { - keyFile = ../secrets/nginx.cert; + keyFile = toString ../../secrets/nginx.cert; destDir = "/secrets/"; user = "nginx"; group = "root"; permissions = "0440"; }; lnd_key = { - keyFile = ../secrets/lnd.key; + keyFile = toString ../../secrets/lnd.key; destDir = "/secrets/"; user = "lnd"; group = "lnd"; permissions = "0440"; }; lnd_cert = { - keyFile = ../secrets/lnd.cert; + keyFile = toString ../../secrets/lnd.cert; destDir = "/secrets/"; user = "lnd"; group = "lnd"; diff --git a/pkgs/nixops/release.nix.patch b/pkgs/nixops/release.nix.patch index 91a81ab..c54432a 100644 --- a/pkgs/nixops/release.nix.patch +++ b/pkgs/nixops/release.nix.patch @@ -32,3 +32,14 @@ # For "nix-build --run-env". + +--- a/nixops/backends/__init__.py ++++ b/nixops/backends/__init__.py +@@ -24,6 +24,7 @@ class MachineDefinition(nixops.resources.ResourceDefinition): + opts = {} + for (key, xmlType) in (('text', 'string'), + ('keyFile', 'path'), ++ ('keyFile', 'string'), + ('destDir', 'string'), + ('user', 'string'), + ('group', 'string'), From f9c29b9318f4c20f4bf8a132f533b9b3e98886b4 Mon Sep 17 00:00:00 2001 From: Erik Arvstedt Date: Wed, 27 Nov 2019 14:04:25 +0100 Subject: [PATCH 11/42] simplify secret definitions --- network/network.nix | 55 +++++++++++++++------------------------------ 1 file changed, 18 insertions(+), 37 deletions(-) diff --git a/network/network.nix b/network/network.nix index 4e2b0c2..c73c3c8 100644 --- a/network/network.nix +++ b/network/network.nix @@ -1,75 +1,56 @@ let secrets = import ../secrets/secrets.nix; - bitcoin-rpcpassword = { + + secretsDir = "/secrets/"; + secret = { text ? null, keyFile ? null, user, group ? user }: { + inherit text user group; + destDir = secretsDir; + permissions = "0440"; + }; + + bitcoin-rpcpassword = secret { text = secrets.bitcoinrpcpassword; - destDir = "/secrets/"; user = "bitcoin"; group = "bitcoinrpc"; - permissions = "0440"; }; - lnd-wallet-password = { + lnd-wallet-password = secret { text = secrets.lnd-wallet-password; - destDir = "/secrets/"; user = "lnd"; - group = "lnd"; - permissions = "0440"; }; - lightning-charge-api-token = { + lightning-charge-api-token = secret { text = "API_TOKEN=" + secrets.lightning-charge-api-token; - destDir = "/secrets/"; user = "clightning"; - group = "clightning"; - permissions = "0440"; }; # variable is called CHARGE_TOKEN instead of API_TOKEN - lightning-charge-api-token-for-nanopos = { + lightning-charge-api-token-for-nanopos = secret { text = "CHARGE_TOKEN=" + secrets.lightning-charge-api-token; - destDir = "/secrets/"; user = "nanopos"; - group = "nanopos"; - permissions = "0440"; }; - liquid-rpcpassword = { + liquid-rpcpassword = secret { text = secrets.liquidrpcpassword; - destDir = "/secrets/"; user = "liquid"; - group = "liquid"; - permissions = "0440"; }; - spark-wallet-login = { + spark-wallet-login = secret { text = "login=" + "spark-wallet:" + secrets.spark-wallet-password; - destDir = "/secrets/"; user = "clightning"; - group = "clightning"; - permissions = "0440"; }; - nginx_key = { + nginx_key = secret { keyFile = toString ../../secrets/nginx.key; - destDir = "/secrets/"; user = "nginx"; group = "root"; - permissions = "0440"; }; - nginx_cert = { + nginx_cert = secret { keyFile = toString ../../secrets/nginx.cert; - destDir = "/secrets/"; user = "nginx"; group = "root"; - permissions = "0440"; }; - lnd_key = { + lnd_key = secret { keyFile = toString ../../secrets/lnd.key; - destDir = "/secrets/"; user = "lnd"; - group = "lnd"; - permissions = "0440"; }; - lnd_cert = { + lnd_cert = secret { keyFile = toString ../../secrets/lnd.cert; - destDir = "/secrets/"; user = "lnd"; - group = "lnd"; - permissions = "0440"; }; in { network.description = "Bitcoin Core node"; From 437b268433e7311f47de63ded2c066fe1a8c3567 Mon Sep 17 00:00:00 2001 From: Erik Arvstedt Date: Wed, 27 Nov 2019 14:04:26 +0100 Subject: [PATCH 12/42] extract make-secrets.nix Needed by the next commit. --- modules/secrets/make-secrets.nix | 68 +++++++++++++++++++++++++++++++ network/network.nix | 69 +++----------------------------- 2 files changed, 73 insertions(+), 64 deletions(-) create mode 100644 modules/secrets/make-secrets.nix diff --git a/modules/secrets/make-secrets.nix b/modules/secrets/make-secrets.nix new file mode 100644 index 0000000..fa9a070 --- /dev/null +++ b/modules/secrets/make-secrets.nix @@ -0,0 +1,68 @@ +{ secretsFile ? null, config ? null }: +let + secrets = import secretsFile; + secretsDir = "/secrets/"; + secret = { text ? null, keyFile ? null, user, group ? user }: { + inherit text keyFile user group; + destDir = secretsDir; + permissions = "0440"; + }; +in rec { + allSecrets = { + bitcoin-rpcpassword = secret { + text = secrets.bitcoinrpcpassword; + user = "bitcoin"; + group = "bitcoinrpc"; + }; + lnd-wallet-password = secret { + text = secrets.lnd-wallet-password; + user = "lnd"; + }; + lightning-charge-api-token = secret { + text = "API_TOKEN=" + secrets.lightning-charge-api-token; + user = "clightning"; + }; + # variable is called CHARGE_TOKEN instead of API_TOKEN + lightning-charge-api-token-for-nanopos = secret { + text = "CHARGE_TOKEN=" + secrets.lightning-charge-api-token; + user = "nanopos"; + }; + liquid-rpcpassword = secret { + text = secrets.liquidrpcpassword; + user = "liquid"; + }; + spark-wallet-login = secret { + text = "login=" + "spark-wallet:" + secrets.spark-wallet-password; + user = "clightning"; + }; + nginx_key = secret { + keyFile = toString ../../secrets/nginx.key; + user = "nginx"; + group = "root"; + }; + nginx_cert = secret { + keyFile = toString ../../secrets/nginx.cert; + user = "nginx"; + group = "root"; + }; + lnd_key = secret { + keyFile = toString ../../secrets/lnd.key; + user = "lnd"; + }; + lnd_cert = secret { + keyFile = toString ../../secrets/lnd.cert; + user = "lnd"; + }; + }; + + activeSecrets = let + secretsFor = service: attrs: if service.enable then attrs else {}; + in with allSecrets; + (secretsFor config.services.bitcoind { inherit bitcoin-rpcpassword; }) + // (secretsFor config.services.lnd { inherit lnd-wallet-password lnd_key lnd_cert; }) + // (secretsFor config.services.lightning-charge { inherit lightning-charge-api-token; }) + // (secretsFor config.services.nanopos { inherit lightning-charge-api-token-for-nanopos; }) + // (secretsFor config.services.liquidd { inherit liquid-rpcpassword; }) + // (secretsFor config.services.spark-wallet { inherit spark-wallet-login; }) + // (secretsFor config.services.electrs { inherit nginx_key nginx_cert; }); +} diff --git a/network/network.nix b/network/network.nix index c73c3c8..269ed45 100644 --- a/network/network.nix +++ b/network/network.nix @@ -1,73 +1,14 @@ -let - secrets = import ../secrets/secrets.nix; - - secretsDir = "/secrets/"; - secret = { text ? null, keyFile ? null, user, group ? user }: { - inherit text user group; - destDir = secretsDir; - permissions = "0440"; - }; - - bitcoin-rpcpassword = secret { - text = secrets.bitcoinrpcpassword; - user = "bitcoin"; - group = "bitcoinrpc"; - }; - lnd-wallet-password = secret { - text = secrets.lnd-wallet-password; - user = "lnd"; - }; - lightning-charge-api-token = secret { - text = "API_TOKEN=" + secrets.lightning-charge-api-token; - user = "clightning"; - }; - # variable is called CHARGE_TOKEN instead of API_TOKEN - lightning-charge-api-token-for-nanopos = secret { - text = "CHARGE_TOKEN=" + secrets.lightning-charge-api-token; - user = "nanopos"; - }; - liquid-rpcpassword = secret { - text = secrets.liquidrpcpassword; - user = "liquid"; - }; - spark-wallet-login = secret { - text = "login=" + "spark-wallet:" + secrets.spark-wallet-password; - user = "clightning"; - }; - nginx_key = secret { - keyFile = toString ../../secrets/nginx.key; - user = "nginx"; - group = "root"; - }; - nginx_cert = secret { - keyFile = toString ../../secrets/nginx.cert; - user = "nginx"; - group = "root"; - }; - lnd_key = secret { - keyFile = toString ../../secrets/lnd.key; - user = "lnd"; - }; - lnd_cert = secret { - keyFile = toString ../../secrets/lnd.cert; - user = "lnd"; - }; -in { +{ network.description = "Bitcoin Core node"; bitcoin-node = { config, pkgs, lib, ... }: { imports = [ ../configuration.nix ]; - deployment.keys = { - inherit bitcoin-rpcpassword; - } - // (if (config.services.lnd.enable) then { inherit lnd-wallet-password lnd_key lnd_cert; } else { }) - // (if (config.services.lightning-charge.enable) then { inherit lightning-charge-api-token; } else { }) - // (if (config.services.nanopos.enable) then { inherit lightning-charge-api-token-for-nanopos; } else { }) - // (if (config.services.liquidd.enable) then { inherit liquid-rpcpassword; } else { }) - // (if (config.services.spark-wallet.enable) then { inherit spark-wallet-login; } else { }) - // (if (config.services.electrs.enable) then { inherit nginx_key nginx_cert; } else { }); + deployment.keys = (import ../modules/secrets/make-secrets.nix { + inherit config; + secretsFile = ../secrets/secrets.nix; + }).activeSecrets; # nixops makes the secrets directory accessible only for users with group 'key'. # For compatibility with other deployment methods besides nixops, we forego the From e3b47ce18a2183bc25c60072b5bf761e6ae4a51d Mon Sep 17 00:00:00 2001 From: Erik Arvstedt Date: Wed, 27 Nov 2019 14:04:27 +0100 Subject: [PATCH 13/42] add setup-secrets.service --- .gitignore | 2 +- modules/modules.nix | 1 + modules/secrets/setup-secrets.nix | 94 +++++++++++++++++++++++++++++++ 3 files changed, 96 insertions(+), 1 deletion(-) create mode 100644 modules/secrets/setup-secrets.nix diff --git a/.gitignore b/.gitignore index 4bd922a..5598d80 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1 @@ -secrets/ +/secrets/ diff --git a/modules/modules.nix b/modules/modules.nix index 9509e5b..27c67c6 100644 --- a/modules/modules.nix +++ b/modules/modules.nix @@ -23,6 +23,7 @@ in { ./recurring-donations.nix ./hardware-wallets.nix ./lnd.nix + ./secrets/setup-secrets.nix ]; disabledModules = [ "services/networking/bitcoind.nix" ]; diff --git a/modules/secrets/setup-secrets.nix b/modules/secrets/setup-secrets.nix new file mode 100644 index 0000000..13393df --- /dev/null +++ b/modules/secrets/setup-secrets.nix @@ -0,0 +1,94 @@ +{ config, pkgs, lib, ... }: + +with lib; +let + secretsDir = "/secrets/"; # TODO: make this an option + + secrets = (import ./make-secrets.nix { inherit config; }).activeSecrets; + + setupSecrets = concatStrings (mapAttrsToList (n: v: '' + setupSecret ${n} ${v.user} ${v.group} ${v.permissions} ${optionalString (v.keyFile != null) (baseNameOf v.keyFile)} + '') secrets); +in +{ + options.nix-bitcoin.setup-secrets = mkEnableOption "Set permissions for secrets generated by 'generate-secrets.sh'"; + + config = mkIf config.nix-bitcoin.setup-secrets { + systemd.targets.nix-bitcoin-secrets = { + requires = [ "setup-secrets.service" ]; + after = [ "setup-secrets.service" ]; + }; + + # Operation of this service: + # - Create missing secrets that are composed of attrs from secrets.nix + # - Set owner and permissions for all used secrets + # - Make all other secrets accessible to root only + # For all steps make sure that no secrets are copied to the nix-store. + # + systemd.services.setup-secrets = { + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + } // config.nix-bitcoin-services.defaultHardening; + script = '' + setupSecret() { + file="$1" + user="$2" + group="$3" + permissions="$4" + srcFile="$5" + if [[ ! -e $file ]]; then + if [[ $srcFile ]]; then + if [[ ! -e $srcFile ]]; then + echo "Error: Secret source file '$srcFile' is missing" + exit 1 + fi + mv "$srcFile" "$file" + else + createFile "$file" + fi + fi + chown "$user:$group" "$file" + chmod "$permissions" "$file" + processedFiles+=("$file") + } + + createFile() { + file="$1" + # 'nix eval' requires filesystem or daemon access to a store even if nothing is built. + # Use a private store so that 'nix eval' always succeeds regardless of the + # execution environment, like a container. + + # This tmp dir is automatically removed by systemd via PrivateTmp + [[ $store ]] || store="$(mktemp -d)" + secretsFile="$(realpath secrets.nix)" \ + ${pkgs.nix}/bin/nix eval --raw --store "$store" "( + (import ${./make-secrets.nix} { + secretsFile = builtins.getEnv \"secretsFile\"; + }).allSecrets.$file.text + )" > "$file" + } + + dir="${secretsDir}" + if [[ ! -e $dir ]]; then + echo "Error: Secrets dir '$dir' is missing" + exit 1 + fi + chown root: "$dir" + cd "$dir" + + processedFiles=() + ${setupSecrets} + + # Make all other files accessible to root only + unprocessedFiles=$(comm -23 <(printf '%s\n' *) <(printf '%s\n' "''${processedFiles[@]}" | sort)) + IFS=$'\n' + chown root: $unprocessedFiles + chmod 0440 $unprocessedFiles + + # Now make the secrets dir accessible to other users + chmod 0751 "$dir" + ''; + }; + }; +} From 51fb0540017a1ae4a274ac5f24279799cde46f12 Mon Sep 17 00:00:00 2001 From: Erik Arvstedt Date: Wed, 27 Nov 2019 14:04:28 +0100 Subject: [PATCH 14/42] generate_secrets.sh: extract makepw command This makes it obvious that all passwords are generated with the same parameters --- secrets/generate_secrets.sh | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/secrets/generate_secrets.sh b/secrets/generate_secrets.sh index 3b5f15b..4cfdb6d 100755 --- a/secrets/generate_secrets.sh +++ b/secrets/generate_secrets.sh @@ -4,13 +4,14 @@ SECRETSFILE=secrets/secrets.nix if [ ! -e "$SECRETSFILE" ]; then echo Write secrets to $SECRETSFILE + makepw="apg -m 20 -x 20 -M Ncl -n 1" { echo \{ - echo " bitcoinrpcpassword = \"$(apg -m 20 -x 20 -M Ncl -n 1)\";" - echo " lnd-wallet-password = \"$(apg -m 20 -x 20 -M Ncl -n 1)\";" - echo " lightning-charge-api-token = \"$(apg -m 20 -x 20 -M Ncl -n 1)\";" - echo " liquidrpcpassword = \"$(apg -m 20 -x 20 -M Ncl -n 1)\";" - echo " spark-wallet-password = \"$(apg -m 20 -x 20 -M Ncl -n 1)\";" + echo " bitcoinrpcpassword = \"$($makepw)\";" + echo " lnd-wallet-password = \"$($makepw)\";" + echo " lightning-charge-api-token = \"$($makepw)\";" + echo " liquidrpcpassword = \"$($makepw)\";" + echo " spark-wallet-password = \"$($makepw)\";" echo \} } >> $SECRETSFILE echo Done From 9d14d5ba64a95c1154ec70c2d91440ddf16250d5 Mon Sep 17 00:00:00 2001 From: Erik Arvstedt Date: Wed, 27 Nov 2019 14:04:29 +0100 Subject: [PATCH 15/42] generate_secrets.sh: write secrets to working directory Write to $PWD instead to ./secrets. 1. Simplifies the code 2. Easier to use in generate-secrets.service (introduced in a later commit) --- secrets/generate_secrets.sh | 22 +++++++++++----------- shell.nix | 2 +- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/secrets/generate_secrets.sh b/secrets/generate_secrets.sh index 4cfdb6d..5abd311 100755 --- a/secrets/generate_secrets.sh +++ b/secrets/generate_secrets.sh @@ -1,6 +1,6 @@ #!/bin/sh -SECRETSFILE=secrets/secrets.nix +SECRETSFILE=secrets.nix if [ ! -e "$SECRETSFILE" ]; then echo Write secrets to $SECRETSFILE @@ -19,23 +19,23 @@ else echo $SECRETSFILE already exists. Skipping. fi -if [ ! -e secrets/nginx.key ] || [ ! -e secrets/nginx.cert ]; then +if [ ! -e nginx.key ] || [ ! -e nginx.cert ]; then echo Generate Nginx Self-Signed Cert - openssl genrsa -out secrets/nginx.key 2048 - openssl req -new -key secrets/nginx.key -out secrets/nginx.csr -subj "/C=KN" - openssl x509 -req -days 1825 -in secrets/nginx.csr -signkey secrets/nginx.key -out secrets/nginx.cert - rm secrets/nginx.csr + openssl genrsa -out nginx.key 2048 + openssl req -new -key nginx.key -out nginx.csr -subj "/C=KN" + openssl x509 -req -days 1825 -in nginx.csr -signkey nginx.key -out nginx.cert + rm nginx.csr echo Done else echo Nginx Cert already exists. Skipping. fi -if [ ! -e secrets/lnd.key ] || [ ! -e secrets/lnd.cert ]; then +if [ ! -e lnd.key ] || [ ! -e lnd.cert ]; then echo Generate LND compatible TLS Cert - openssl ecparam -genkey -name prime256v1 -out secrets/lnd.key - openssl req -config secrets/openssl.cnf -new -sha256 -key secrets/lnd.key -out secrets/lnd.csr -subj '/CN=localhost/O=lnd' - openssl req -config secrets/openssl.cnf -x509 -sha256 -days 1825 -key secrets/lnd.key -in secrets/lnd.csr -out secrets/lnd.cert - rm secrets/lnd.csr + openssl ecparam -genkey -name prime256v1 -out lnd.key + openssl req -config openssl.cnf -new -sha256 -key lnd.key -out lnd.csr -subj '/CN=localhost/O=lnd' + openssl req -config openssl.cnf -x509 -sha256 -days 1825 -key lnd.key -in lnd.csr -out lnd.cert + rm lnd.csr echo Done else echo LND cert already exists. Skipping. diff --git a/shell.nix b/shell.nix index f48a5c5..3a4c7b5 100644 --- a/shell.nix +++ b/shell.nix @@ -18,6 +18,6 @@ stdenv.mkDerivation rec { # keys already added to my ssh-agent. export SSH_AUTH_SOCK="" figlet "nix-bitcoin" - ./secrets/generate_secrets.sh + (cd secrets; ./generate_secrets.sh) ''; } From e34093a8aca06bbd1107bf90990869250a9de32d Mon Sep 17 00:00:00 2001 From: Erik Arvstedt Date: Wed, 27 Nov 2019 14:04:30 +0100 Subject: [PATCH 16/42] generate_secrets.sh: add opensslConf option Needed for the following commit. --- secrets/generate_secrets.sh | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/secrets/generate_secrets.sh b/secrets/generate_secrets.sh index 5abd311..b569c3a 100755 --- a/secrets/generate_secrets.sh +++ b/secrets/generate_secrets.sh @@ -1,9 +1,10 @@ #!/bin/sh -SECRETSFILE=secrets.nix +opensslConf=${1:-openssl.cnf} +secretsFile=secrets.nix -if [ ! -e "$SECRETSFILE" ]; then - echo Write secrets to $SECRETSFILE +if [ ! -e "$secretsFile" ]; then + echo Write secrets to $secretsFile makepw="apg -m 20 -x 20 -M Ncl -n 1" { echo \{ @@ -13,10 +14,10 @@ if [ ! -e "$SECRETSFILE" ]; then echo " liquidrpcpassword = \"$($makepw)\";" echo " spark-wallet-password = \"$($makepw)\";" echo \} - } >> $SECRETSFILE + } >> $secretsFile echo Done else - echo $SECRETSFILE already exists. Skipping. + echo $secretsFile already exists. Skipping. fi if [ ! -e nginx.key ] || [ ! -e nginx.cert ]; then @@ -33,8 +34,8 @@ fi if [ ! -e lnd.key ] || [ ! -e lnd.cert ]; then echo Generate LND compatible TLS Cert openssl ecparam -genkey -name prime256v1 -out lnd.key - openssl req -config openssl.cnf -new -sha256 -key lnd.key -out lnd.csr -subj '/CN=localhost/O=lnd' - openssl req -config openssl.cnf -x509 -sha256 -days 1825 -key lnd.key -in lnd.csr -out lnd.cert + openssl req -config $opensslConf -new -sha256 -key lnd.key -out lnd.csr -subj '/CN=localhost/O=lnd' + openssl req -config $opensslConf -x509 -sha256 -days 1825 -key lnd.key -in lnd.csr -out lnd.cert rm lnd.csr echo Done else From 644769421481818025419fe1f4274f9dc49fbd24 Mon Sep 17 00:00:00 2001 From: Erik Arvstedt Date: Wed, 27 Nov 2019 14:04:31 +0100 Subject: [PATCH 17/42] add generate-secrets pkg generate-secrets.sh will also be used in generate-secrets.nix, so DRY its dependency definitions. --- pkgs/default.nix | 1 + pkgs/generate-secrets/default.nix | 6 ++++++ .../generate-secrets/generate-secrets.sh | 0 {secrets => pkgs/generate-secrets}/openssl.cnf | 0 shell.nix | 5 +++-- 5 files changed, 10 insertions(+), 2 deletions(-) create mode 100644 pkgs/generate-secrets/default.nix rename secrets/generate_secrets.sh => pkgs/generate-secrets/generate-secrets.sh (100%) rename {secrets => pkgs/generate-secrets}/openssl.cnf (100%) diff --git a/pkgs/default.nix b/pkgs/default.nix index cdde65d..2864369 100644 --- a/pkgs/default.nix +++ b/pkgs/default.nix @@ -9,4 +9,5 @@ hwi = pkgs.callPackage ./hwi { }; pylightning = pkgs.python3Packages.callPackage ./pylightning { }; liquid-swap = pkgs.python3Packages.callPackage ./liquid-swap { }; + generate-secrets = pkgs.callPackage ./generate-secrets { }; } diff --git a/pkgs/generate-secrets/default.nix b/pkgs/generate-secrets/default.nix new file mode 100644 index 0000000..8222057 --- /dev/null +++ b/pkgs/generate-secrets/default.nix @@ -0,0 +1,6 @@ +{ pkgs }: with pkgs; + +writeScript "generate-secrets" '' + export PATH=${lib.makeBinPath [ coreutils apg openssl ]} + . ${./generate-secrets.sh} ${./openssl.cnf} +'' diff --git a/secrets/generate_secrets.sh b/pkgs/generate-secrets/generate-secrets.sh similarity index 100% rename from secrets/generate_secrets.sh rename to pkgs/generate-secrets/generate-secrets.sh diff --git a/secrets/openssl.cnf b/pkgs/generate-secrets/openssl.cnf similarity index 100% rename from secrets/openssl.cnf rename to pkgs/generate-secrets/openssl.cnf diff --git a/shell.nix b/shell.nix index 3a4c7b5..769a8e8 100644 --- a/shell.nix +++ b/shell.nix @@ -7,8 +7,9 @@ stdenv.mkDerivation rec { name = "nix-bitcoin-environment"; nixops19_09 = callPackage ./pkgs/nixops {}; + generate-secrets = callPackage ./pkgs/generate-secrets {}; - buildInputs = with pkgs; [ nixops19_09 figlet apg openssl ]; + buildInputs = [ nixops19_09 figlet ]; shellHook = '' export NIX_PATH="nixpkgs=${nixpkgs}:." @@ -18,6 +19,6 @@ stdenv.mkDerivation rec { # keys already added to my ssh-agent. export SSH_AUTH_SOCK="" figlet "nix-bitcoin" - (cd secrets; ./generate_secrets.sh) + (mkdir -p secrets; cd secrets; ${generate-secrets}) ''; } From b90bf6691bf7f7bb53c1dd1ede188186bb141905 Mon Sep 17 00:00:00 2001 From: Erik Arvstedt Date: Wed, 27 Nov 2019 14:04:32 +0100 Subject: [PATCH 18/42] add generate-secrets.service --- modules/secrets/generate-secrets.nix | 29 ++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 modules/secrets/generate-secrets.nix diff --git a/modules/secrets/generate-secrets.nix b/modules/secrets/generate-secrets.nix new file mode 100644 index 0000000..f7edc03 --- /dev/null +++ b/modules/secrets/generate-secrets.nix @@ -0,0 +1,29 @@ +{ config, pkgs, lib, ... }: + +# This is mainly for testing. +# When using this for regular deployments, make sure to create a backup of the +# generated secrets. + +with lib; +let + secretsDir = "/secrets/"; # TODO: make this an option +in +{ + nix-bitcoin.setup-secrets = true; + + systemd.services.generate-secrets = { + requiredBy = [ "setup-secrets.service" ]; + before = [ "setup-secrets.service" ]; + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + } // config.nix-bitcoin-services.defaultHardening; + script = '' + mkdir -p "${secretsDir}" + cd "${secretsDir}" + chown root: . + chmod 0700 . + ${pkgs.nix-bitcoin.generate-secrets} + ''; + }; +} From 1833b158883cce8886cbeb21eab4e29ab267f3fb Mon Sep 17 00:00:00 2001 From: Erik Arvstedt Date: Wed, 27 Nov 2019 14:04:33 +0100 Subject: [PATCH 19/42] clightning: add cli option An executable is more robust to use than shell aliases. This is also a preparation for commit 'add module test' because the NixOS testing framework makes interactive aliases hard to use: It unsets 'PS1' which is used by programs/bash/bash.nix to detect interactive shells. --- modules/clightning.nix | 10 ++++++++++ modules/nix-bitcoin-webindex.nix | 7 ++++++- modules/nix-bitcoin.nix | 5 +---- modules/recurring-donations.nix | 2 +- pkgs/nodeinfo/nodeinfo.sh | 2 +- 5 files changed, 19 insertions(+), 7 deletions(-) diff --git a/modules/clightning.nix b/modules/clightning.nix index 07457b5..fb513b9 100644 --- a/modules/clightning.nix +++ b/modules/clightning.nix @@ -57,6 +57,16 @@ in { default = "/var/lib/clightning"; description = "The data directory for clightning."; }; + cli = mkOption { + readOnly = true; + default = pkgs.writeScriptBin "lightning-cli" + # Switch user because c-lightning doesn't allow setting the permissions of the rpc socket + # https://github.com/ElementsProject/lightning/issues/1366 + '' + exec sudo -u clightning ${pkgs.nix-bitcoin.clightning}/bin/lightning-cli --lightning-dir='${cfg.dataDir}' "$@" + ''; + description = "Binary to connect with the clightning instance."; + }; enforceTor = nix-bitcoin-services.enforceTor; }; diff --git a/modules/nix-bitcoin-webindex.nix b/modules/nix-bitcoin-webindex.nix index 1f2815b..b4d9621 100644 --- a/modules/nix-bitcoin-webindex.nix +++ b/modules/nix-bitcoin-webindex.nix @@ -74,7 +74,12 @@ in { description = "Get node info"; wantedBy = [ "multi-user.target" ]; after = [ "nodeinfo.service" ]; - path = with pkgs; [ nix-bitcoin.nodeinfo nix-bitcoin.clightning jq sudo ]; + path = with pkgs; [ + nix-bitcoin.nodeinfo + config.services.clightning.cli + jq + sudo + ]; serviceConfig = { ExecStart="${pkgs.bash}/bin/bash ${createWebIndex}"; User = "root"; diff --git a/modules/nix-bitcoin.nix b/modules/nix-bitcoin.nix index 9013726..5aee387 100644 --- a/modules/nix-bitcoin.nix +++ b/modules/nix-bitcoin.nix @@ -102,9 +102,6 @@ in { services.onion-chef.access.operator = [ "bitcoind" "clightning" "nginx" "liquidd" "spark-wallet" "electrs" "sshd" ]; environment.interactiveShellInit = '' - ${optionalString (config.services.clightning.enable) '' - alias lightning-cli='sudo -u clightning lightning-cli --lightning-dir=${config.services.clightning.dataDir}' - ''} ${optionalString (config.services.lnd.enable) '' alias lncli='sudo -u lnd lncli --tlscertpath /secrets/lnd_cert --macaroonpath ${config.services.lnd.dataDir}/chain/bitcoin/mainnet/admin.macaroon' ''} @@ -175,7 +172,7 @@ in { jq qrencode ] - ++ optionals config.services.clightning.enable [clightning] + ++ optionals config.services.clightning.enable [clightning (hiPrio config.services.clightning.cli)] ++ optionals config.services.lnd.enable [lnd] ++ optionals config.services.lightning-charge.enable [lightning-charge] ++ optionals config.services.nanopos.enable [nanopos] diff --git a/modules/recurring-donations.nix b/modules/recurring-donations.nix index f1ef5f9..03896ee 100644 --- a/modules/recurring-donations.nix +++ b/modules/recurring-donations.nix @@ -6,7 +6,7 @@ let cfg = config.services.recurring-donations; inherit (config) nix-bitcoin-services; recurring-donations-script = pkgs.writeScript "recurring-donations.sh" '' - LNCLI="lightning-cli --lightning-dir=${config.services.clightning.dataDir}" + LNCLI="${pkgs.nix-bitcoin.clightning}/bin/lightning-cli --lightning-dir=${config.services.clightning.dataDir}" pay_tallycoin() { NAME=$1 AMOUNT=$2 diff --git a/pkgs/nodeinfo/nodeinfo.sh b/pkgs/nodeinfo/nodeinfo.sh index 6b62ff0..7ab1649 100644 --- a/pkgs/nodeinfo/nodeinfo.sh +++ b/pkgs/nodeinfo/nodeinfo.sh @@ -5,7 +5,7 @@ BITCOIND_ONION="$(cat /var/lib/onion-chef/operator/bitcoind)" echo BITCOIND_ONION="$BITCOIND_ONION" if [ -x "$(command -v lightning-cli)" ]; then - CLIGHTNING_NODEID=$(sudo -u clightning lightning-cli --lightning-dir=/var/lib/clightning getinfo | jq -r '.id') + CLIGHTNING_NODEID=$(lightning-cli getinfo | jq -r '.id') CLIGHTNING_ONION="$(cat /var/lib/onion-chef/operator/clightning)" CLIGHTNING_ID="$CLIGHTNING_NODEID@$CLIGHTNING_ONION:9735" echo CLIGHTNING_NODEID="$CLIGHTNING_NODEID" From cd5ed39b9cdde341325170c7e27b4ea0c9aca302 Mon Sep 17 00:00:00 2001 From: Erik Arvstedt Date: Wed, 27 Nov 2019 14:04:34 +0100 Subject: [PATCH 20/42] lnd: add cli option --- modules/lnd.nix | 10 ++++++++++ modules/nix-bitcoin-webindex.nix | 1 + modules/nix-bitcoin.nix | 5 +---- pkgs/nodeinfo/nodeinfo.sh | 2 +- 4 files changed, 13 insertions(+), 5 deletions(-) diff --git a/modules/lnd.nix b/modules/lnd.nix index b41c21e..589d5ea 100644 --- a/modules/lnd.nix +++ b/modules/lnd.nix @@ -88,6 +88,16 @@ in { ''; description = "Additional configurations to be appended to lnd.conf."; }; + cli = mkOption { + readOnly = true; + default = pkgs.writeScriptBin "lncli" + # Switch user because lnd makes datadir contents readable by user only + '' + exec sudo -u lnd ${pkgs.nix-bitcoin.lnd}/bin/lncli --tlscertpath /secrets/lnd_cert \ + --macaroonpath '${cfg.dataDir}/chain/bitcoin/mainnet/admin.macaroon' "$@" + ''; + description = "Binary to connect with the lnd instance."; + }; enforceTor = nix-bitcoin-services.enforceTor; }; diff --git a/modules/nix-bitcoin-webindex.nix b/modules/nix-bitcoin-webindex.nix index b4d9621..3371438 100644 --- a/modules/nix-bitcoin-webindex.nix +++ b/modules/nix-bitcoin-webindex.nix @@ -77,6 +77,7 @@ in { path = with pkgs; [ nix-bitcoin.nodeinfo config.services.clightning.cli + config.services.lnd.cli jq sudo ]; diff --git a/modules/nix-bitcoin.nix b/modules/nix-bitcoin.nix index 5aee387..304cf3c 100644 --- a/modules/nix-bitcoin.nix +++ b/modules/nix-bitcoin.nix @@ -102,9 +102,6 @@ in { services.onion-chef.access.operator = [ "bitcoind" "clightning" "nginx" "liquidd" "spark-wallet" "electrs" "sshd" ]; environment.interactiveShellInit = '' - ${optionalString (config.services.lnd.enable) '' - alias lncli='sudo -u lnd lncli --tlscertpath /secrets/lnd_cert --macaroonpath ${config.services.lnd.dataDir}/chain/bitcoin/mainnet/admin.macaroon' - ''} ${optionalString (config.services.liquidd.enable) '' alias elements-cli='elements-cli -datadir=${config.services.liquidd.dataDir}' alias liquidswap-cli='liquidswap-cli -c ${config.services.liquidd.dataDir}/elements.conf' @@ -173,7 +170,7 @@ in { qrencode ] ++ optionals config.services.clightning.enable [clightning (hiPrio config.services.clightning.cli)] - ++ optionals config.services.lnd.enable [lnd] + ++ optionals config.services.lnd.enable [lnd (hiPrio config.services.lnd.cli)] ++ optionals config.services.lightning-charge.enable [lightning-charge] ++ optionals config.services.nanopos.enable [nanopos] ++ optionals config.services.nix-bitcoin-webindex.enable [nginx] diff --git a/pkgs/nodeinfo/nodeinfo.sh b/pkgs/nodeinfo/nodeinfo.sh index 7ab1649..a45bc5b 100644 --- a/pkgs/nodeinfo/nodeinfo.sh +++ b/pkgs/nodeinfo/nodeinfo.sh @@ -14,7 +14,7 @@ if [ -x "$(command -v lightning-cli)" ]; then fi if [ -x "$(command -v lncli)" ]; then - LND_NODEID=$(sudo -u lnd lncli --tlscertpath /secrets/lnd_cert --macaroonpath /var/lib/lnd/chain/bitcoin/mainnet/admin.macaroon getinfo | jq -r '.uris[0]') + LND_NODEID=$(lncli getinfo | jq -r '.uris[0]') echo LND_NODEID="$LND_NODEID" fi From 14ecb5511a29f61adacd0d4b45cf0c741604d138 Mon Sep 17 00:00:00 2001 From: Erik Arvstedt Date: Wed, 27 Nov 2019 14:04:35 +0100 Subject: [PATCH 21/42] liquid: add cli option --- modules/liquid.nix | 14 ++++++++++++++ modules/nix-bitcoin.nix | 12 +++++------- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/modules/liquid.nix b/modules/liquid.nix index 042db0e..5859d23 100644 --- a/modules/liquid.nix +++ b/modules/liquid.nix @@ -175,6 +175,20 @@ in { Validate pegin claims. All functionaries must run this. ''; }; + cli = mkOption { + readOnly = true; + default = pkgs.writeScriptBin "elements-cli" '' + exec ${pkgs.nix-bitcoin.elementsd}/bin/elements-cli -datadir='${cfg.dataDir}' "$@" + ''; + description = "Binary to connect with the liquidd instance."; + }; + swap-cli = mkOption { + readOnly = true; + default = pkgs.writeScriptBin "liquidswap-cli" '' + exec ${pkgs.nix-bitcoin.liquid-swap}/bin/liquidswap-cli -c '${cfg.dataDir}/elements.conf' "$@" + ''; + description = "Binary for managing liquid swaps."; + }; enforceTor = nix-bitcoin-services.enforceTor; }; }; diff --git a/modules/nix-bitcoin.nix b/modules/nix-bitcoin.nix index 304cf3c..52785ec 100644 --- a/modules/nix-bitcoin.nix +++ b/modules/nix-bitcoin.nix @@ -101,12 +101,6 @@ in { services.onion-chef.enable = true; services.onion-chef.access.operator = [ "bitcoind" "clightning" "nginx" "liquidd" "spark-wallet" "electrs" "sshd" ]; - environment.interactiveShellInit = '' - ${optionalString (config.services.liquidd.enable) '' - alias elements-cli='elements-cli -datadir=${config.services.liquidd.dataDir}' - alias liquidswap-cli='liquidswap-cli -c ${config.services.liquidd.dataDir}/elements.conf' - ''} - ''; # Unfortunately c-lightning doesn't allow setting the permissions of the rpc socket # https://github.com/ElementsProject/lightning/issues/1366 security.sudo.configFile = ( @@ -174,7 +168,11 @@ in { ++ optionals config.services.lightning-charge.enable [lightning-charge] ++ optionals config.services.nanopos.enable [nanopos] ++ optionals config.services.nix-bitcoin-webindex.enable [nginx] - ++ optionals config.services.liquidd.enable [elementsd liquid-swap] + ++ optionals config.services.liquidd.enable [ + elementsd + (hiPrio config.services.liquidd.cli) + (hiPrio config.services.liquidd.swap-cli) + ] ++ optionals config.services.spark-wallet.enable [spark-wallet] ++ optionals config.services.electrs.enable [electrs] ++ optionals (config.services.hardware-wallets.ledger || config.services.hardware-wallets.trezor) [ From 2b9b3ba1c500e51c86c6cfb06fd49febe56e455d Mon Sep 17 00:00:00 2001 From: Erik Arvstedt Date: Wed, 27 Nov 2019 14:04:36 +0100 Subject: [PATCH 22/42] systemPackages: improve readability with shorter service references --- modules/nix-bitcoin.nix | 33 ++++++++++++++++----------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/modules/nix-bitcoin.nix b/modules/nix-bitcoin.nix index 52785ec..cc050c3 100644 --- a/modules/nix-bitcoin.nix +++ b/modules/nix-bitcoin.nix @@ -155,32 +155,31 @@ in { }]; version = 3; }; - environment.systemPackages = with pkgs; with nix-bitcoin; [ + environment.systemPackages = with pkgs; with nix-bitcoin; let + s = config.services; + in + [ tor bitcoind - (hiPrio config.services.bitcoind.cli) + (hiPrio s.bitcoind.cli) nodeinfo jq qrencode ] - ++ optionals config.services.clightning.enable [clightning (hiPrio config.services.clightning.cli)] - ++ optionals config.services.lnd.enable [lnd (hiPrio config.services.lnd.cli)] - ++ optionals config.services.lightning-charge.enable [lightning-charge] - ++ optionals config.services.nanopos.enable [nanopos] - ++ optionals config.services.nix-bitcoin-webindex.enable [nginx] - ++ optionals config.services.liquidd.enable [ - elementsd - (hiPrio config.services.liquidd.cli) - (hiPrio config.services.liquidd.swap-cli) - ] - ++ optionals config.services.spark-wallet.enable [spark-wallet] - ++ optionals config.services.electrs.enable [electrs] - ++ optionals (config.services.hardware-wallets.ledger || config.services.hardware-wallets.trezor) [ + ++ optionals s.clightning.enable [clightning (hiPrio s.clightning.cli)] + ++ optionals s.lnd.enable [lnd (hiPrio s.lnd.cli)] + ++ optionals s.lightning-charge.enable [lightning-charge] + ++ optionals s.nanopos.enable [nanopos] + ++ optionals s.nix-bitcoin-webindex.enable [nginx] + ++ optionals s.liquidd.enable [elementsd (hiPrio s.liquidd.cli) (hiPrio s.liquidd.swap-cli)] + ++ optionals s.spark-wallet.enable [spark-wallet] + ++ optionals s.electrs.enable [electrs] + ++ optionals (s.hardware-wallets.ledger || s.hardware-wallets.trezor) [ hwi - # To allow debugging issues with lsusb: + # To allow debugging issues with lsusb usbutils ] - ++ optionals config.services.hardware-wallets.trezor [ + ++ optionals s.hardware-wallets.trezor [ python3.pkgs.trezor ]; }; From 60c732a6a1cbbe8b6112f3517140f03481c97956 Mon Sep 17 00:00:00 2001 From: Erik Arvstedt Date: Wed, 27 Nov 2019 14:04:37 +0100 Subject: [PATCH 23/42] onion-chef: set RemainAfterExit, fix tor dependency This better fits the semantics of this unit and allows for easier automated testing whether the service is active. wantedBy = bindsTo = after = tor.service is the simplest way to ensure that this unit is always running/restarted in lockstep with tor. Previously, onion-chef would have stayed inactive in the case that tor was stopped and then later restarted. --- modules/onion-chef.nix | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/modules/onion-chef.nix b/modules/onion-chef.nix index 0ec05c0..df9c36f 100644 --- a/modules/onion-chef.nix +++ b/modules/onion-chef.nix @@ -70,14 +70,13 @@ in { config = mkIf cfg.enable { systemd.services.onion-chef = { description = "Run onion-chef"; - wantedBy = [ "multi-user.target" ]; - requires = [ "tor.service" ]; - partOf = [ "tor.service" ]; + wantedBy = [ "tor.service" ]; + bindsTo = [ "tor.service" ]; after = [ "tor.service" ]; serviceConfig = { ExecStart = "${pkgs.bash}/bin/bash ${onion-chef-script}"; - User = "root"; Type = "oneshot"; + RemainAfterExit = true; } // nix-bitcoin-services.defaultHardening; }; }; From 86167c6e6d3362a02055e9045981434d29c7cca5 Mon Sep 17 00:00:00 2001 From: Erik Arvstedt Date: Wed, 27 Nov 2019 14:04:38 +0100 Subject: [PATCH 24/42] clightning: wait until the RPC socket appears This fixes failures with spark-wallet which requires clightning RPC --- modules/clightning.nix | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/modules/clightning.nix b/modules/clightning.nix index fb513b9..f3de4a0 100644 --- a/modules/clightning.nix +++ b/modules/clightning.nix @@ -106,6 +106,11 @@ in { then nix-bitcoin-services.allowTor else nix-bitcoin-services.allowAnyIP ); + # Wait until the rpc socket appears + postStart = '' + while read f; do [[ $f == lightning-rpc ]] && break; done \ + < <(${pkgs.inotifyTools}/bin/inotifywait --quiet --monitor -e create,moved_to --format '%f' '${cfg.dataDir}') + ''; }; }; } From 6e58beae8a27908f9e3e6afbe0e620b5c964c791 Mon Sep 17 00:00:00 2001 From: Erik Arvstedt Date: Wed, 27 Nov 2019 14:04:39 +0100 Subject: [PATCH 25/42] lnd: use postStart option for script - set -e is implicit - coreutils are in PATH and don't have to be explicitly referenced (echo is a shell builtin anyways) - exit 0 is unneeded ('if' statements never fail) --- modules/lnd.nix | 74 +++++++++++++++++++++++-------------------------- 1 file changed, 34 insertions(+), 40 deletions(-) diff --git a/modules/lnd.nix b/modules/lnd.nix index 589d5ea..712d3d0 100644 --- a/modules/lnd.nix +++ b/modules/lnd.nix @@ -26,45 +26,6 @@ let ${cfg.extraConfig} ''; - init-lnd-wallet-script = pkgs.writeScript "init-lnd-wallet.sh" '' -#!/bin/sh - -set -e -umask 377 - -${pkgs.coreutils}/bin/sleep 5 - -if [ ! -f /secrets/lnd-seed-mnemonic ] -then - ${pkgs.coreutils}/bin/echo Creating lnd seed - - ${pkgs.curl}/bin/curl -s \ - --cacert /secrets/lnd_cert \ - -X GET https://127.0.0.1:8080/v1/genseed | ${pkgs.jq}/bin/jq -c '.cipher_seed_mnemonic' > /secrets/lnd-seed-mnemonic -fi - -if [ ! -f ${cfg.dataDir}/chain/bitcoin/mainnet/wallet.db ] -then - ${pkgs.coreutils}/bin/echo Creating lnd wallet - - ${pkgs.curl}/bin/curl -s \ - --cacert /secrets/lnd_cert \ - -X POST -d "{\"wallet_password\": \"$(${pkgs.coreutils}/bin/cat /secrets/lnd-wallet-password | ${pkgs.coreutils}/bin/tr -d '\n' | ${pkgs.coreutils}/bin/base64 -w0)\", \ - \"cipher_seed_mnemonic\": $(${pkgs.coreutils}/bin/cat /secrets/lnd-seed-mnemonic | ${pkgs.coreutils}/bin/tr -d '\n')}" \ - https://127.0.0.1:8080/v1/initwallet -else - ${pkgs.coreutils}/bin/echo Unlocking lnd wallet - - ${pkgs.curl}/bin/curl -s \ - -H "Grpc-Metadata-macaroon: $(${pkgs.xxd}/bin/xxd -ps -u -c 99999 ${cfg.dataDir}/chain/bitcoin/mainnet/admin.macaroon)" \ - --cacert /secrets/lnd_cert \ - -X POST \ - -d "{\"wallet_password\": \"$(${pkgs.coreutils}/bin/cat /secrets/lnd-wallet-password | ${pkgs.coreutils}/bin/tr -d '\n' | ${pkgs.coreutils}/bin/base64 -w0)\"}" \ - https://127.0.0.1:8080/v1/unlockwallet -fi - -exit 0 -''; in { options.services.lnd = { @@ -126,7 +87,6 @@ in { serviceConfig = { PermissionsStartOnly = "true"; ExecStart = "${pkgs.nix-bitcoin.lnd}/bin/lnd --configfile=${cfg.dataDir}/lnd.conf"; - ExecStartPost = "${pkgs.bash}/bin/bash ${init-lnd-wallet-script}"; User = "lnd"; Restart = "on-failure"; RestartSec = "10s"; @@ -135,6 +95,40 @@ in { then nix-bitcoin-services.allowTor else nix-bitcoin-services.allowAnyIP ) // nix-bitcoin-services.allowAnyProtocol; # For ZMQ + postStart = '' + umask 377 + + sleep 5 + + if [ ! -f /secrets/lnd-seed-mnemonic ] + then + echo Creating lnd seed + + ${pkgs.curl}/bin/curl -s \ + --cacert /secrets/lnd_cert \ + -X GET https://127.0.0.1:8080/v1/genseed | ${pkgs.jq}/bin/jq -c '.cipher_seed_mnemonic' > /secrets/lnd-seed-mnemonic + fi + + if [ ! -f ${cfg.dataDir}/chain/bitcoin/mainnet/wallet.db ] + then + echo Creating lnd wallet + + ${pkgs.curl}/bin/curl -s \ + --cacert /secrets/lnd_cert \ + -X POST -d "{\"wallet_password\": \"$(cat /secrets/lnd-wallet-password | tr -d '\n' |base64 -w0)\", \ + \"cipher_seed_mnemonic\": $(cat /secrets/lnd-seed-mnemonic | tr -d '\n')}" \ + https://127.0.0.1:8080/v1/initwallet + else + echo Unlocking lnd wallet + + ${pkgs.curl}/bin/curl -s \ + -H "Grpc-Metadata-macaroon: $(${pkgs.xxd}/bin/xxd -ps -u -c 99999 ${cfg.dataDir}/chain/bitcoin/mainnet/admin.macaroon)" \ + --cacert /secrets/lnd_cert \ + -X POST \ + -d "{\"wallet_password\": \"$(cat /secrets/lnd-wallet-password | tr -d '\n' | base64 -w0)\"}" \ + https://127.0.0.1:8080/v1/unlockwallet + fi + ''; }; }; } From 795c51dc01efe72b58d33085490318d5991e969e Mon Sep 17 00:00:00 2001 From: Erik Arvstedt Date: Wed, 27 Nov 2019 14:04:40 +0100 Subject: [PATCH 26/42] lnd postStart: make more idiomatic - [[]]-style tests - indent all multi-line statements the same way --- modules/lnd.nix | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/modules/lnd.nix b/modules/lnd.nix index 712d3d0..9e80bc8 100644 --- a/modules/lnd.nix +++ b/modules/lnd.nix @@ -100,33 +100,31 @@ in { sleep 5 - if [ ! -f /secrets/lnd-seed-mnemonic ] - then + if [[ ! -f /secrets/lnd-seed-mnemonic ]]; then echo Creating lnd seed ${pkgs.curl}/bin/curl -s \ - --cacert /secrets/lnd_cert \ - -X GET https://127.0.0.1:8080/v1/genseed | ${pkgs.jq}/bin/jq -c '.cipher_seed_mnemonic' > /secrets/lnd-seed-mnemonic + --cacert /secrets/lnd_cert \ + -X GET https://127.0.0.1:8080/v1/genseed | ${pkgs.jq}/bin/jq -c '.cipher_seed_mnemonic' > /secrets/lnd-seed-mnemonic fi - if [ ! -f ${cfg.dataDir}/chain/bitcoin/mainnet/wallet.db ] - then + if [[ ! -f ${cfg.dataDir}/chain/bitcoin/mainnet/wallet.db ]]; then echo Creating lnd wallet ${pkgs.curl}/bin/curl -s \ - --cacert /secrets/lnd_cert \ - -X POST -d "{\"wallet_password\": \"$(cat /secrets/lnd-wallet-password | tr -d '\n' |base64 -w0)\", \ - \"cipher_seed_mnemonic\": $(cat /secrets/lnd-seed-mnemonic | tr -d '\n')}" \ - https://127.0.0.1:8080/v1/initwallet + --cacert /secrets/lnd_cert \ + -X POST -d "{\"wallet_password\": \"$(cat /secrets/lnd-wallet-password | tr -d '\n' |base64 -w0)\", \ + \"cipher_seed_mnemonic\": $(cat /secrets/lnd-seed-mnemonic | tr -d '\n')}" \ + https://127.0.0.1:8080/v1/initwallet else echo Unlocking lnd wallet ${pkgs.curl}/bin/curl -s \ - -H "Grpc-Metadata-macaroon: $(${pkgs.xxd}/bin/xxd -ps -u -c 99999 ${cfg.dataDir}/chain/bitcoin/mainnet/admin.macaroon)" \ - --cacert /secrets/lnd_cert \ - -X POST \ - -d "{\"wallet_password\": \"$(cat /secrets/lnd-wallet-password | tr -d '\n' | base64 -w0)\"}" \ - https://127.0.0.1:8080/v1/unlockwallet + -H "Grpc-Metadata-macaroon: $(${pkgs.xxd}/bin/xxd -ps -u -c 99999 ${cfg.dataDir}/chain/bitcoin/mainnet/admin.macaroon)" \ + --cacert /secrets/lnd_cert \ + -X POST \ + -d "{\"wallet_password\": \"$(cat /secrets/lnd-wallet-password | tr -d '\n' | base64 -w0)\"}" \ + https://127.0.0.1:8080/v1/unlockwallet fi ''; }; From 3e86637327663ec8745294e12faf41d9fcb4f58e Mon Sep 17 00:00:00 2001 From: Erik Arvstedt Date: Wed, 27 Nov 2019 14:04:41 +0100 Subject: [PATCH 27/42] lnd postStart: poll for REST service availability Improves service startup time compared to just sleeping --- modules/lnd.nix | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/modules/lnd.nix b/modules/lnd.nix index 9e80bc8..9764f5e 100644 --- a/modules/lnd.nix +++ b/modules/lnd.nix @@ -98,7 +98,11 @@ in { postStart = '' umask 377 - sleep 5 + attempts=50 + while ! { exec 3>/dev/tcp/127.0.0.1/8080 && exec 3>&-; } &>/dev/null; do + ((attempts-- == 0)) && { echo "lnd REST service unreachable"; exit 1; } + sleep 0.1 + done if [[ ! -f /secrets/lnd-seed-mnemonic ]]; then echo Creating lnd seed From 120e3e8cfea6221371ef5a939cbcb7138e3808dc Mon Sep 17 00:00:00 2001 From: Erik Arvstedt Date: Wed, 27 Nov 2019 14:04:42 +0100 Subject: [PATCH 28/42] lnd postStart: suppress curl response output Errors are still shown --- modules/lnd.nix | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/lnd.nix b/modules/lnd.nix index 9764f5e..73b9740 100644 --- a/modules/lnd.nix +++ b/modules/lnd.nix @@ -115,9 +115,9 @@ in { if [[ ! -f ${cfg.dataDir}/chain/bitcoin/mainnet/wallet.db ]]; then echo Creating lnd wallet - ${pkgs.curl}/bin/curl -s \ + ${pkgs.curl}/bin/curl -s --output /dev/null --show-error \ --cacert /secrets/lnd_cert \ - -X POST -d "{\"wallet_password\": \"$(cat /secrets/lnd-wallet-password | tr -d '\n' |base64 -w0)\", \ + -X POST -d "{\"wallet_password\": \"$(cat /secrets/lnd-wallet-password | tr -d '\n' | base64 -w0)\", \ \"cipher_seed_mnemonic\": $(cat /secrets/lnd-seed-mnemonic | tr -d '\n')}" \ https://127.0.0.1:8080/v1/initwallet else From 1868bef4625a2897eb03355099e5077739d07365 Mon Sep 17 00:00:00 2001 From: Erik Arvstedt Date: Wed, 27 Nov 2019 14:04:43 +0100 Subject: [PATCH 29/42] lnd: add option 'rpcPort' 10009 is lnd's default port. Needed for the following commit. --- modules/lnd.nix | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/modules/lnd.nix b/modules/lnd.nix index 73b9740..9c074ba 100644 --- a/modules/lnd.nix +++ b/modules/lnd.nix @@ -12,6 +12,8 @@ let tlscertpath=/secrets/lnd_cert tlskeypath=/secrets/lnd_key + rpclisten=localhost:${toString cfg.rpcPort} + bitcoin.active=1 bitcoin.node=bitcoind @@ -41,6 +43,11 @@ in { default = "/var/lib/lnd"; description = "The data directory for LND."; }; + rpcPort = mkOption { + type = types.ints.u16; + default = 10009; + description = "Port on which to listen for gRPC connections."; + }; extraConfig = mkOption { type = types.lines; default = ""; From 6f2a55d63ca9b4daf70a22e31285ced720697e0c Mon Sep 17 00:00:00 2001 From: Erik Arvstedt Date: Wed, 27 Nov 2019 14:04:44 +0100 Subject: [PATCH 30/42] lnd: wait until RPC port is open --- modules/lnd.nix | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/modules/lnd.nix b/modules/lnd.nix index 9c074ba..19ab45f 100644 --- a/modules/lnd.nix +++ b/modules/lnd.nix @@ -137,6 +137,11 @@ in { -d "{\"wallet_password\": \"$(cat /secrets/lnd-wallet-password | tr -d '\n' | base64 -w0)\"}" \ https://127.0.0.1:8080/v1/unlockwallet fi + + # Wait until the RPC port is open + while ! { exec 3>/dev/tcp/127.0.0.1/${toString cfg.rpcPort}; } &>/dev/null; do + sleep 0.1 + done ''; }; }; From 5536b64fb3c699ad0b24d165d5f3073be357e8c5 Mon Sep 17 00:00:00 2001 From: Erik Arvstedt Date: Wed, 27 Nov 2019 14:04:45 +0100 Subject: [PATCH 31/42] lnd: wait until wallet is created --- modules/lnd.nix | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/modules/lnd.nix b/modules/lnd.nix index 19ab45f..f879cc2 100644 --- a/modules/lnd.nix +++ b/modules/lnd.nix @@ -102,7 +102,9 @@ in { then nix-bitcoin-services.allowTor else nix-bitcoin-services.allowAnyIP ) // nix-bitcoin-services.allowAnyProtocol; # For ZMQ - postStart = '' + postStart = let + mainnetDir = "${cfg.dataDir}/chain/bitcoin/mainnet"; + in '' umask 377 attempts=50 @@ -112,26 +114,32 @@ in { done if [[ ! -f /secrets/lnd-seed-mnemonic ]]; then - echo Creating lnd seed + echo Create lnd seed ${pkgs.curl}/bin/curl -s \ --cacert /secrets/lnd_cert \ -X GET https://127.0.0.1:8080/v1/genseed | ${pkgs.jq}/bin/jq -c '.cipher_seed_mnemonic' > /secrets/lnd-seed-mnemonic fi - if [[ ! -f ${cfg.dataDir}/chain/bitcoin/mainnet/wallet.db ]]; then - echo Creating lnd wallet + if [[ ! -f ${mainnetDir}/wallet.db ]]; then + echo Create lnd wallet ${pkgs.curl}/bin/curl -s --output /dev/null --show-error \ --cacert /secrets/lnd_cert \ -X POST -d "{\"wallet_password\": \"$(cat /secrets/lnd-wallet-password | tr -d '\n' | base64 -w0)\", \ \"cipher_seed_mnemonic\": $(cat /secrets/lnd-seed-mnemonic | tr -d '\n')}" \ https://127.0.0.1:8080/v1/initwallet + + # Guarantees that RPC calls with cfg.cli succeed after the service is started + echo Wait until wallet is created + while [[ ! -f ${mainnetDir}/admin.macaroon ]]; do + sleep 0.1 + done else - echo Unlocking lnd wallet + echo Unlock lnd wallet ${pkgs.curl}/bin/curl -s \ - -H "Grpc-Metadata-macaroon: $(${pkgs.xxd}/bin/xxd -ps -u -c 99999 ${cfg.dataDir}/chain/bitcoin/mainnet/admin.macaroon)" \ + -H "Grpc-Metadata-macaroon: $(${pkgs.xxd}/bin/xxd -ps -u -c 99999 '${mainnetDir}/admin.macaroon')" \ --cacert /secrets/lnd_cert \ -X POST \ -d "{\"wallet_password\": \"$(cat /secrets/lnd-wallet-password | tr -d '\n' | base64 -w0)\"}" \ From ad7a519284eed218c2c9958c141f817c2abc9e93 Mon Sep 17 00:00:00 2001 From: Erik Arvstedt Date: Wed, 27 Nov 2019 14:04:46 +0100 Subject: [PATCH 32/42] bitcoind: wait until RPC port is open This fixes rare failures in clightning which requires an open bitcoind RPC port --- modules/bitcoind.nix | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/modules/bitcoind.nix b/modules/bitcoind.nix index eab5b40..cc073e1 100644 --- a/modules/bitcoind.nix +++ b/modules/bitcoind.nix @@ -19,7 +19,7 @@ let 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) @@ -108,9 +108,9 @@ in { rpc = { port = mkOption { - type = types.nullOr types.ints.u16; - default = null; - description = "Override the default port on which to listen for JSON-RPC connections."; + type = types.ints.u16; + default = 8332; + description = "Port on which to listen for JSON-RPC connections."; }; users = mkOption { default = {}; @@ -241,6 +241,12 @@ in { echo "rpcpassword=$(cat /secrets/bitcoin-rpcpassword)" >> '${cfg.dataDir}/bitcoin.conf' chmod -R g+rX '${cfg.dataDir}/blocks' ''; + # Wait until RPC port is open. This usually takes just a few ms. + postStart = '' + while ! { exec 3>/dev/tcp/127.0.0.1/${toString cfg.rpc.port}; } &>/dev/null; do + sleep 0.05 + done + ''; serviceConfig = { Type = "simple"; User = "${cfg.user}"; From 10d6b04ac82226dd8c39da492ef63f1491b2dd65 Mon Sep 17 00:00:00 2001 From: Erik Arvstedt Date: Wed, 27 Nov 2019 14:04:47 +0100 Subject: [PATCH 33/42] support enabling clightning and lnd simultaneously Needed for testing. --- modules/nix-bitcoin.nix | 16 +++++++--------- pkgs/nodeinfo/nodeinfo.sh | 4 ++-- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/modules/nix-bitcoin.nix b/modules/nix-bitcoin.nix index cc050c3..5cbab71 100644 --- a/modules/nix-bitcoin.nix +++ b/modules/nix-bitcoin.nix @@ -103,15 +103,13 @@ in { # Unfortunately c-lightning doesn't allow setting the permissions of the rpc socket # https://github.com/ElementsProject/lightning/issues/1366 - security.sudo.configFile = ( - if config.services.clightning.enable then '' - operator ALL=(clightning) NOPASSWD: ALL - '' - else if config.services.lnd.enable then '' - operator ALL=(lnd) NOPASSWD: ALL - '' - else "" - ); + security.sudo.configFile = + (optionalString config.services.clightning.enable '' + operator ALL=(clightning) NOPASSWD: ALL + '') + + (optionalString config.services.lnd.enable '' + operator ALL=(lnd) NOPASSWD: ALL + ''); # Give root ssh access to the operator account systemd.services.copy-root-authorized-keys = { diff --git a/pkgs/nodeinfo/nodeinfo.sh b/pkgs/nodeinfo/nodeinfo.sh index a45bc5b..bb6dcdb 100644 --- a/pkgs/nodeinfo/nodeinfo.sh +++ b/pkgs/nodeinfo/nodeinfo.sh @@ -4,7 +4,7 @@ set -o pipefail BITCOIND_ONION="$(cat /var/lib/onion-chef/operator/bitcoind)" echo BITCOIND_ONION="$BITCOIND_ONION" -if [ -x "$(command -v lightning-cli)" ]; then +if systemctl is-active --quiet clightning; then CLIGHTNING_NODEID=$(lightning-cli getinfo | jq -r '.id') CLIGHTNING_ONION="$(cat /var/lib/onion-chef/operator/clightning)" CLIGHTNING_ID="$CLIGHTNING_NODEID@$CLIGHTNING_ONION:9735" @@ -13,7 +13,7 @@ if [ -x "$(command -v lightning-cli)" ]; then echo CLIGHTNING_ID="$CLIGHTNING_ID" fi -if [ -x "$(command -v lncli)" ]; then +if systemctl is-active --quiet lnd; then LND_NODEID=$(lncli getinfo | jq -r '.uris[0]') echo LND_NODEID="$LND_NODEID" fi From 190a92507cf395a359912f777c470cdd4d6dbef5 Mon Sep 17 00:00:00 2001 From: Erik Arvstedt Date: Wed, 27 Nov 2019 14:04:48 +0100 Subject: [PATCH 34/42] travis: split up scripts into statements MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Advantages: - The build fails if a statement fails. Previously, errors in all but the last statement were silently ignored. - The Travis log gives a fine-grained view of the output of each statement. Add if statements because the '[…] &&' prefix results in YAML syntax errors. --- .travis.yml | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/.travis.yml b/.travis.yml index 2846259..6259587 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,19 +3,19 @@ language: minimal # broken: # - PKG=electrs STABLE=0 -# Retry installing nix due to nondeterministic error -# Fatal error: glibc detected an invalid stdio handle -# see: -# https://github.com/nh2/static-haskell-nix/pull/27#issuecomment-502652181 -# https://github.com/nixos/nix/issues/2733 -install: | - (for i in {1..5}; do bash <(curl https://nixos.org/nix/install) && exit 0; done; exit 1) - . /home/travis/.nix-profile/etc/profile.d/nix.sh - nix-env -iA cachix -f https://cachix.org/api/v1/install - cachix use nix-bitcoin - [ $STABLE -eq 1 ] && export NIX_PATH="nixpkgs=$(nix eval --raw -f pkgs/nixpkgs-pinned.nix nixpkgs)" - [ $STABLE -eq 0 ] && export NIX_PATH="nixpkgs=$(nix eval --raw -f pkgs/nixpkgs-pinned.nix nixpkgs-unstable)" - VER="$(nix eval nixpkgs.lib.version)" +install: + # Retry installing nix due to nondeterministic error + # Fatal error: glibc detected an invalid stdio handle + # see: + # https://github.com/nh2/static-haskell-nix/pull/27#issuecomment-502652181 + # https://github.com/nixos/nix/issues/2733 + - (for i in {1..5}; do bash <(curl https://nixos.org/nix/install) && exit 0; done; exit 1) + - . /home/travis/.nix-profile/etc/profile.d/nix.sh + - nix-env -iA cachix -f https://cachix.org/api/v1/install + - cachix use nix-bitcoin + - if [[ $STABLE == 1 ]]; then export NIX_PATH="nixpkgs=$(nix eval --raw -f pkgs/nixpkgs-pinned.nix nixpkgs)"; fi + - if [[ $STABLE == 0 ]]; then export NIX_PATH="nixpkgs=$(nix eval --raw -f pkgs/nixpkgs-pinned.nix nixpkgs-unstable)"; fi + - VER="$(nix eval nixpkgs.lib.version)" env: matrix: - PKG=nodeinfo STABLE=1 @@ -29,6 +29,6 @@ env: - PKG=elementsd STABLE=0 - PKG=electrs STABLE=1 - PKG=liquid-swap STABLE=1 -script: | - printf '%s (%s)\n' "$NIX_PATH" "$VER" - nix-build -A $PKG +script: + - printf '%s (%s)\n' "$NIX_PATH" "$VER" + - nix-build -A $PKG From 7092dce0c77cfbb3e2a01a670c3a7618fca2a759 Mon Sep 17 00:00:00 2001 From: Erik Arvstedt Date: Sun, 12 Jan 2020 20:52:33 +0100 Subject: [PATCH 35/42] travis: remove use of deprecated statements --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 6259587..31e41f5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,4 @@ -language: minimal +language: shell # broken: # - PKG=electrs STABLE=0 @@ -17,7 +17,7 @@ install: - if [[ $STABLE == 0 ]]; then export NIX_PATH="nixpkgs=$(nix eval --raw -f pkgs/nixpkgs-pinned.nix nixpkgs-unstable)"; fi - VER="$(nix eval nixpkgs.lib.version)" env: - matrix: + jobs: - PKG=nodeinfo STABLE=1 - PKG=hwi STABLE=1 - PKG=lightning-charge STABLE=1 From c51bbcf104d3163366b91b2dfaa3e806f31cd02a Mon Sep 17 00:00:00 2001 From: Erik Arvstedt Date: Sun, 12 Jan 2020 20:52:34 +0100 Subject: [PATCH 36/42] travis: move comment --- .travis.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 31e41f5..1828ba3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,5 @@ language: shell -# broken: -# - PKG=electrs STABLE=0 - install: # Retry installing nix due to nondeterministic error # Fatal error: glibc detected an invalid stdio handle @@ -28,6 +25,8 @@ env: - PKG=elementsd STABLE=1 - PKG=elementsd STABLE=0 - PKG=electrs STABLE=1 + # broken + # - PKG=electrs STABLE=0 - PKG=liquid-swap STABLE=1 script: - printf '%s (%s)\n' "$NIX_PATH" "$VER" From b0e759160dcd29e2dfe0bd6471d39444e7d09123 Mon Sep 17 00:00:00 2001 From: Erik Arvstedt Date: Sun, 12 Jan 2020 20:52:35 +0100 Subject: [PATCH 37/42] travis: set NIX_PATH as early as possible This avoids some unneded downloads when installing cachix. --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 1828ba3..cbb568a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,10 +8,10 @@ install: # https://github.com/nixos/nix/issues/2733 - (for i in {1..5}; do bash <(curl https://nixos.org/nix/install) && exit 0; done; exit 1) - . /home/travis/.nix-profile/etc/profile.d/nix.sh - - nix-env -iA cachix -f https://cachix.org/api/v1/install - - cachix use nix-bitcoin - if [[ $STABLE == 1 ]]; then export NIX_PATH="nixpkgs=$(nix eval --raw -f pkgs/nixpkgs-pinned.nix nixpkgs)"; fi - if [[ $STABLE == 0 ]]; then export NIX_PATH="nixpkgs=$(nix eval --raw -f pkgs/nixpkgs-pinned.nix nixpkgs-unstable)"; fi + - nix-env -iA cachix -f https://cachix.org/api/v1/install + - cachix use nix-bitcoin - VER="$(nix eval nixpkgs.lib.version)" env: jobs: From 766fa4f300511a7f0703327248bd9cc8cdbe0c3b Mon Sep 17 00:00:00 2001 From: Erik Arvstedt Date: Sun, 12 Jan 2020 20:52:36 +0100 Subject: [PATCH 38/42] travis: cache all build outputs with cachix This further speeds up builds, in particular the modules test in the next commit. By checking if the expected final build output has already been cached, we can even skip the download of cached builds. --- .travis.yml | 26 +++++++++++++++++++++++++- helper/wait-for-network-idle.rb | 29 +++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 1 deletion(-) create mode 100755 helper/wait-for-network-idle.rb diff --git a/.travis.yml b/.travis.yml index cbb568a..3835300 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,6 +14,9 @@ install: - cachix use nix-bitcoin - VER="$(nix eval nixpkgs.lib.version)" env: + global: + # CACHIX_SIGNING_KEY + - secure: "xXCFZ7g+k5YmCGm8R8l3bZElVmt+RD1KscG3kGr5w4HyyDPTzFetPo+sT8bUpysDU0u3HWhfVhHtpog2mhNhwVl3tQwKXea3dHKC1i6ypBg3gjDngmJRR5wo++ocYDpK8qPaU7m/jHQTNFnTA4CbmMcc05GcYx/1Ai/ZGkNwWFjdIcVeOUoiol33gykMOXIGDg2qlXudt33wP53FHbX8L4fxzodWfAuxKK4AoGprxy5eSnU7LCaXxxJmu4HwuV+Ux2U1NfE/E33cvhlUvTQCswVSZFG06mg8rwhMG1ozsDvlL2itZlu/BeUQH5y3XMMlnJIUXUazkRBibf1w/ebVjpOF+anqkqmq8tcbFEa7T+RJeVTIsvP+L8rE8fcmuZtdg9hNmgRnLmaeT0vVwD1L2UqW9HdRyujdoS0jPYuoc1W7f1JQWfAPhBPQ1SrtKyNNqcbVJ34aN7b+4vCzRpQL1JTbmjzQIWhkiKN1qMo1v/wbIydW8yka4hc4JOfdQLaAJEPI1eAC1MLotSAegMnwKWE1dzm66MuPSipksYjZrvsB28cV4aCVUffIuRhrSr1i2afRHwTpNbK9U4/576hah15ftUdR79Sfkcoi1ekSQTFGRvkRIPYtkKLYwFa3jVA41qz7+IIZCf4TsApy3XDdFx91cRub7yPq9BeZ83A+qYQ=" jobs: - PKG=nodeinfo STABLE=1 - PKG=hwi STABLE=1 @@ -30,4 +33,25 @@ env: - PKG=liquid-swap STABLE=1 script: - printf '%s (%s)\n' "$NIX_PATH" "$VER" - - nix-build -A $PKG + - time nix-instantiate -A $PKG --add-root ./drv --indirect + - outPath=$(nix-store --query ./drv) + - | + if nix path-info --store https://nix-bitcoin.cachix.org $outPath &>/dev/null; then + echo "$outPath" has already been built successfully. + travis_terminate 0 + fi + # Travis doesn't expose secrets to pull-request builds, + # so skip cache uploading in this case + - | + if [[ $CACHIX_SIGNING_KEY ]]; then + cachix push nix-bitcoin --watch-store & + cachixPid=$! + fi + - nix-build ./drv + - | + if [[ $CACHIX_SIGNING_KEY ]]; then + # Wait until cachix has finished uploading + # Run as root because yama/ptrace_scope != 0 + ruby=$(nix-build '' -A ruby)/bin/ruby + time sudo $ruby helper/wait-for-network-idle.rb $cachixPid + fi diff --git a/helper/wait-for-network-idle.rb b/helper/wait-for-network-idle.rb new file mode 100755 index 0000000..18b5a9a --- /dev/null +++ b/helper/wait-for-network-idle.rb @@ -0,0 +1,29 @@ +#!/usr/bin/env ruby +require 'open3' + +# Wait until the given PID had no network activity for `Timeout` seconds, then exit. + +pid = ARGV.first +Timeout = 2 + +stdin, out, err, wait_thread = Open3.popen3("strace -f -e trace=network -s 1 -q -p #{pid}") +while IO.select([err], nil, nil, Timeout) + begin + out = err.read_nonblock(1 << 10) + rescue EOFError + status = wait_thread.value + if status.success? + puts "Monitored process #{pid} exited" + exit 0 + else + puts "Strace failed with exit code #{status.to_i}. Last output:\n#{out}" + # strace often fails with code 256 which looks like success to shells. fail with 1 instead. + exit 1 + end + end +end + +# If we exit without an explicit kill, +# ptrace can fail on reattachment: ptrace(PTRACE_SEIZE, $PID): Operation not permitted +# Only relevant for testing. +Process.kill("TERM", wait_thread.pid) From 314272a228f974dc41c36ba2a9f3997d35ff7361 Mon Sep 17 00:00:00 2001 From: Erik Arvstedt Date: Sun, 12 Jan 2020 20:52:37 +0100 Subject: [PATCH 39/42] lnd, nanopos: move user and group definitions to the bottom This is the default service formatting style in nixpkgs. --- modules/lnd.nix | 15 +++++++-------- modules/nanopos.nix | 11 +++++------ 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/modules/lnd.nix b/modules/lnd.nix index f879cc2..a26e189 100644 --- a/modules/lnd.nix +++ b/modules/lnd.nix @@ -70,14 +70,6 @@ in { }; config = mkIf cfg.enable { - users.users.lnd = { - description = "LND User"; - group = "lnd"; - extraGroups = [ "bitcoinrpc" ]; - home = cfg.dataDir; - }; - users.groups.lnd = {}; - systemd.services.lnd = { description = "Run LND"; path = [ pkgs.nix-bitcoin.bitcoind ]; @@ -152,5 +144,12 @@ in { done ''; }; + users.users.lnd = { + description = "LND User"; + group = "lnd"; + extraGroups = [ "bitcoinrpc" ]; + home = cfg.dataDir; + }; + users.groups.lnd = {}; }; } diff --git a/modules/nanopos.nix b/modules/nanopos.nix index cbfdc0c..1510198 100644 --- a/modules/nanopos.nix +++ b/modules/nanopos.nix @@ -52,12 +52,6 @@ in { }; config = mkIf cfg.enable { - users.users.nanopos = { - description = "nanopos User"; - group = "nanopos"; - }; - users.groups.nanopos = {}; - systemd.services.nanopos = { description = "Run nanopos"; wantedBy = [ "multi-user.target" ]; @@ -74,5 +68,10 @@ in { // nix-bitcoin-services.nodejs // nix-bitcoin-services.allowTor; }; + users.users.nanopos = { + description = "nanopos User"; + group = "nanopos"; + }; + users.groups.nanopos = {}; }; } From b1e13e9415f16180cdf7f2d23c209150471aa520 Mon Sep 17 00:00:00 2001 From: Erik Arvstedt Date: Sun, 12 Jan 2020 20:52:38 +0100 Subject: [PATCH 40/42] simplify secrets file format Each secret file to be deployed is now backed by one local file. This simplifies 'setup-secrets' and the secret definitions. Also, with the old format it was not possible to add new secrets to secrets.nix in a simple way. Old secrets are automatically converted to the new format when running nix-shell. Using the new option 'nix-bitcoin.secrets', secrets are now directly defined by the services that use them. --- modules/bitcoind.nix | 5 ++ modules/electrs.nix | 11 ++- modules/lightning-charge.nix | 3 +- modules/liquid.nix | 1 + modules/lnd.nix | 17 +++-- modules/modules.nix | 2 +- modules/nanopos.nix | 3 +- modules/secrets/make-secrets.nix | 68 ------------------- .../{setup-secrets.nix => secrets.nix} | 65 +++++++++--------- modules/spark-wallet.nix | 1 + network/network.nix | 9 +-- pkgs/generate-secrets/generate-secrets.sh | 56 ++++++--------- pkgs/generate-secrets/update-and-generate.nix | 10 +++ pkgs/generate-secrets/update-secrets.sh | 48 +++++++++++++ shell.nix | 4 +- 15 files changed, 151 insertions(+), 152 deletions(-) delete mode 100644 modules/secrets/make-secrets.nix rename modules/secrets/{setup-secrets.nix => secrets.nix} (54%) create mode 100644 pkgs/generate-secrets/update-and-generate.nix create mode 100644 pkgs/generate-secrets/update-secrets.sh diff --git a/modules/bitcoind.nix b/modules/bitcoind.nix index cc073e1..a4c147e 100644 --- a/modules/bitcoind.nix +++ b/modules/bitcoind.nix @@ -307,5 +307,10 @@ in { }; users.groups.${cfg.group} = {}; users.groups.bitcoinrpc = {}; + + nix-bitcoin.secrets.bitcoin-rpcpassword = { + user = "bitcoin"; + group = "bitcoinrpc"; + }; }; } diff --git a/modules/electrs.nix b/modules/electrs.nix index fcb87db..7f98b97 100644 --- a/modules/electrs.nix +++ b/modules/electrs.nix @@ -103,8 +103,8 @@ in { listen ${toString config.services.electrs.nginxport} ssl; proxy_pass electrs; - ssl_certificate /secrets/nginx_cert; - ssl_certificate_key /secrets/nginx_key; + ssl_certificate /secrets/nginx-cert; + ssl_certificate_key /secrets/nginx-key; ssl_session_cache shared:SSL:1m; ssl_session_timeout 4h; ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3; @@ -117,5 +117,12 @@ in { requires = [ "nix-bitcoin-secrets.target" ]; after = [ "nix-bitcoin-secrets.target" ]; }; + nix-bitcoin.secrets = rec { + nginx-key = { + user = "nginx"; + group = "root"; + }; + nginx-cert = nginx-key; + }; }; } diff --git a/modules/lightning-charge.nix b/modules/lightning-charge.nix index e3eeafc..71140a8 100644 --- a/modules/lightning-charge.nix +++ b/modules/lightning-charge.nix @@ -30,7 +30,7 @@ in { requires = [ "clightning.service" ]; after = [ "clightning.service" ]; serviceConfig = { - EnvironmentFile = "/secrets/lightning-charge-api-token"; + EnvironmentFile = "/secrets/lightning-charge-env"; ExecStart = "${pkgs.nix-bitcoin.lightning-charge}/bin/charged -l ${config.services.clightning.dataDir} -d ${config.services.clightning.dataDir}/lightning-charge.db"; # Unfortunately c-lightning doesn't allow setting the permissions of the rpc socket, # so this must run as the clightning user @@ -42,5 +42,6 @@ in { // nix-bitcoin-services.nodejs // nix-bitcoin-services.allowTor; }; + nix-bitcoin.secrets.lightning-charge-env.user = "clightning"; }; } diff --git a/modules/liquid.nix b/modules/liquid.nix index 5859d23..6a731b5 100644 --- a/modules/liquid.nix +++ b/modules/liquid.nix @@ -233,5 +233,6 @@ in { home = cfg.dataDir; }; users.groups.${cfg.group} = {}; + nix-bitcoin.secrets.liquid-rpcpassword.user = "liquid"; }; } diff --git a/modules/lnd.nix b/modules/lnd.nix index a26e189..75f9d84 100644 --- a/modules/lnd.nix +++ b/modules/lnd.nix @@ -9,8 +9,8 @@ let datadir=${cfg.dataDir} logdir=${cfg.dataDir}/logs bitcoin.mainnet=1 - tlscertpath=/secrets/lnd_cert - tlskeypath=/secrets/lnd_key + tlscertpath=/secrets/lnd-cert + tlskeypath=/secrets/lnd-key rpclisten=localhost:${toString cfg.rpcPort} @@ -61,7 +61,7 @@ in { default = pkgs.writeScriptBin "lncli" # Switch user because lnd makes datadir contents readable by user only '' - exec sudo -u lnd ${pkgs.nix-bitcoin.lnd}/bin/lncli --tlscertpath /secrets/lnd_cert \ + exec sudo -u lnd ${pkgs.nix-bitcoin.lnd}/bin/lncli --tlscertpath /secrets/lnd-cert \ --macaroonpath '${cfg.dataDir}/chain/bitcoin/mainnet/admin.macaroon' "$@" ''; description = "Binary to connect with the lnd instance."; @@ -109,7 +109,7 @@ in { echo Create lnd seed ${pkgs.curl}/bin/curl -s \ - --cacert /secrets/lnd_cert \ + --cacert /secrets/lnd-cert \ -X GET https://127.0.0.1:8080/v1/genseed | ${pkgs.jq}/bin/jq -c '.cipher_seed_mnemonic' > /secrets/lnd-seed-mnemonic fi @@ -117,7 +117,7 @@ in { echo Create lnd wallet ${pkgs.curl}/bin/curl -s --output /dev/null --show-error \ - --cacert /secrets/lnd_cert \ + --cacert /secrets/lnd-cert \ -X POST -d "{\"wallet_password\": \"$(cat /secrets/lnd-wallet-password | tr -d '\n' | base64 -w0)\", \ \"cipher_seed_mnemonic\": $(cat /secrets/lnd-seed-mnemonic | tr -d '\n')}" \ https://127.0.0.1:8080/v1/initwallet @@ -132,7 +132,7 @@ in { ${pkgs.curl}/bin/curl -s \ -H "Grpc-Metadata-macaroon: $(${pkgs.xxd}/bin/xxd -ps -u -c 99999 '${mainnetDir}/admin.macaroon')" \ - --cacert /secrets/lnd_cert \ + --cacert /secrets/lnd-cert \ -X POST \ -d "{\"wallet_password\": \"$(cat /secrets/lnd-wallet-password | tr -d '\n' | base64 -w0)\"}" \ https://127.0.0.1:8080/v1/unlockwallet @@ -151,5 +151,10 @@ in { home = cfg.dataDir; }; users.groups.lnd = {}; + nix-bitcoin.secrets = { + lnd-wallet-password.user = "lnd"; + lnd-key.user = "lnd"; + lnd-cert.user = "lnd"; + }; }; } diff --git a/modules/modules.nix b/modules/modules.nix index 27c67c6..0b85f32 100644 --- a/modules/modules.nix +++ b/modules/modules.nix @@ -23,7 +23,7 @@ in { ./recurring-donations.nix ./hardware-wallets.nix ./lnd.nix - ./secrets/setup-secrets.nix + ./secrets/secrets.nix ]; disabledModules = [ "services/networking/bitcoind.nix" ]; diff --git a/modules/nanopos.nix b/modules/nanopos.nix index 1510198..ee5e04e 100644 --- a/modules/nanopos.nix +++ b/modules/nanopos.nix @@ -58,7 +58,7 @@ in { requires = [ "lightning-charge.service" ]; after = [ "lightning-charge.service" ]; serviceConfig = { - EnvironmentFile = "/secrets/lightning-charge-api-token-for-nanopos"; + EnvironmentFile = "/secrets/nanopos-env"; ExecStart = "${pkgs.nix-bitcoin.nanopos}/bin/nanopos -y ${cfg.itemsFile} -p ${toString cfg.port} --show-bolt11"; User = "nanopos"; @@ -73,5 +73,6 @@ in { group = "nanopos"; }; users.groups.nanopos = {}; + nix-bitcoin.secrets.nanopos-env.user = "nanopos"; }; } diff --git a/modules/secrets/make-secrets.nix b/modules/secrets/make-secrets.nix deleted file mode 100644 index fa9a070..0000000 --- a/modules/secrets/make-secrets.nix +++ /dev/null @@ -1,68 +0,0 @@ -{ secretsFile ? null, config ? null }: -let - secrets = import secretsFile; - secretsDir = "/secrets/"; - secret = { text ? null, keyFile ? null, user, group ? user }: { - inherit text keyFile user group; - destDir = secretsDir; - permissions = "0440"; - }; -in rec { - allSecrets = { - bitcoin-rpcpassword = secret { - text = secrets.bitcoinrpcpassword; - user = "bitcoin"; - group = "bitcoinrpc"; - }; - lnd-wallet-password = secret { - text = secrets.lnd-wallet-password; - user = "lnd"; - }; - lightning-charge-api-token = secret { - text = "API_TOKEN=" + secrets.lightning-charge-api-token; - user = "clightning"; - }; - # variable is called CHARGE_TOKEN instead of API_TOKEN - lightning-charge-api-token-for-nanopos = secret { - text = "CHARGE_TOKEN=" + secrets.lightning-charge-api-token; - user = "nanopos"; - }; - liquid-rpcpassword = secret { - text = secrets.liquidrpcpassword; - user = "liquid"; - }; - spark-wallet-login = secret { - text = "login=" + "spark-wallet:" + secrets.spark-wallet-password; - user = "clightning"; - }; - nginx_key = secret { - keyFile = toString ../../secrets/nginx.key; - user = "nginx"; - group = "root"; - }; - nginx_cert = secret { - keyFile = toString ../../secrets/nginx.cert; - user = "nginx"; - group = "root"; - }; - lnd_key = secret { - keyFile = toString ../../secrets/lnd.key; - user = "lnd"; - }; - lnd_cert = secret { - keyFile = toString ../../secrets/lnd.cert; - user = "lnd"; - }; - }; - - activeSecrets = let - secretsFor = service: attrs: if service.enable then attrs else {}; - in with allSecrets; - (secretsFor config.services.bitcoind { inherit bitcoin-rpcpassword; }) - // (secretsFor config.services.lnd { inherit lnd-wallet-password lnd_key lnd_cert; }) - // (secretsFor config.services.lightning-charge { inherit lightning-charge-api-token; }) - // (secretsFor config.services.nanopos { inherit lightning-charge-api-token-for-nanopos; }) - // (secretsFor config.services.liquidd { inherit liquid-rpcpassword; }) - // (secretsFor config.services.spark-wallet { inherit spark-wallet-login; }) - // (secretsFor config.services.electrs { inherit nginx_key nginx_cert; }); -} diff --git a/modules/secrets/setup-secrets.nix b/modules/secrets/secrets.nix similarity index 54% rename from modules/secrets/setup-secrets.nix rename to modules/secrets/secrets.nix index 13393df..d3a064d 100644 --- a/modules/secrets/setup-secrets.nix +++ b/modules/secrets/secrets.nix @@ -2,18 +2,41 @@ with lib; let + cfg = config.nix-bitcoin; secretsDir = "/secrets/"; # TODO: make this an option - secrets = (import ./make-secrets.nix { inherit config; }).activeSecrets; - setupSecrets = concatStrings (mapAttrsToList (n: v: '' - setupSecret ${n} ${v.user} ${v.group} ${v.permissions} ${optionalString (v.keyFile != null) (baseNameOf v.keyFile)} - '') secrets); + setupSecret ${n} ${v.user} ${v.group} ${v.permissions} } + '') cfg.secrets); in { - options.nix-bitcoin.setup-secrets = mkEnableOption "Set permissions for secrets generated by 'generate-secrets.sh'"; + options.nix-bitcoin = { + secrets = mkOption { + default = {}; + type = with types; attrsOf (submodule ( + { config, ... }: { + options = { + user = mkOption { + type = str; + default = "root"; + }; + group = mkOption { + type = str; + default = config.user; + }; + permissions = mkOption { + type = str; + default = "0440"; + }; + }; + } + )); + }; - config = mkIf config.nix-bitcoin.setup-secrets { + setup-secrets = mkEnableOption "Set permissions for secrets generated by 'generate-secrets.sh'"; + }; + + config = mkIf cfg.setup-secrets { systemd.targets.nix-bitcoin-secrets = { requires = [ "setup-secrets.service" ]; after = [ "setup-secrets.service" ]; @@ -23,7 +46,7 @@ in # - Create missing secrets that are composed of attrs from secrets.nix # - Set owner and permissions for all used secrets # - Make all other secrets accessible to root only - # For all steps make sure that no secrets are copied to the nix-store. + # For all steps make sure that no secrets are copied to the nix store. # systemd.services.setup-secrets = { serviceConfig = { @@ -36,39 +59,15 @@ in user="$2" group="$3" permissions="$4" - srcFile="$5" if [[ ! -e $file ]]; then - if [[ $srcFile ]]; then - if [[ ! -e $srcFile ]]; then - echo "Error: Secret source file '$srcFile' is missing" - exit 1 - fi - mv "$srcFile" "$file" - else - createFile "$file" - fi + echo "Error: Secret file '$file' is missing" + exit 1 fi chown "$user:$group" "$file" chmod "$permissions" "$file" processedFiles+=("$file") } - createFile() { - file="$1" - # 'nix eval' requires filesystem or daemon access to a store even if nothing is built. - # Use a private store so that 'nix eval' always succeeds regardless of the - # execution environment, like a container. - - # This tmp dir is automatically removed by systemd via PrivateTmp - [[ $store ]] || store="$(mktemp -d)" - secretsFile="$(realpath secrets.nix)" \ - ${pkgs.nix}/bin/nix eval --raw --store "$store" "( - (import ${./make-secrets.nix} { - secretsFile = builtins.getEnv \"secretsFile\"; - }).allSecrets.$file.text - )" > "$file" - } - dir="${secretsDir}" if [[ ! -e $dir ]]; then echo "Error: Secrets dir '$dir' is missing" diff --git a/modules/spark-wallet.nix b/modules/spark-wallet.nix index 7717404..aa40974 100644 --- a/modules/spark-wallet.nix +++ b/modules/spark-wallet.nix @@ -73,5 +73,6 @@ in { // nix-bitcoin-services.nodejs // nix-bitcoin-services.allowTor; }; + nix-bitcoin.secrets.spark-wallet-login.user = "clightning"; }; } diff --git a/network/network.nix b/network/network.nix index 269ed45..191355a 100644 --- a/network/network.nix +++ b/network/network.nix @@ -5,10 +5,11 @@ { config, pkgs, lib, ... }: { imports = [ ../configuration.nix ]; - deployment.keys = (import ../modules/secrets/make-secrets.nix { - inherit config; - secretsFile = ../secrets/secrets.nix; - }).activeSecrets; + deployment.keys = builtins.mapAttrs (n: v: { + keyFile = "${toString ../secrets}/${n}"; + destDir = "/secrets/"; + inherit (v) user group permissions; + }) config.nix-bitcoin.secrets; # nixops makes the secrets directory accessible only for users with group 'key'. # For compatibility with other deployment methods besides nixops, we forego the diff --git a/pkgs/generate-secrets/generate-secrets.sh b/pkgs/generate-secrets/generate-secrets.sh index b569c3a..4eac950 100755 --- a/pkgs/generate-secrets/generate-secrets.sh +++ b/pkgs/generate-secrets/generate-secrets.sh @@ -1,43 +1,31 @@ -#!/bin/sh +#!/usr/bin/env bash opensslConf=${1:-openssl.cnf} -secretsFile=secrets.nix -if [ ! -e "$secretsFile" ]; then - echo Write secrets to $secretsFile - makepw="apg -m 20 -x 20 -M Ncl -n 1" - { - echo \{ - echo " bitcoinrpcpassword = \"$($makepw)\";" - echo " lnd-wallet-password = \"$($makepw)\";" - echo " lightning-charge-api-token = \"$($makepw)\";" - echo " liquidrpcpassword = \"$($makepw)\";" - echo " spark-wallet-password = \"$($makepw)\";" - echo \} - } >> $secretsFile - echo Done -else - echo $secretsFile already exists. Skipping. -fi +makePasswordSecret() { + [[ -e $1 ]] || apg -m 20 -x 20 -M Ncl -n 1 > "$1" +} -if [ ! -e nginx.key ] || [ ! -e nginx.cert ]; then - echo Generate Nginx Self-Signed Cert - openssl genrsa -out nginx.key 2048 - openssl req -new -key nginx.key -out nginx.csr -subj "/C=KN" - openssl x509 -req -days 1825 -in nginx.csr -signkey nginx.key -out nginx.cert +makePasswordSecret bitcoin-rpcpassword +makePasswordSecret lnd-wallet-password +makePasswordSecret liquid-rpcpassword +makePasswordSecret lightning-charge-token +makePasswordSecret spark-wallet-password + +[[ -e lightning-charge-env ]] || echo "API_TOKEN=$(cat lightning-charge-token)" > lightning-charge-env +[[ -e nanopos-env ]] || echo "CHARGE_TOKEN=$(cat lightning-charge-token)" > nanopos-env +[[ -e spark-wallet-login ]] || echo "login=spark-wallet:$(cat spark-wallet-password)" > spark-wallet-login + +if [[ ! -e nginx-key || ! -e nginx-cert ]]; then + openssl genrsa -out nginx-key 2048 + openssl req -new -key nginx-key -out nginx.csr -subj "/C=KN" + openssl x509 -req -days 1825 -in nginx.csr -signkey nginx-key -out nginx-cert rm nginx.csr - echo Done -else - echo Nginx Cert already exists. Skipping. fi -if [ ! -e lnd.key ] || [ ! -e lnd.cert ]; then - echo Generate LND compatible TLS Cert - openssl ecparam -genkey -name prime256v1 -out lnd.key - openssl req -config $opensslConf -new -sha256 -key lnd.key -out lnd.csr -subj '/CN=localhost/O=lnd' - openssl req -config $opensslConf -x509 -sha256 -days 1825 -key lnd.key -in lnd.csr -out lnd.cert +if [[ ! -e lnd-key || ! -e lnd-cert ]]; then + openssl ecparam -genkey -name prime256v1 -out lnd-key + openssl req -config $opensslConf -new -sha256 -key lnd-key -out lnd.csr -subj '/CN=localhost/O=lnd' + openssl req -config $opensslConf -x509 -sha256 -days 1825 -key lnd-key -in lnd.csr -out lnd-cert rm lnd.csr - echo Done -else - echo LND cert already exists. Skipping. fi diff --git a/pkgs/generate-secrets/update-and-generate.nix b/pkgs/generate-secrets/update-and-generate.nix new file mode 100644 index 0000000..c2cfae9 --- /dev/null +++ b/pkgs/generate-secrets/update-and-generate.nix @@ -0,0 +1,10 @@ +{ pkgs }: with pkgs; + +let + generate-secrets = callPackage ./. {}; +in +writeScript "make-secrets" '' + # Update from old secrets format + [[ -e secrets.nix ]] && . ${./update-secrets.sh} + ${generate-secrets} +'' diff --git a/pkgs/generate-secrets/update-secrets.sh b/pkgs/generate-secrets/update-secrets.sh new file mode 100644 index 0000000..c9600aa --- /dev/null +++ b/pkgs/generate-secrets/update-secrets.sh @@ -0,0 +1,48 @@ +#!/usr/bin/env bash + +set -eo pipefail + +# Update secrets from the old format to the current one where each secret +# has a local source file. + +reportError() { + echo "Updating secrets failed. (Error in line $1)" + echo "The secret files have been moved to secrets/old-secrets" +} +trap 'reportError $LINENO' ERR + +echo "Updating old secrets to the current format." + +mkdir old-secrets +# move all files into old-secrets +shopt -s extglob dotglob +mv !(old-secrets) old-secrets +shopt -u dotglob + +secrets=$(cat old-secrets/secrets.nix) + +extractPassword() { + pwName="$1" + destFile="${2:-$pwName}" + echo "$secrets" | sed -nE "s/.*?$pwName = \"(.*?)\".*/\1/p" > "$destFile" +} + +rename() { + old="old-secrets/$1" + if [[ -e $old ]]; then + cp "$old" "$2" + fi +} + +extractPassword bitcoinrpcpassword bitcoin-rpcpassword +extractPassword lnd-wallet-password +extractPassword liquidrpcpassword liquid-rpcpassword +extractPassword lightning-charge-api-token lightning-charge-token +extractPassword spark-wallet-password + +rename nginx.key nginx-key +rename nginx.cert nginx-cert +rename lnd.key lnd-key +rename lnd.cert lnd-cert + +rm -r old-secrets diff --git a/shell.nix b/shell.nix index 769a8e8..45ab264 100644 --- a/shell.nix +++ b/shell.nix @@ -7,7 +7,7 @@ stdenv.mkDerivation rec { name = "nix-bitcoin-environment"; nixops19_09 = callPackage ./pkgs/nixops {}; - generate-secrets = callPackage ./pkgs/generate-secrets {}; + make-secrets = callPackage ./pkgs/generate-secrets/update-and-generate.nix {}; buildInputs = [ nixops19_09 figlet ]; @@ -19,6 +19,6 @@ stdenv.mkDerivation rec { # keys already added to my ssh-agent. export SSH_AUTH_SOCK="" figlet "nix-bitcoin" - (mkdir -p secrets; cd secrets; ${generate-secrets}) + (mkdir -p secrets; cd secrets; ${make-secrets}) ''; } From 826245484ec0af402a54f85c8f228b29c91db1f7 Mon Sep 17 00:00:00 2001 From: Erik Arvstedt Date: Sun, 12 Jan 2020 20:52:39 +0100 Subject: [PATCH 41/42] make secrets dir location configurable Users of the nix-bitcoin modules shouldn't be forced to add an extra dir under root. The secrets location is unchanged for the default node config. --- modules/bitcoind.nix | 2 +- modules/clightning.nix | 2 +- modules/electrs.nix | 7 ++++--- modules/lightning-charge.nix | 2 +- modules/liquid.nix | 5 +++-- modules/lnd.nix | 25 +++++++++++++------------ modules/nanopos.nix | 2 +- modules/nix-bitcoin.nix | 2 ++ modules/secrets/generate-secrets.nix | 7 ++----- modules/secrets/secrets.nix | 10 +++++++--- modules/spark-wallet.nix | 2 +- network/network.nix | 4 ++-- 12 files changed, 38 insertions(+), 32 deletions(-) diff --git a/modules/bitcoind.nix b/modules/bitcoind.nix index a4c147e..b2a6bf3 100644 --- a/modules/bitcoind.nix +++ b/modules/bitcoind.nix @@ -238,7 +238,7 @@ in { cp '${cfg.configFileOption}' '${cfg.dataDir}/bitcoin.conf' chmod o-rw '${cfg.dataDir}/bitcoin.conf' chown -R '${cfg.user}:${cfg.group}' '${cfg.dataDir}' - echo "rpcpassword=$(cat /secrets/bitcoin-rpcpassword)" >> '${cfg.dataDir}/bitcoin.conf' + echo "rpcpassword=$(cat ${config.nix-bitcoin.secretsDir}/bitcoin-rpcpassword)" >> '${cfg.dataDir}/bitcoin.conf' chmod -R g+rX '${cfg.dataDir}/blocks' ''; # Wait until RPC port is open. This usually takes just a few ms. diff --git a/modules/clightning.nix b/modules/clightning.nix index f3de4a0..5b7b15b 100644 --- a/modules/clightning.nix +++ b/modules/clightning.nix @@ -93,7 +93,7 @@ in { chmod u=rw,g=r,o= ${cfg.dataDir}/config # The RPC socket has to be removed otherwise we might have stale sockets rm -f ${cfg.dataDir}/lightning-rpc - echo "bitcoin-rpcpassword=$(cat /secrets/bitcoin-rpcpassword)" >> '${cfg.dataDir}/config' + echo "bitcoin-rpcpassword=$(cat ${config.nix-bitcoin.secretsDir}/bitcoin-rpcpassword)" >> '${cfg.dataDir}/config' ''; serviceConfig = { PermissionsStartOnly = "true"; diff --git a/modules/electrs.nix b/modules/electrs.nix index 7f98b97..dfeddc9 100644 --- a/modules/electrs.nix +++ b/modules/electrs.nix @@ -5,6 +5,7 @@ with lib; let cfg = config.services.electrs; inherit (config) nix-bitcoin-services; + secretsDir = config.nix-bitcoin.secretsDir; index-batch-size = "${if cfg.high-memory then "" else "--index-batch-size=10"}"; jsonrpc-import = "${if cfg.high-memory then "" else "--jsonrpc-import"}"; in { @@ -74,7 +75,7 @@ in { preStart = '' mkdir -m 0770 -p ${cfg.dataDir} chown -R '${cfg.user}:${cfg.group}' ${cfg.dataDir} - echo "${pkgs.nix-bitcoin.electrs}/bin/electrs -vvv ${index-batch-size} ${jsonrpc-import} --timestamp --db-dir ${cfg.dataDir} --daemon-dir /var/lib/bitcoind --cookie=${config.services.bitcoind.rpcuser}:$(cat /secrets/bitcoin-rpcpassword) --electrum-rpc-addr=127.0.0.1:${toString cfg.port}" > /run/electrs/startscript.sh + echo "${pkgs.nix-bitcoin.electrs}/bin/electrs -vvv ${index-batch-size} ${jsonrpc-import} --timestamp --db-dir ${cfg.dataDir} --daemon-dir /var/lib/bitcoind --cookie=${config.services.bitcoind.rpcuser}:$(cat ${secretsDir}/bitcoin-rpcpassword) --electrum-rpc-addr=127.0.0.1:${toString cfg.port}" > /run/electrs/startscript.sh ''; serviceConfig = rec { RuntimeDirectory = "electrs"; @@ -103,8 +104,8 @@ in { listen ${toString config.services.electrs.nginxport} ssl; proxy_pass electrs; - ssl_certificate /secrets/nginx-cert; - ssl_certificate_key /secrets/nginx-key; + ssl_certificate ${secretsDir}/nginx-cert; + ssl_certificate_key ${secretsDir}/nginx-key; ssl_session_cache shared:SSL:1m; ssl_session_timeout 4h; ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3; diff --git a/modules/lightning-charge.nix b/modules/lightning-charge.nix index 71140a8..5f43ed0 100644 --- a/modules/lightning-charge.nix +++ b/modules/lightning-charge.nix @@ -30,7 +30,7 @@ in { requires = [ "clightning.service" ]; after = [ "clightning.service" ]; serviceConfig = { - EnvironmentFile = "/secrets/lightning-charge-env"; + EnvironmentFile = "${config.nix-bitcoin.secretsDir}/lightning-charge-env"; ExecStart = "${pkgs.nix-bitcoin.lightning-charge}/bin/charged -l ${config.services.clightning.dataDir} -d ${config.services.clightning.dataDir}/lightning-charge.db"; # Unfortunately c-lightning doesn't allow setting the permissions of the rpc socket, # so this must run as the clightning user diff --git a/modules/liquid.nix b/modules/liquid.nix index 6a731b5..3dce19a 100644 --- a/modules/liquid.nix +++ b/modules/liquid.nix @@ -5,6 +5,7 @@ with lib; let cfg = config.services.liquidd; inherit (config) nix-bitcoin-services; + secretsDir = config.nix-bitcoin.secretsDir; pidFile = "${cfg.dataDir}/liquidd.pid"; configFile = pkgs.writeText "elements.conf" '' chain=liquidv1 @@ -207,8 +208,8 @@ in { cp '${configFile}' '${cfg.dataDir}/elements.conf' chmod o-rw '${cfg.dataDir}/elements.conf' chown -R '${cfg.user}:${cfg.group}' '${cfg.dataDir}' - echo "rpcpassword=$(cat /secrets/liquid-rpcpassword)" >> '${cfg.dataDir}/elements.conf' - echo "mainchainrpcpassword=$(cat /secrets/bitcoin-rpcpassword)" >> '${cfg.dataDir}/elements.conf' + echo "rpcpassword=$(cat ${secretsDir}/liquid-rpcpassword)" >> '${cfg.dataDir}/elements.conf' + echo "mainchainrpcpassword=$(cat ${secretsDir}/bitcoin-rpcpassword)" >> '${cfg.dataDir}/elements.conf' ''; serviceConfig = { Type = "simple"; diff --git a/modules/lnd.nix b/modules/lnd.nix index 75f9d84..651883f 100644 --- a/modules/lnd.nix +++ b/modules/lnd.nix @@ -5,12 +5,13 @@ with lib; let cfg = config.services.lnd; inherit (config) nix-bitcoin-services; + secretsDir = config.nix-bitcoin.secretsDir; configFile = pkgs.writeText "lnd.conf" '' datadir=${cfg.dataDir} logdir=${cfg.dataDir}/logs bitcoin.mainnet=1 - tlscertpath=/secrets/lnd-cert - tlskeypath=/secrets/lnd-key + tlscertpath=${secretsDir}/lnd-cert + tlskeypath=${secretsDir}/lnd-key rpclisten=localhost:${toString cfg.rpcPort} @@ -61,7 +62,7 @@ in { default = pkgs.writeScriptBin "lncli" # Switch user because lnd makes datadir contents readable by user only '' - exec sudo -u lnd ${pkgs.nix-bitcoin.lnd}/bin/lncli --tlscertpath /secrets/lnd-cert \ + exec sudo -u lnd ${pkgs.nix-bitcoin.lnd}/bin/lncli --tlscertpath ${secretsDir}/lnd-cert \ --macaroonpath '${cfg.dataDir}/chain/bitcoin/mainnet/admin.macaroon' "$@" ''; description = "Binary to connect with the lnd instance."; @@ -81,7 +82,7 @@ in { cp ${configFile} ${cfg.dataDir}/lnd.conf chown -R 'lnd:lnd' '${cfg.dataDir}' chmod u=rw,g=r,o= ${cfg.dataDir}/lnd.conf - echo "bitcoind.rpcpass=$(cat /secrets/bitcoin-rpcpassword)" >> '${cfg.dataDir}/lnd.conf' + echo "bitcoind.rpcpass=$(cat ${secretsDir}/bitcoin-rpcpassword)" >> '${cfg.dataDir}/lnd.conf' ''; serviceConfig = { PermissionsStartOnly = "true"; @@ -105,21 +106,21 @@ in { sleep 0.1 done - if [[ ! -f /secrets/lnd-seed-mnemonic ]]; then + if [[ ! -f ${secretsDir}/lnd-seed-mnemonic ]]; then echo Create lnd seed ${pkgs.curl}/bin/curl -s \ - --cacert /secrets/lnd-cert \ - -X GET https://127.0.0.1:8080/v1/genseed | ${pkgs.jq}/bin/jq -c '.cipher_seed_mnemonic' > /secrets/lnd-seed-mnemonic + --cacert ${secretsDir}/lnd-cert \ + -X GET https://127.0.0.1:8080/v1/genseed | ${pkgs.jq}/bin/jq -c '.cipher_seed_mnemonic' > ${secretsDir}/lnd-seed-mnemonic fi if [[ ! -f ${mainnetDir}/wallet.db ]]; then echo Create lnd wallet ${pkgs.curl}/bin/curl -s --output /dev/null --show-error \ - --cacert /secrets/lnd-cert \ - -X POST -d "{\"wallet_password\": \"$(cat /secrets/lnd-wallet-password | tr -d '\n' | base64 -w0)\", \ - \"cipher_seed_mnemonic\": $(cat /secrets/lnd-seed-mnemonic | tr -d '\n')}" \ + --cacert ${secretsDir}/lnd-cert \ + -X POST -d "{\"wallet_password\": \"$(cat ${secretsDir}/lnd-wallet-password | tr -d '\n' | base64 -w0)\", \ + \"cipher_seed_mnemonic\": $(cat ${secretsDir}/lnd-seed-mnemonic | tr -d '\n')}" \ https://127.0.0.1:8080/v1/initwallet # Guarantees that RPC calls with cfg.cli succeed after the service is started @@ -132,9 +133,9 @@ in { ${pkgs.curl}/bin/curl -s \ -H "Grpc-Metadata-macaroon: $(${pkgs.xxd}/bin/xxd -ps -u -c 99999 '${mainnetDir}/admin.macaroon')" \ - --cacert /secrets/lnd-cert \ + --cacert ${secretsDir}/lnd-cert \ -X POST \ - -d "{\"wallet_password\": \"$(cat /secrets/lnd-wallet-password | tr -d '\n' | base64 -w0)\"}" \ + -d "{\"wallet_password\": \"$(cat ${secretsDir}/lnd-wallet-password | tr -d '\n' | base64 -w0)\"}" \ https://127.0.0.1:8080/v1/unlockwallet fi diff --git a/modules/nanopos.nix b/modules/nanopos.nix index ee5e04e..b34cf6d 100644 --- a/modules/nanopos.nix +++ b/modules/nanopos.nix @@ -58,7 +58,7 @@ in { requires = [ "lightning-charge.service" ]; after = [ "lightning-charge.service" ]; serviceConfig = { - EnvironmentFile = "/secrets/nanopos-env"; + EnvironmentFile = "${config.nix-bitcoin.secretsDir}/nanopos-env"; ExecStart = "${pkgs.nix-bitcoin.nanopos}/bin/nanopos -y ${cfg.itemsFile} -p ${toString cfg.port} --show-bolt11"; User = "nanopos"; diff --git a/modules/nix-bitcoin.nix b/modules/nix-bitcoin.nix index 5cbab71..e8c7af2 100644 --- a/modules/nix-bitcoin.nix +++ b/modules/nix-bitcoin.nix @@ -28,6 +28,8 @@ in { }; config = mkIf cfg.enable { + nix-bitcoin.secretsDir = mkDefault "/secrets"; + networking.firewall.enable = true; # Tor diff --git a/modules/secrets/generate-secrets.nix b/modules/secrets/generate-secrets.nix index f7edc03..fa72110 100644 --- a/modules/secrets/generate-secrets.nix +++ b/modules/secrets/generate-secrets.nix @@ -5,9 +5,6 @@ # generated secrets. with lib; -let - secretsDir = "/secrets/"; # TODO: make this an option -in { nix-bitcoin.setup-secrets = true; @@ -19,8 +16,8 @@ in RemainAfterExit = true; } // config.nix-bitcoin-services.defaultHardening; script = '' - mkdir -p "${secretsDir}" - cd "${secretsDir}" + mkdir -p "${config.nix-bitcoin.secretsDir}" + cd "${config.nix-bitcoin.secretsDir}" chown root: . chmod 0700 . ${pkgs.nix-bitcoin.generate-secrets} diff --git a/modules/secrets/secrets.nix b/modules/secrets/secrets.nix index d3a064d..163346d 100644 --- a/modules/secrets/secrets.nix +++ b/modules/secrets/secrets.nix @@ -3,14 +3,18 @@ with lib; let cfg = config.nix-bitcoin; - secretsDir = "/secrets/"; # TODO: make this an option - setupSecrets = concatStrings (mapAttrsToList (n: v: '' setupSecret ${n} ${v.user} ${v.group} ${v.permissions} } '') cfg.secrets); in { options.nix-bitcoin = { + secretsDir = mkOption { + type = types.path; + default = "/etc/nix-bitcoin-secrets"; + description = "Directory to store secrets"; + }; + secrets = mkOption { default = {}; type = with types; attrsOf (submodule ( @@ -68,7 +72,7 @@ in processedFiles+=("$file") } - dir="${secretsDir}" + dir="${cfg.secretsDir}" if [[ ! -e $dir ]]; then echo "Error: Secrets dir '$dir' is missing" exit 1 diff --git a/modules/spark-wallet.nix b/modules/spark-wallet.nix index aa40974..bef8d45 100644 --- a/modules/spark-wallet.nix +++ b/modules/spark-wallet.nix @@ -8,7 +8,7 @@ let dataDir = "/var/lib/spark-wallet/"; onion-chef-service = (if cfg.onion-service then [ "onion-chef.service" ] else []); run-spark-wallet = pkgs.writeScript "run-spark-wallet" '' - CMD="${pkgs.nix-bitcoin.spark-wallet}/bin/spark-wallet --ln-path ${cfg.ln-path} -Q -k -c /secrets/spark-wallet-login" + CMD="${pkgs.nix-bitcoin.spark-wallet}/bin/spark-wallet --ln-path ${cfg.ln-path} -Q -k -c ${config.nix-bitcoin.secretsDir}/spark-wallet-login" ${optionalString cfg.onion-service '' echo Getting onion hostname diff --git a/network/network.nix b/network/network.nix index 191355a..fa69939 100644 --- a/network/network.nix +++ b/network/network.nix @@ -7,7 +7,7 @@ deployment.keys = builtins.mapAttrs (n: v: { keyFile = "${toString ../secrets}/${n}"; - destDir = "/secrets/"; + destDir = config.nix-bitcoin.secretsDir; inherit (v) user group permissions; }) config.nix-bitcoin.secrets; @@ -19,7 +19,7 @@ systemd.services.allowSecretsDirAccess = { requires = [ "keys.target" ]; after = [ "keys.target" ]; - script = "chmod o+x /secrets"; + script = "chmod o+x ${config.nix-bitcoin.secretsDir}"; serviceConfig.Type = "oneshot"; }; From 187ff884dbf064ef80ebd47a3f49ca985cb0bdf5 Mon Sep 17 00:00:00 2001 From: Erik Arvstedt Date: Sun, 12 Jan 2020 20:52:40 +0100 Subject: [PATCH 42/42] add modules test The build of package 'nodeinfo' is implicitly tested by the modules test. --- .travis.yml | 19 ++++++- test/make-test.nix | 44 +++++++++++++++ test/run-tests.sh | 106 +++++++++++++++++++++++++++++++++++++ test/test-script.py | 104 ++++++++++++++++++++++++++++++++++++ test/test.nix | 52 ++++++++++++++++++ test/use-stable-pkgs.patch | 41 ++++++++++++++ 6 files changed, 364 insertions(+), 2 deletions(-) create mode 100644 test/make-test.nix create mode 100755 test/run-tests.sh create mode 100644 test/test-script.py create mode 100644 test/test.nix create mode 100644 test/use-stable-pkgs.patch diff --git a/.travis.yml b/.travis.yml index 3835300..4fc3a42 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,3 +1,4 @@ +dist: bionic # needed for KVM language: shell install: @@ -18,7 +19,7 @@ env: # CACHIX_SIGNING_KEY - secure: "xXCFZ7g+k5YmCGm8R8l3bZElVmt+RD1KscG3kGr5w4HyyDPTzFetPo+sT8bUpysDU0u3HWhfVhHtpog2mhNhwVl3tQwKXea3dHKC1i6ypBg3gjDngmJRR5wo++ocYDpK8qPaU7m/jHQTNFnTA4CbmMcc05GcYx/1Ai/ZGkNwWFjdIcVeOUoiol33gykMOXIGDg2qlXudt33wP53FHbX8L4fxzodWfAuxKK4AoGprxy5eSnU7LCaXxxJmu4HwuV+Ux2U1NfE/E33cvhlUvTQCswVSZFG06mg8rwhMG1ozsDvlL2itZlu/BeUQH5y3XMMlnJIUXUazkRBibf1w/ebVjpOF+anqkqmq8tcbFEa7T+RJeVTIsvP+L8rE8fcmuZtdg9hNmgRnLmaeT0vVwD1L2UqW9HdRyujdoS0jPYuoc1W7f1JQWfAPhBPQ1SrtKyNNqcbVJ34aN7b+4vCzRpQL1JTbmjzQIWhkiKN1qMo1v/wbIydW8yka4hc4JOfdQLaAJEPI1eAC1MLotSAegMnwKWE1dzm66MuPSipksYjZrvsB28cV4aCVUffIuRhrSr1i2afRHwTpNbK9U4/576hah15ftUdR79Sfkcoi1ekSQTFGRvkRIPYtkKLYwFa3jVA41qz7+IIZCf4TsApy3XDdFx91cRub7yPq9BeZ83A+qYQ=" jobs: - - PKG=nodeinfo STABLE=1 + - TestModules=1 STABLE=1 - PKG=hwi STABLE=1 - PKG=lightning-charge STABLE=1 - PKG=lightning-charge STABLE=0 @@ -33,7 +34,21 @@ env: - PKG=liquid-swap STABLE=1 script: - printf '%s (%s)\n' "$NIX_PATH" "$VER" - - time nix-instantiate -A $PKG --add-root ./drv --indirect + - | + getBuildExpr() { + if [[ $TestModules ]]; then + if [[ ! -e /dev/kvm ]]; then + >&2 echo "No KVM available on VM Host." + exit 1 + fi + sudo chmod go+rw /dev/kvm + test/run-tests.sh exprForCI + else + echo "(import ./. {}).$PKG" + fi + } + - buildExpr=$(getBuildExpr) + - time nix-instantiate -E "$buildExpr" --add-root ./drv --indirect - outPath=$(nix-store --query ./drv) - | if nix path-info --store https://nix-bitcoin.cachix.org $outPath &>/dev/null; then diff --git a/test/make-test.nix b/test/make-test.nix new file mode 100644 index 0000000..018ab27 --- /dev/null +++ b/test/make-test.nix @@ -0,0 +1,44 @@ +testArgs: + +let + pkgs = import { config = {}; overlays = []; }; + + # Stable nixpkgs doesn't yet include the Python testing framework. + # Use unstable nixpkgs and patch it so that it uses stable nixpkgs for the VM + # machine configuration. + testingPkgs = let + # unstable as of 2020-01-09 + rev = "9beb0d1ac2ebd6063efbdc4d3631f8ce137bbf90"; + src = builtins.fetchTarball { + url = "https://github.com/nixos/nixpkgs-channels/archive/${rev}.tar.gz"; + sha256 = "1v95779di35qhrz70p2v27kmwm09h8pgh74i1wc72v0zp3bdrf50"; + }; + in + pkgs.runCommand "nixpkgs-testing" {} '' + cp -r ${src} $out + cd $out + chmod +w -R . + patch -p1 < ${./use-stable-pkgs.patch} + ''; + + test = (import "${testingPkgs}/nixos/tests/make-test-python.nix") testArgs; + + # Fix the black Python code formatter that's used in the test to allow the test + # script to have longer lines. The default width of 88 chars is too restrictive for + # our script. + fixedTest = { system ? builtins.currentSystem, ... }@args: + let + pkgs = (import testingPkgs { inherit system; config = {}; overlays = []; } ); + pkgsFixed = pkgs // { + python3Packages = pkgs.python3Packages // { + black = pkgs.writeScriptBin "black" '' + fileToCheck=''${@:$#} + [[ $fileToCheck = *test-script ]] && extraArgs='--line-length 100' + exec ${pkgs.python3Packages.black}/bin/black $extraArgs "$@" + ''; + }; + }; + in + test (args // { pkgs = pkgsFixed; }); +in + fixedTest diff --git a/test/run-tests.sh b/test/run-tests.sh new file mode 100755 index 0000000..69a969f --- /dev/null +++ b/test/run-tests.sh @@ -0,0 +1,106 @@ +#!/usr/bin/env bash + +# Modules integration test runner. +# The test (./test.nix) uses the NixOS testing framework and is executed in a VM. +# +# Usage: +# Run test +# ./run-tests.sh +# +# Run test and save result to avoid garbage collection +# ./run-tests.sh build --out-link /tmp/nix-bitcoin-test +# +# Run interactive test debugging +# ./run-tests.sh debug +# +# This starts the testing VM and drops you into a Python REPL where you can +# manually execute the tests from ./test-script.py + +set -eo pipefail + +numCPUs=${numCPUs:-$(nproc)} +# Min. 800 MiB needed to avoid 'out of memory' errors +memoryMiB=${memoryMiB:-2048} + +scriptDir=$(cd "${BASH_SOURCE[0]%/*}" && pwd) + +getPkgs() { + nix eval --raw -f "$scriptDir/../pkgs/nixpkgs-pinned.nix" $1 +} +export NIX_PATH=nixpkgs=$(getPkgs nixpkgs):nixpkgs-unstable=$(getPkgs nixpkgs-unstable) + +# Run the test. No temporary files are left on the host system. +run() { + # TMPDIR is also used by the test driver for VM tmp files + export TMPDIR=$(mktemp -d -p /tmp nix-bitcoin-test.XXXXXX) + trap "rm -rf $TMPDIR" EXIT + + nix-build --out-link $TMPDIR/driver "$scriptDir/test.nix" -A driver + + # Variable 'tests' contains the Python code that is executed by the driver on startup + if [[ $1 == --interactive ]]; then + echo "Running interactive testing environment" + tests=$( + echo 'is_interactive = True' + # The test script raises an error when 'is_interactive' is defined so + # that it just loads the initial helper functions and stops before + # executing the actual tests + echo 'try:' + echo ' exec(os.environ["testScript"])' + echo 'except:' + echo ' pass' + # Start VM + echo 'start_all()' + # Start REPL + echo 'import code' + echo 'code.interact(local=globals())' + ) + else + tests='exec(os.environ["testScript"])' + fi + + echo "VM stats: CPUs: $numCPUs, memory: $memoryMiB MiB" + [[ $NB_TEST_ENABLE_NETWORK ]] || QEMU_NET_OPTS='restrict=on' + env -i \ + NIX_PATH="$NIX_PATH" \ + TMPDIR="$TMPDIR" \ + tests="$tests" \ + QEMU_OPTS="-smp $numCPUs -m $memoryMiB -nographic $QEMU_OPTS" \ + QEMU_NET_OPTS="$QEMU_NET_OPTS" \ + $TMPDIR/driver/bin/nixos-test-driver +} + +debug() { + run --interactive +} + +# Run the test by building the test derivation +build() { + vmTestNixExpr | nix-build --no-out-link "$@" - +} + +# On continuous integration nodes there are few other processes running alongside the +# test, so use more memory here for maximum performance. +exprForCI() { + memoryMiB=3072 + memTotalKiB=$(awk '/MemTotal/ { print $2 }' /proc/meminfo) + memAvailableKiB=$(awk '/MemAvailable/ { print $2 }' /proc/meminfo) + # Round down to nearest multiple of 50 MiB for improved test build caching + ((memAvailableMiB = memAvailableKiB / (1024 * 50) * 50)) + ((memAvailableMiB < memoryMiB)) && memoryMiB=$memAvailableMiB + >&2 echo "Host memory: total $((memTotalKiB / 1024)) MiB, available $memAvailableMiB MiB, VM $memoryMiB MiB" + vmTestNixExpr +} + +vmTestNixExpr() { + cat < ]; + security.allowUserNamespaces = true; # reenable disabled option + }; + + machine = { pkgs, lib, ... }: with lib; { + imports = [ + ../modules/nix-bitcoin.nix + ../modules/secrets/generate-secrets.nix + # using the hardened profile increases total test duration by ~50%, so disable it for now + # hardened + ]; + + services.nix-bitcoin.enable = true; + services.bitcoind.extraConfig = mkForce "connect=0"; + + services.clightning.enable = true; + services.spark-wallet.enable = true; + services.lightning-charge.enable = true; + services.nanopos.enable = true; + + services.lnd.enable = true; + systemd.services.lnd.wantedBy = mkForce []; + + services.electrs.enable = true; + + services.liquidd = { + enable = true; + listen = mkForce false; + extraConfig = "noconnect=1"; + }; + + services.nix-bitcoin-webindex.enable = true; + + services.hardware-wallets = { + trezor = true; + ledger = true; + }; + + # to test that unused secrets are made inaccessible by 'setup-secrets' + systemd.services.generate-secrets.postStart = '' + install -o nobody -g nogroup -m777 <(:) /secrets/dummy + ''; + }; + + testScript = builtins.readFile ./test-script.py; +} diff --git a/test/use-stable-pkgs.patch b/test/use-stable-pkgs.patch new file mode 100644 index 0000000..d89cf2d --- /dev/null +++ b/test/use-stable-pkgs.patch @@ -0,0 +1,41 @@ +--- a/nixos/lib/build-vms.nix ++++ b/nixos/lib/build-vms.nix +@@ -30,10 +30,10 @@ rec { + buildVM = + nodes: configurations: + +- import ./eval-config.nix { ++ import { + inherit system; + modules = configurations ++ extraConfigurations; +- baseModules = (import ../modules/module-list.nix) ++ ++ baseModules = (import ) ++ + [ ../modules/virtualisation/qemu-vm.nix + ../modules/testing/test-instrumentation.nix # !!! should only get added for automated test runs + { key = "no-manual"; documentation.nixos.enable = false; } + + +services.connman doesn't exist in stable nixpkgs +--- a/nixos/modules/virtualisation/qemu-vm.nix ++++ b/nixos/modules/virtualisation/qemu-vm.nix +@@ -620,7 +620,6 @@ in + + # Wireless won't work in the VM. + networking.wireless.enable = mkVMOverride false; +- services.connman.enable = mkVMOverride false; + + # Speed up booting by not waiting for ARP. + networking.dhcpcd.extraConfig = "noarp"; + +The test driver assumed coreutils to be in PATH. This fix will be ported to upstream. +--- a/nixos/lib/testing-python.nix ++++ b/nixos/lib/testing-python.nix +@@ -155,7 +155,7 @@ in rec { + --add-flags "''${vms[*]}" \ + ${lib.optionalString enableOCR + "--prefix PATH : '${ocrProg}/bin:${imagemagick_tiff}/bin'"} \ +- --run "export testScript=\"\$(cat $out/test-script)\"" \ ++ --run "export testScript=\"\$(${coreutils}/bin/cat $out/test-script)\"" \ + --set VLANS '${toString vlans}' + ln -s ${testDriver}/bin/nixos-test-driver $out/bin/nixos-run-vms + wrapProgram $out/bin/nixos-run-vms \