diff --git a/examples/configuration.nix b/examples/configuration.nix index 514a4ea..08ea8bf 100644 --- a/examples/configuration.nix +++ b/examples/configuration.nix @@ -76,6 +76,20 @@ # sync faster. Only available if hardware wallets are disabled. # services.electrs.high-memory = true; + ### BTCPayServer + # Enable this module to use BTCPayServer, a self-hosted, open-source + # cryptocurrency payment processor. + # Privacy Warning: BTCPayServer currently looks up price rates without + # proxying them through Tor. This means an outside observer can correlate + # your BTCPayServer usage, like invoice creation times, with your IP address. + # services.btcpayserver.enable = true; + # Enable this option to connect BTCPayServer to clightning. + # services.btcpayserver.lightningBackend = "clightning"; + # Enable this option to connect BTCPayServert to lnd. + # services.btcpayserver.lightningBackend = "lnd"; + # Afterwards you need to go into Store > General Settings > Lightning Nodes + # and click to use "the internal lightning node of this BTCPay Server". + ### LIQUIDD # Enable this module to use Liquid, a sidechain for an inter-exchange # settlement network linking together cryptocurrency exchanges and diff --git a/modules/btcpayserver.nix b/modules/btcpayserver.nix new file mode 100644 index 0000000..6465ab0 --- /dev/null +++ b/modules/btcpayserver.nix @@ -0,0 +1,211 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services; + inherit (config) nix-bitcoin-services; +in { + options.services = { + nbxplorer = { + package = mkOption { + type = types.package; + default = pkgs.nix-bitcoin.nbxplorer; + defaultText = "pkgs.nix-bitcoin.nbxplorer"; + description = "The package providing nbxplorer binaries."; + }; + dataDir = mkOption { + type = types.path; + default = "/var/lib/nbxplorer"; + description = "The data directory for nbxplorer."; + }; + user = mkOption { + type = types.str; + default = "nbxplorer"; + description = "The user as which to run nbxplorer."; + }; + group = mkOption { + type = types.str; + default = cfg.nbxplorer.user; + description = "The group as which to run nbxplorer."; + }; + bind = mkOption { + type = types.str; + default = "127.0.0.1"; + description = "The address on which to bind."; + }; + enable = mkOption { + # This option is only used by netns-isolation + internal = true; + default = cfg.btcpayserver.enable; + }; + enforceTor = nix-bitcoin-services.enforceTor; + }; + + btcpayserver = { + enable = mkEnableOption "btcpayserver"; + package = mkOption { + type = types.package; + default = pkgs.nix-bitcoin.btcpayserver; + defaultText = "pkgs.nix-bitcoin.btcpayserver"; + description = "The package providing btcpayserver binaries."; + }; + dataDir = mkOption { + type = types.path; + default = "/var/lib/btcpayserver"; + description = "The data directory for btcpayserver."; + }; + user = mkOption { + type = types.str; + default = "btcpayserver"; + description = "The user as which to run btcpayserver."; + }; + group = mkOption { + type = types.str; + default = cfg.btcpayserver.user; + description = "The group as which to run btcpayserver."; + }; + bind = mkOption { + type = types.str; + default = "127.0.0.1"; + description = "The address on which to bind."; + }; + lightningBackend = mkOption { + type = types.nullOr (types.enum [ "clightning" "lnd" ]); + default = null; + description = "The lightning node implementation to use."; + }; + enforceTor = nix-bitcoin-services.enforceTor; + }; + }; + + config = mkIf cfg.btcpayserver.enable { + assertions = let + backend = cfg.btcpayserver.lightningBackend; + in [ + { assertion = (backend != null) -> cfg.${backend}.enable; + message = "btcpayserver requires ${backend}."; + } + ]; + + systemd.tmpfiles.rules = [ + "d '${cfg.nbxplorer.dataDir}' 0770 ${cfg.nbxplorer.user} ${cfg.nbxplorer.group} - -" + "d '${cfg.btcpayserver.dataDir}' 0770 ${cfg.btcpayserver.user} ${cfg.btcpayserver.group} - -" + ]; + + systemd.services.nbxplorer = let + configFile = builtins.toFile "config" '' + network=mainnet + btcrpcuser=${cfg.bitcoind.rpc.users.btcpayserver.name} + btcrpcurl=http://${builtins.elemAt config.services.bitcoind.rpcbind 0}:8332 + btcnodeendpoint=${config.services.bitcoind.bind}:8333 + bind=${cfg.nbxplorer.bind} + ''; + in { + description = "Run nbxplorer"; + wantedBy = [ "multi-user.target" ]; + requires = [ "bitcoind.service" ]; + after = [ "bitcoind.service" ]; + preStart = '' + install -m 600 ${configFile} ${cfg.nbxplorer.dataDir}/settings.config + echo "btcrpcpassword=$(cat ${config.nix-bitcoin.secretsDir}/bitcoin-rpcpassword-btcpayserver)" \ + >> '${cfg.nbxplorer.dataDir}/settings.config' + ''; + serviceConfig = nix-bitcoin-services.defaultHardening // { + ExecStart = '' + ${cfg.nbxplorer.package}/bin/nbxplorer --conf=${cfg.nbxplorer.dataDir}/settings.config \ + --datadir=${cfg.nbxplorer.dataDir} + ''; + User = cfg.nbxplorer.user; + Restart = "on-failure"; + RestartSec = "10s"; + ReadWritePaths = cfg.nbxplorer.dataDir; + MemoryDenyWriteExecute = "false"; + } // (if cfg.nbxplorer.enforceTor + then nix-bitcoin-services.allowTor + else nix-bitcoin-services.allowAnyIP + ); + }; + + systemd.services.btcpayserver = let + configFile = builtins.toFile "config" ('' + network=mainnet + socksendpoint=${cfg.tor.client.socksListenAddress} + btcexplorerurl=http://${cfg.nbxplorer.bind}:24444/ + btcexplorercookiefile=${cfg.nbxplorer.dataDir}/Main/.cookie + bind=${cfg.btcpayserver.bind} + '' + optionalString (cfg.btcpayserver.lightningBackend == "clightning") '' + btclightning=type=clightning;server=unix:///${cfg.clightning.dataDir}/bitcoin/lightning-rpc + ''); + lndConfig = + "btclightning=type=lnd-rest;" + + "server=https://${toString cfg.lnd.listen}:${toString cfg.lnd.restPort}/;" + + "macaroonfilepath=/run/lnd/btcpayserver.macaroon;" + + "certthumbprint="; + in let self = { + wantedBy = [ "multi-user.target" ]; + requires = [ "nbxplorer.service" ] + ++ optional (cfg.btcpayserver.lightningBackend != null) "${cfg.btcpayserver.lightningBackend}.service"; + after = self.requires; + preStart = '' + install -m 600 ${configFile} ${cfg.btcpayserver.dataDir}/settings.config + ${optionalString (cfg.btcpayserver.lightningBackend == "lnd") '' + { + echo -n "${lndConfig}"; + ${pkgs.openssl}/bin/openssl x509 -noout -fingerprint -sha256 -in ${config.nix-bitcoin.secretsDir}/lnd-cert \ + | sed -e 's/.*=//;s/://g'; + } >> ${cfg.btcpayserver.dataDir}/settings.config + ''} + ''; + serviceConfig = nix-bitcoin-services.defaultHardening // { + ExecStart = '' + ${cfg.btcpayserver.package}/bin/btcpayserver --conf=${cfg.btcpayserver.dataDir}/settings.config \ + --datadir=${cfg.btcpayserver.dataDir} + ''; + User = cfg.btcpayserver.user; + Restart = "on-failure"; + RestartSec = "10s"; + ReadWritePaths = cfg.btcpayserver.dataDir; + MemoryDenyWriteExecute = "false"; + } // (if cfg.btcpayserver.enforceTor + then nix-bitcoin-services.allowTor + else nix-bitcoin-services.allowAnyIP + ); + }; in self; + + services.lnd.macaroons.btcpayserver = mkIf (cfg.btcpayserver.lightningBackend == "lnd") { + inherit (cfg.btcpayserver) user; + permissions = ''{"entity":"info","action":"read"},{"entity":"onchain","action":"read"},{"entity":"offchain","action":"read"},{"entity":"address","action":"read"},{"entity":"message","action":"read"},{"entity":"peers","action":"read"},{"entity":"signer","action":"read"},{"entity":"invoices","action":"read"},{"entity":"invoices","action":"write"},{"entity":"address","action":"write"}''; + }; + + users.users.${cfg.nbxplorer.user} = { + description = "nbxplorer user"; + group = cfg.nbxplorer.group; + extraGroups = [ "bitcoinrpc" ]; + home = cfg.nbxplorer.dataDir; + }; + users.groups.${cfg.nbxplorer.group} = {}; + users.users.${cfg.btcpayserver.user} = { + description = "btcpayserver user"; + group = cfg.btcpayserver.group; + extraGroups = [ "nbxplorer" ] + ++ optional (cfg.btcpayserver.lightningBackend == "clightning") cfg.clightning.user; + home = cfg.btcpayserver.dataDir; + }; + users.groups.${cfg.btcpayserver.group} = {}; + + services.bitcoind.rpc.users.btcpayserver = { + passwordHMACFromFile = true; + rpcwhitelist = cfg.bitcoind.rpc.users.public.rpcwhitelist ++ [ + "setban" + "generatetoaddress" + "getpeerinfo" + ]; + }; + nix-bitcoin.secrets.bitcoin-rpcpassword-btcpayserver = { + user = "bitcoin"; + group = "nbxplorer"; + }; + nix-bitcoin.secrets.bitcoin-HMAC-btcpayserver.user = "bitcoin"; + }; +} diff --git a/modules/modules.nix b/modules/modules.nix index 1b6e253..e86772d 100644 --- a/modules/modules.nix +++ b/modules/modules.nix @@ -18,6 +18,7 @@ ./netns-isolation.nix ./security.nix ./backups.nix + ./btcpayserver.nix ]; disabledModules = [ "services/networking/bitcoind.nix" ]; diff --git a/modules/netns-isolation.nix b/modules/netns-isolation.nix index 4ca3544..7b05139 100644 --- a/modules/netns-isolation.nix +++ b/modules/netns-isolation.nix @@ -232,6 +232,16 @@ in { id = 22; connections = [ "lnd" ]; }; + nbxplorer = { + id = 23; + connections = [ "bitcoind" ]; + }; + btcpayserver = { + id = 24; + connections = [ "nbxplorer" ] + ++ optional (config.services.btcpayserver.lightningBackend == "lnd") "lnd"; + # communicates with clightning over rpc socket + }; }; services.bitcoind = { @@ -301,6 +311,9 @@ in { }; services.lightning-loop.cliExec = mkCliExec "lightning-loop"; + + services.nbxplorer.bind = netns.nbxplorer.address; + services.btcpayserver.bind = netns.btcpayserver.address; } ]); } diff --git a/modules/presets/secure-node.nix b/modules/presets/secure-node.nix index 1447532..ebb5ab6 100644 --- a/modules/presets/secure-node.nix +++ b/modules/presets/secure-node.nix @@ -128,6 +128,12 @@ in { port = cfg.electrs.port; toHost = cfg.electrs.address; }); + # btcpayserver + # disable tor enforcement until btcpayserver can fetch rates over Tor + services.btcpayserver.enforceTor = false; + services.nbxplorer.enforceTor = true; + services.tor.hiddenServices.btcpayserver = mkIf cfg.btcpayserver.enable (mkHiddenService { port = 80; toPort = 23000; toHost = cfg.btcpayserver.bind; }); + services.spark-wallet = { onion-service = true; enforceTor = true; diff --git a/pkgs/generate-secrets/generate-secrets.sh b/pkgs/generate-secrets/generate-secrets.sh index 5a60cbe..87bdaa4 100755 --- a/pkgs/generate-secrets/generate-secrets.sh +++ b/pkgs/generate-secrets/generate-secrets.sh @@ -11,6 +11,7 @@ makeHMAC() { } makePasswordSecret bitcoin-rpcpassword-privileged +makePasswordSecret bitcoin-rpcpassword-btcpayserver makePasswordSecret bitcoin-rpcpassword-public makePasswordSecret lnd-wallet-password makePasswordSecret liquid-rpcpassword @@ -20,6 +21,7 @@ makePasswordSecret backup-encryption-password [[ -e bitcoin-HMAC-privileged ]] || makeHMAC privileged [[ -e bitcoin-HMAC-public ]] || makeHMAC public +[[ -e bitcoin-HMAC-btcpayserver ]] || makeHMAC btcpayserver [[ -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