diff --git a/README.md b/README.md index ea801e4..c6e658b 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ running nix-bitcoin does not require any previous experience with the Nix ecosys Examples --- -See the [examples directory](examples/README.md). +See [here for examples](examples/README.md). Features --- @@ -76,7 +76,7 @@ NixOS modules * Helper * [netns-isolation](modules/netns-isolation.nix): isolates applications on the network-level via network namespaces * [nodeinfo](modules/nodeinfo.nix): script which prints info about the node's services - * [backups](modules/backups.nix): daily duplicity backups of all your node's important files + * [backups](modules/backups.nix): duplicity backups of all your node's important files * [operator](modules/operator.nix): adds non-root user `operator` who has access to client tools (e.g. `bitcoin-cli`, `lightning-cli`) Security diff --git a/ci/build-to-cachix.sh b/ci/build-to-cachix.sh index 73a8260..0fbbd5a 100755 --- a/ci/build-to-cachix.sh +++ b/ci/build-to-cachix.sh @@ -11,18 +11,8 @@ cachixCache=nix-bitcoin trap 'echo Error at line $LINENO' ERR -atExit() { - rm -rf $tmpDir - if [[ -v cachixPid ]]; then stopCachix; fi -} tmpDir=$(mktemp -d -p /tmp) -trap atExit EXIT - -stopCachix() { - kill $cachixPid 2>/dev/null || true - # Wait for cachix to finish - tail --pid=$cachixPid -f /dev/null -} +trap "rm -rf $tmpDir" EXIT ## Instantiate @@ -43,14 +33,14 @@ fi if [[ $CACHIX_SIGNING_KEY ]]; then # Speed up task by uploading store paths as soon as they are created - cachix push $cachixCache --watch-store & - cachixPid=$! + buildCmd="cachix watch-exec $cachixCache nix-build --" +else + buildCmd=nix-build fi -nix-build --out-link $tmpDir/result $tmpDir/drv >/dev/null +$buildCmd --out-link $tmpDir/result $tmpDir/drv >/dev/null if [[ $CACHIX_SIGNING_KEY ]]; then - stopCachix cachix push $cachixCache $outPath fi diff --git a/examples/deploy-container.sh b/examples/deploy-container.sh index 965c2ec..53b3843 100755 --- a/examples/deploy-container.sh +++ b/examples/deploy-container.sh @@ -5,8 +5,8 @@ set -euo pipefail # Running this script leaves no traces on your host system. # This demo is a template for your own experiments. -# Feel free to modify or to run nix-shell and execute individual statements of this -# script in the interactive shell. +# Run with option `--interactive` or `-i` to start a shell for interacting with +# the node. if [[ ! -v IN_NIX_SHELL ]]; then echo "Running script in nix shell env..." diff --git a/examples/deploy-nixops.sh b/examples/deploy-nixops.sh index 568bbcd..8e8307b 100755 --- a/examples/deploy-nixops.sh +++ b/examples/deploy-nixops.sh @@ -5,8 +5,8 @@ set -euo pipefail # Running this script leaves no traces on your host system. # This demo is a template for your own experiments. -# Feel free to modify or to run nix-shell and execute individual statements of this -# script in the interactive shell. +# Run with option `--interactive` or `-i` to start a shell for interacting with +# the node. if [[ ! -v IN_NIX_SHELL ]]; then echo "Running script in nix shell env..." diff --git a/examples/deploy-qemu-vm.sh b/examples/deploy-qemu-vm.sh index 331451f..c83a96e 100755 --- a/examples/deploy-qemu-vm.sh +++ b/examples/deploy-qemu-vm.sh @@ -5,8 +5,8 @@ set -euo pipefail # Running this script leaves no traces on your host system. # This demo is a template for your own experiments. -# Feel free to modify or to run nix-shell and execute individual statements of this -# script in the interactive shell. +# Run with option `--interactive` or `-i` to start a shell for interacting with +# the node. # MAKE SURE TO REPLACE the SSH identity file if you use this script for # anything serious. diff --git a/modules/backups.nix b/modules/backups.nix index 322a84c..2669620 100644 --- a/modules/backups.nix +++ b/modules/backups.nix @@ -4,13 +4,15 @@ with lib; let cfg = config.services.backups; + secretsDir = config.nix-bitcoin.secretsDir; + filelist = pkgs.writeText "filelist.txt" '' ${optionalString (!cfg.with-bulk-data) "- ${config.services.bitcoind.dataDir}/blocks"} ${optionalString (!cfg.with-bulk-data) "- ${config.services.bitcoind.dataDir}/chainstate"} ${config.services.bitcoind.dataDir} ${config.services.clightning.dataDir} ${config.services.lnd.dataDir} - /secrets/lnd-seed-mnemonic + ${secretsDir}/lnd-seed-mnemonic ${optionalString (!cfg.with-bulk-data) "- ${config.services.liquidd.dataDir}/*/blocks"} ${optionalString (!cfg.with-bulk-data) "- ${config.services.liquidd.dataDir}/*/chainstate"} ${config.services.liquidd.dataDir} @@ -18,7 +20,7 @@ let ${config.services.nbxplorer.dataDir} ${config.services.btcpayserver.dataDir} ${config.services.joinmarket.dataDir} - /secrets/jm-wallet-seed + ${secretsDir}/jm-wallet-seed ${config.services.postgresqlBackup.location}/btcpaydb.sql.gz /var/lib/tor # Extra files @@ -27,7 +29,6 @@ let # Exclude all unspecified files and directories - / ''; - in { options.services.backups = { enable = mkEnableOption "Backups service"; @@ -72,7 +73,7 @@ in { "--include-filelist" "${filelist}" "--full-if-older-than" "1M" ]; - targetUrl = "${cfg.destination}"; + targetUrl = cfg.destination; frequency = cfg.frequency; secretFile = "${config.nix-bitcoin.secretsDir}/backup-encryption-env"; }; diff --git a/modules/bitcoind-rpc-public-whitelist.nix b/modules/bitcoind-rpc-public-whitelist.nix index 966111e..70ce406 100644 --- a/modules/bitcoind-rpc-public-whitelist.nix +++ b/modules/bitcoind-rpc-public-whitelist.nix @@ -58,5 +58,5 @@ "validateaddress" "verifymessage" # Zmq - "getzmqnotifications" + "getzmqnotifications" ] diff --git a/modules/bitcoind.nix b/modules/bitcoind.nix index af84cc8..fe8e9d3 100644 --- a/modules/bitcoind.nix +++ b/modules/bitcoind.nix @@ -7,7 +7,7 @@ let nbLib = config.nix-bitcoin.lib; secretsDir = config.nix-bitcoin.secretsDir; - configFile = pkgs.writeText "bitcoin.conf" '' + configFile = builtins.toFile "bitcoin.conf" '' # We're already logging via journald nodebuglogfile=1 @@ -90,7 +90,7 @@ in { par=16 logips=1 ''; - description = "Additional configurations to be appended to bitcoin.conf."; + description = "Extra lines appended to bitcoin.conf."; }; dataDir = mkOption { type = types.path; @@ -138,7 +138,7 @@ in { alice.passwordHMAC = "f7efda5c189b999524f151318c0c86$d5b51b3beffbc02b724e5d095828e0bc8b2456e9ac8757ae3211a5d9b16a22ae"; bob.passwordHMAC = "b2dd077cb54591a2f3139e69a897ac$4e71f08d48b4347cf8eff3815c0e25ae2e9a4340474079f55705f40574f4ec99"; }; - type = with types; loaOf (submodule ({ name, ... }: { + type = with types; attrsOf (submodule ({ name, ... }: { options = { name = mkOption { type = types.str; @@ -197,9 +197,7 @@ in { listen = mkOption { type = types.bool; default = false; - description = '' - If enabled, the bitcoin service will listen. - ''; + description = "Accept incoming connections."; }; dataDirReadableByGroup = mkOption { type = types.bool; @@ -228,21 +226,15 @@ in { type = types.nullOr (types.ints.between 4 16384); default = null; example = 4000; - description = "Override the default database cache size in megabytes."; + description = "Override the default database cache size in MiB."; }; prune = mkOption { type = types.ints.unsigned; default = 0; example = 10000; description = '' - Reduce storage requirements by enabling pruning (deleting) of old - blocks. This allows the pruneblockchain RPC to be called to delete - specific blocks, and enables automatic pruning of old blocks if a - target size in MiB is provided. This mode is incompatible with -txindex - and -rescan. Warning: Reverting this setting requires re-downloading - the entire blockchain. ("disable" = disable pruning blocks, "manual" - = allow manual pruning via RPC, >=550 = automatically prune block files - to stay under the specified target size in MiB) + Automatically prune block files to stay under the specified target size in MiB. + Value 0 disables pruning. ''; }; zmqpubrawblock = mkOption { @@ -281,7 +273,7 @@ in { type = types.nullOr types.str; default = null; example = "bech32"; - description = "What type of addresses to use"; + description = "The type of addresses to use"; }; cli = mkOption { readOnly = true; @@ -320,7 +312,6 @@ in { ]; systemd.services.bitcoind = { - description = "Bitcoin daemon"; requires = [ "nix-bitcoin-secrets.target" ]; after = [ "network.target" "nix-bitcoin-secrets.target" ]; wantedBy = [ "multi-user.target" ]; @@ -334,10 +325,10 @@ in { in '' ${optionalString cfg.dataDirReadableByGroup "chmod -R g+rX '${cfg.dataDir}/blocks'"} cfg=$( - cat ${configFile}; + cat ${configFile} ${extraRpcauth} ${/* Enable bitcoin-cli for group 'bitcoin' */ ""} - printf "rpcuser=${cfg.rpc.users.privileged.name}\nrpcpassword="; cat "${secretsDir}/bitcoin-rpcpassword-privileged"; + printf "rpcuser=${cfg.rpc.users.privileged.name}\nrpcpassword="; cat "${secretsDir}/bitcoin-rpcpassword-privileged" echo ${optionalString (cfg.getPublicAddressCmd != "") '' echo "externalip=$(${cfg.getPublicAddressCmd})" @@ -351,13 +342,13 @@ in { serviceConfig = nbLib.defaultHardening // { Type = "notify"; NotifyAccess = "all"; - User = "${cfg.user}"; - Group = "${cfg.group}"; + User = cfg.user; + Group = cfg.group; TimeoutStartSec = 300; ExecStart = "${cfg.package}/bin/bitcoind -datadir='${cfg.dataDir}'"; Restart = "on-failure"; UMask = mkIf cfg.dataDirReadableByGroup "0027"; - ReadWritePaths = "${cfg.dataDir}"; + ReadWritePaths = cfg.dataDir; } // (if cfg.enforceTor then nbLib.allowTor else nbLib.allowAnyIP) @@ -383,16 +374,13 @@ in { done ''; serviceConfig = nbLib.defaultHardening // { - User = "${cfg.user}"; - Group = "${cfg.group}"; - ReadWritePaths = "${cfg.dataDir}"; + User = cfg.user; + Group = cfg.group; + ReadWritePaths = cfg.dataDir; } // nbLib.allowTor; }; - users.users.${cfg.user} = { - group = cfg.group; - description = "Bitcoin daemon user"; - }; + users.users.${cfg.user}.group = cfg.group; users.groups.${cfg.group} = {}; users.groups.bitcoinrpc = {}; nix-bitcoin.operator.groups = [ cfg.group ]; diff --git a/modules/btcpayserver.nix b/modules/btcpayserver.nix index b663132..a65b8fa 100644 --- a/modules/btcpayserver.nix +++ b/modules/btcpayserver.nix @@ -99,20 +99,34 @@ in { services.clightning.enable = mkIf (cfg.btcpayserver.lightningBackend == "clightning") true; services.lnd.enable = mkIf (cfg.btcpayserver.lightningBackend == "lnd") true; - 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} - -" - ]; + services.bitcoind.rpc.users.btcpayserver = { + passwordHMACFromFile = true; + rpcwhitelist = cfg.bitcoind.rpc.users.public.rpcwhitelist ++ [ + "setban" + "generatetoaddress" + "getpeerinfo" + ]; + }; + + 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"}''; + }; services.postgresql = { enable = true; ensureDatabases = [ "btcpaydb" ]; ensureUsers = [{ - name = "${cfg.btcpayserver.user}"; + name = cfg.btcpayserver.user; ensurePermissions."DATABASE btcpaydb" = "ALL PRIVILEGES"; }]; }; + 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=${config.services.bitcoind.network} @@ -123,12 +137,11 @@ in { port=${toString cfg.nbxplorer.port} ''; in { - description = "Run nbxplorer"; wantedBy = [ "multi-user.target" ]; requires = [ "bitcoind.service" ]; after = [ "bitcoind.service" ]; preStart = '' - install -m 600 ${configFile} ${cfg.nbxplorer.dataDir}/settings.config + install -m 600 ${configFile} '${cfg.nbxplorer.dataDir}/settings.config' echo "btcrpcpassword=$(cat ${config.nix-bitcoin.secretsDir}/bitcoin-rpcpassword-btcpayserver)" \ >> '${cfg.nbxplorer.dataDir}/settings.config' ''; @@ -151,13 +164,13 @@ in { systemd.services.btcpayserver = let configFile = builtins.toFile "config" ('' network=${config.services.bitcoind.network} - postgres=User ID=${cfg.btcpayserver.user};Host=/run/postgresql;Database=btcpaydb + bind=${cfg.btcpayserver.address} + port=${toString cfg.btcpayserver.port} socksendpoint=${cfg.tor.client.socksListenAddress} btcexplorerurl=http://${cfg.nbxplorer.address}:${toString cfg.nbxplorer.port}/ btcexplorercookiefile=${cfg.nbxplorer.dataDir}/${config.services.bitcoind.makeNetworkName "Main" "RegTest"}/.cookie - bind=${cfg.btcpayserver.address} + postgres=User ID=${cfg.btcpayserver.user};Host=/run/postgresql;Database=btcpaydb ${optionalString (cfg.btcpayserver.rootpath != null) "rootpath=${cfg.btcpayserver.rootpath}"} - port=${toString cfg.btcpayserver.port} '' + optionalString (cfg.btcpayserver.lightningBackend == "clightning") '' btclightning=type=clightning;server=unix:///${cfg.clightning.dataDir}/bitcoin/lightning-rpc ''); @@ -168,17 +181,17 @@ in { "certthumbprint="; in let self = { wantedBy = [ "multi-user.target" ]; - requires = [ "nbxplorer.service" ] + requires = [ "nbxplorer.service" "postgresql.service" ] ++ optional (cfg.btcpayserver.lightningBackend != null) "${cfg.btcpayserver.lightningBackend}.service"; after = self.requires; preStart = '' - install -m 600 ${configFile} ${cfg.btcpayserver.dataDir}/settings.config + 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 + } >> '${cfg.btcpayserver.dataDir}/settings.config' ''} ''; serviceConfig = nbLib.defaultHardening // { @@ -197,20 +210,13 @@ in { ); }; 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; @@ -218,18 +224,12 @@ in { }; 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"; + }; + bitcoin-HMAC-btcpayserver.user = "bitcoin"; }; - nix-bitcoin.secrets.bitcoin-rpcpassword-btcpayserver = { - user = "bitcoin"; - group = "nbxplorer"; - }; - nix-bitcoin.secrets.bitcoin-HMAC-btcpayserver.user = "bitcoin"; }; } diff --git a/modules/clightning.nix b/modules/clightning.nix index d8838ec..69c53aa 100644 --- a/modules/clightning.nix +++ b/modules/clightning.nix @@ -11,7 +11,7 @@ let network=${network} bitcoin-datadir=${config.services.bitcoind.dataDir} ${optionalString (cfg.proxy != null) "proxy=${cfg.proxy}"} - always-use-proxy=${if cfg.always-use-proxy then "true" else "false"} + always-use-proxy=${boolToString cfg.always-use-proxy} bind-addr=${cfg.address}:${toString cfg.port} bitcoin-rpcconnect=${config.services.bitcoind.rpc.address} bitcoin-rpcport=${toString config.services.bitcoind.rpc.port} @@ -21,13 +21,7 @@ let ''; in { options.services.clightning = { - enable = mkOption { - type = types.bool; - default = false; - description = '' - If enabled, the clightning service will be installed. - ''; - }; + enable = mkEnableOption "clightning"; address = mkOption { type = types.str; default = "127.0.0.1"; @@ -41,13 +35,17 @@ in { proxy = mkOption { type = types.nullOr types.str; default = if cfg.enforceTor then config.services.tor.client.socksListenAddress else null; - description = "Set a socks proxy to use to connect to Tor nodes (or for all connections if *always-use-proxy* is set)"; + description = '' + Socks proxy for connecting to Tor nodes (or for all connections if option always-use-proxy is set). + ''; }; always-use-proxy = mkOption { type = types.bool; default = cfg.enforceTor; description = '' - Always use the *proxy*, even to connect to normal IP addresses (you can still connect to Unix domain sockets manually). This also disables all DNS lookups, to avoid leaking information. + Always use the proxy, even to connect to normal IP addresses. + You can still connect to Unix domain sockets manually. + This also disables all DNS lookups, to avoid leaking address information. ''; }; dataDir = mkOption { @@ -63,7 +61,7 @@ in { extraConfig = mkOption { type = types.lines; default = ""; - description = "Additional lines appended to the config file."; + description = "Extra lines appended to the configuration file."; }; user = mkOption { type = types.str; @@ -77,8 +75,7 @@ in { }; cli = mkOption { readOnly = true; - default = pkgs.writeScriptBin "lightning-cli" - '' + default = pkgs.writeScriptBin "lightning-cli" '' ${nbPkgs.clightning}/bin/lightning-cli --lightning-dir='${cfg.dataDir}' "$@" ''; description = "Binary to connect with the clightning instance."; @@ -103,44 +100,34 @@ in { }; environment.systemPackages = [ nbPkgs.clightning (hiPrio cfg.cli) ]; - users.users.${cfg.user} = { - description = "clightning User"; - group = cfg.group; - extraGroups = [ "bitcoinrpc" ]; - }; - users.groups.${cfg.group} = {}; - nix-bitcoin.operator.groups = [ cfg.group ]; systemd.tmpfiles.rules = [ "d '${cfg.dataDir}' 0770 ${cfg.user} ${cfg.group} - -" ]; systemd.services.clightning = { - description = "Run clightningd"; path = [ nbPkgs.bitcoind ]; wantedBy = [ "multi-user.target" ]; requires = [ "bitcoind.service" ]; after = [ "bitcoind.service" ]; preStart = '' - cp ${configFile} ${cfg.dataDir}/config chown -R '${cfg.user}:${cfg.group}' '${cfg.dataDir}' # The RPC socket has to be removed otherwise we might have stale sockets rm -f ${cfg.networkDir}/lightning-rpc - chmod 640 ${cfg.dataDir}/config + install -m 640 ${configFile} '${cfg.dataDir}/config' { echo "bitcoin-rpcpassword=$(cat ${config.nix-bitcoin.secretsDir}/bitcoin-rpcpassword-public)" ${optionalString (cfg.getPublicAddressCmd != "") '' echo "announce-addr=$(${cfg.getPublicAddressCmd})" ''} } >> '${cfg.dataDir}/config' - - ''; + ''; serviceConfig = nbLib.defaultHardening // { ExecStart = "${nbPkgs.clightning}/bin/lightningd --lightning-dir=${cfg.dataDir}"; - User = "${cfg.user}"; + User = cfg.user; Restart = "on-failure"; RestartSec = "10s"; - ReadWritePaths = "${cfg.dataDir}"; + ReadWritePaths = cfg.dataDir; } // (if cfg.enforceTor then nbLib.allowTor else nbLib.allowAnyIP @@ -154,5 +141,12 @@ in { chmod g+x ${cfg.networkDir} ''; }; + + users.users.${cfg.user} = { + group = cfg.group; + extraGroups = [ "bitcoinrpc" ]; + }; + users.groups.${cfg.group} = {}; + nix-bitcoin.operator.groups = [ cfg.group ]; }; } diff --git a/modules/electrs.nix b/modules/electrs.nix index a2c8102..f11dc76 100644 --- a/modules/electrs.nix +++ b/modules/electrs.nix @@ -68,7 +68,6 @@ in { ]; systemd.services.electrs = { - description = "Electrs Electrum Server"; wantedBy = [ "multi-user.target" ]; requires = [ "bitcoind.service" ]; after = [ "bitcoind.service" ]; @@ -110,7 +109,6 @@ in { }; users.users.${cfg.user} = { - description = "electrs User"; group = cfg.group; extraGroups = [ "bitcoinrpc" ] ++ optionals cfg.high-memory [ "bitcoin" ]; }; diff --git a/modules/hardware-wallets.nix b/modules/hardware-wallets.nix index 1d1c2b1..b2e9352 100644 --- a/modules/hardware-wallets.nix +++ b/modules/hardware-wallets.nix @@ -47,11 +47,12 @@ in { # Provides lsusb for debugging pkgs.usbutils ]; - users.groups."${cfg.group}" = {}; + + users.groups.${cfg.group} = {}; nix-bitcoin.operator.groups = [ cfg.group ]; }) - (mkIf cfg.ledger { + (mkIf cfg.ledger { # Ledger Nano S according to https://github.com/LedgerHQ/udev-rules/blob/master/add_udev_rules.sh # Don't use rules from nixpkgs because we want to use our own group. services.udev.packages = lib.singleton (pkgs.writeTextFile { diff --git a/modules/joinmarket-ob-watcher.nix b/modules/joinmarket-ob-watcher.nix index e8b2a30..62f670b 100644 --- a/modules/joinmarket-ob-watcher.nix +++ b/modules/joinmarket-ob-watcher.nix @@ -46,16 +46,6 @@ in { default = "/var/lib/joinmarket-ob-watcher"; description = "The data directory for JoinMarket orderbook watcher."; }; - user = mkOption { - type = types.str; - default = "joinmarket-ob-watcher"; - description = "The user as which to run JoinMarket orderbook watcher."; - }; - group = mkOption { - type = types.str; - default = cfg.user; - description = "The group as which to run JoinMarket orderbook watcher."; - }; # This option is only used by netns-isolation enforceTor = mkOption { readOnly = true; @@ -64,6 +54,7 @@ in { }; config = mkIf cfg.enable { + # Joinmarket is Tor-only services.tor = { enable = true; client.enable = true; @@ -73,27 +64,23 @@ in { wantedBy = [ "multi-user.target" ]; requires = [ "tor.service" ]; after = [ "tor.service" ]; + # The service writes to HOME/.config/matplotlib + environment.HOME = cfg.dataDir; preStart = '' ln -snf ${configFile} ${cfg.dataDir}/joinmarket.cfg ''; serviceConfig = nbLib.defaultHardening // rec { + DynamicUser = true; StateDirectory = "joinmarket-ob-watcher"; StateDirectoryMode = "0770"; - WorkingDirectory = "${cfg.dataDir}"; # The service creates dir 'logs' in the working dir + WorkingDirectory = cfg.dataDir; # The service creates dir 'logs' in the working dir ExecStart = '' ${nbPkgs.joinmarket}/bin/ob-watcher --datadir=${cfg.dataDir} \ --host=${cfg.address} --port=${toString cfg.port} ''; - User = cfg.user; Restart = "on-failure"; RestartSec = "10s"; } // nbLib.allowTor; }; - - users.users.${cfg.user} = { - group = cfg.group; - home = cfg.dataDir; # The service writes to HOME/.config/matplotlib - }; - users.groups.${cfg.group} = {}; }; } diff --git a/modules/joinmarket.nix b/modules/joinmarket.nix index ccdc8cb..f3cbf5d 100644 --- a/modules/joinmarket.nix +++ b/modules/joinmarket.nix @@ -141,75 +141,79 @@ in { }; config = mkIf cfg.enable (mkMerge [{ - services.bitcoind.enable = true; + services.bitcoind = { + enable = true; + disablewallet = false; + }; + + # Joinmarket is Tor-only + services.tor = { + enable = true; + client.enable = true; + # Needed for payjoin onion service creation + controlSocket.enable = true; + }; environment.systemPackages = [ (hiPrio cfg.cli) ]; - users.users.${cfg.user} = { - description = "joinmarket User"; - group = "${cfg.group}"; - home = cfg.dataDir; - extraGroups = [ "tor" ]; - }; - users.groups.${cfg.group} = {}; - nix-bitcoin.operator = { - groups = [ cfg.group ]; - sudoUsers = [ cfg.group ]; - }; systemd.tmpfiles.rules = [ "d '${cfg.dataDir}' 0770 ${cfg.user} ${cfg.group} - -" ]; - services.bitcoind.disablewallet = false; - - # Joinmarket is TOR-only - services.tor = { - enable = true; - client.enable = true; - controlSocket.enable = true; - }; - systemd.services.joinmarket = { - description = "JoinMarket Daemon"; wantedBy = [ "multi-user.target" ]; requires = [ "bitcoind.service" ]; after = [ "bitcoind.service" ]; path = [ pkgs.sudo ]; serviceConfig = nbLib.defaultHardening // { - ExecStartPre = nbLib.privileged '' + ExecStartPre = nbLib.privileged "joinmarket-create-config" '' install -o '${cfg.user}' -g '${cfg.group}' -m 640 ${configFile} ${cfg.dataDir}/joinmarket.cfg sed -i \ "s|@@RPC_PASSWORD@@|rpc_password = $(cat ${secretsDir}/bitcoin-rpcpassword-privileged)|" \ '${cfg.dataDir}/joinmarket.cfg' ''; # Generating wallets (jmclient/wallet.py) is only supported for mainnet or testnet - ExecStartPost = mkIf (bitcoind.network == "mainnet") (nbLib.privileged '' - walletname=wallet.jmdat - pw=$(cat "${secretsDir}"/jm-wallet-password) - mnemonic=${secretsDir}/jm-wallet-seed - if [[ ! -f ${cfg.dataDir}/wallets/$walletname ]]; then - echo Create joinmarket wallet - # Use bash variables so commands don't proceed on previous failures - # (like with pipes) - cd ${cfg.dataDir} && \ - out=$(sudo -u ${cfg.user} \ - ${nbPkgs.joinmarket}/bin/jm-genwallet \ - --datadir=${cfg.dataDir} $walletname $pw) - recoveryseed=$(echo "$out" | grep 'recovery_seed') - echo "$recoveryseed" | cut -d ':' -f2 > $mnemonic - fi - ''); + ExecStartPost = mkIf (bitcoind.network == "mainnet") + (nbLib.privileged "joinmarket-create-wallet" '' + walletname=wallet.jmdat + wallet=${cfg.dataDir}/wallets/$walletname + if [[ ! -f $wallet ]]; then + echo "Create wallet" + pw=$(cat "${secretsDir}"/jm-wallet-password) + cd ${cfg.dataDir} + if ! sudo -u ${cfg.user} ${nbPkgs.joinmarket}/bin/jm-genwallet --datadir=${cfg.dataDir} $walletname $pw \ + | grep 'recovery_seed' \ + | cut -d ':' -f2 \ + | (umask u=r,go=; cat > "${secretsDir}/jm-wallet-seed"); then + echo "wallet creation failed" + rm -f "$wallet" "${secretsDir}/jm-wallet-seed" + exit 1 + fi + fi + ''); ExecStart = "${nbPkgs.joinmarket}/bin/joinmarketd"; - WorkingDirectory = "${cfg.dataDir}"; # The service creates 'commitmentlist' in the working dir - User = "${cfg.user}"; + WorkingDirectory = cfg.dataDir; # The service creates 'commitmentlist' in the working dir + User = cfg.user; Restart = "on-failure"; RestartSec = "10s"; - ReadWritePaths = "${cfg.dataDir}"; + ReadWritePaths = cfg.dataDir; } // nbLib.allowTor; }; + users.users.${cfg.user} = { + group = cfg.group; + home = cfg.dataDir; + # Allow access to the tor control socket, needed for payjoin onion service creation + extraGroups = [ "tor" ]; + }; + users.groups.${cfg.group} = {}; + nix-bitcoin.operator = { + groups = [ cfg.group ]; + sudoUsers = [ cfg.group ]; + }; + nix-bitcoin.secrets.jm-wallet-password.user = cfg.user; } @@ -227,7 +231,6 @@ in { chmod +x $out ''; in { - description = "CoinJoin maker bot to gain privacy and passively generate income"; wantedBy = [ "joinmarket.service" ]; requires = [ "joinmarket.service" ]; after = [ "joinmarket.service" ]; @@ -242,10 +245,14 @@ in { serviceConfig = nbLib.defaultHardening // rec { RuntimeDirectory = "joinmarket-yieldgenerator"; # Only used to create start script RuntimeDirectoryMode = "700"; - WorkingDirectory = "${cfg.dataDir}"; # The service creates dir 'logs' in the working dir + WorkingDirectory = cfg.dataDir; # The service creates dir 'logs' in the working dir ExecStart = "${pkgs.bash}/bin/bash /run/${RuntimeDirectory}/start"; - User = "${cfg.user}"; - ReadWritePaths = "${cfg.dataDir}"; + # Show "joinmarket-yieldgenerator" instead of "bash" in the journal. + # The parent bash start process has to run alongside the main process + # because it provides the wallet password via stdin to the main process + SyslogIdentifier = "joinmarket-yieldgenerator"; + User = cfg.user; + ReadWritePaths = cfg.dataDir; } // nbLib.allowTor; }; }) diff --git a/modules/lightning-loop.nix b/modules/lightning-loop.nix index fd87b19..3c84713 100644 --- a/modules/lightning-loop.nix +++ b/modules/lightning-loop.nix @@ -101,7 +101,7 @@ in { User = "lnd"; Restart = "on-failure"; RestartSec = "10s"; - ReadWritePaths = "${cfg.dataDir}"; + ReadWritePaths = cfg.dataDir; } // (if cfg.enforceTor then nbLib.allowTor else nbLib.allowAnyIP); diff --git a/modules/liquid.nix b/modules/liquid.nix index 26b9e5a..a5ee67e 100644 --- a/modules/liquid.nix +++ b/modules/liquid.nix @@ -87,9 +87,8 @@ in { par=16 rpcthreads=16 logips=1 - ''; - description = "Additional configurations to be appended to elements.conf."; + description = "Extra lines appended to elements.conf."; }; dataDir = mkOption { type = types.path; @@ -123,7 +122,7 @@ in { alice.passwordHMAC = "f7efda5c189b999524f151318c0c86$d5b51b3beffbc02b724e5d095828e0bc8b2456e9ac8757ae3211a5d9b16a22ae"; bob.passwordHMAC = "b2dd077cb54591a2f3139e69a897ac$4e71f08d48b4347cf8eff3815c0e25ae2e9a4340474079f55705f40574f4ec99"; }; - type = with types; loaOf (submodule rpcUserOpts); + type = with types; attrsOf (submodule rpcUserOpts); description = '' RPC user information for JSON-RPC connections. ''; @@ -221,25 +220,25 @@ in { ]; systemd.services.liquidd = { - description = "Elements daemon providing access to the Liquid sidechain"; requires = [ "bitcoind.service" ]; after = [ "bitcoind.service" ]; wantedBy = [ "multi-user.target" ]; preStart = '' - cp '${configFile}' '${cfg.dataDir}/elements.conf' - chmod 640 '${cfg.dataDir}/elements.conf' chown -R '${cfg.user}:${cfg.group}' '${cfg.dataDir}' - echo "rpcpassword=$(cat ${secretsDir}/liquid-rpcpassword)" >> '${cfg.dataDir}/elements.conf' - echo "mainchainrpcpassword=$(cat ${secretsDir}/bitcoin-rpcpassword-public)" >> '${cfg.dataDir}/elements.conf' + install -m 640 ${configFile} '${cfg.dataDir}/elements.conf' + { + echo "rpcpassword=$(cat ${secretsDir}/liquid-rpcpassword)" + echo "mainchainrpcpassword=$(cat ${secretsDir}/bitcoin-rpcpassword-public)" + } >> '${cfg.dataDir}/elements.conf' ''; serviceConfig = nbLib.defaultHardening // { Type = "simple"; - User = "${cfg.user}"; - Group = "${cfg.group}"; + User = cfg.user; + Group = cfg.group; ExecStart = "${nbPkgs.elementsd}/bin/elementsd ${cmdlineOptions}"; - PIDFile = "${pidFile}"; + PIDFile = pidFile; Restart = "on-failure"; - ReadWritePaths = "${cfg.dataDir}"; + ReadWritePaths = cfg.dataDir; } // (if cfg.enforceTor then nbLib.allowTor else nbLib.allowAnyIP @@ -249,7 +248,6 @@ in { users.users.${cfg.user} = { group = cfg.group; extraGroups = [ "bitcoinrpc" ]; - description = "Liquid sidechain user"; }; users.groups.${cfg.group} = {}; nix-bitcoin.operator.groups = [ cfg.group ]; diff --git a/modules/lnd.nix b/modules/lnd.nix index 4899dac..930d7d4 100644 --- a/modules/lnd.nix +++ b/modules/lnd.nix @@ -37,13 +37,7 @@ let in { options.services.lnd = { - enable = mkOption { - type = types.bool; - default = false; - description = '' - If enabled, the LND service will be installed. - ''; - }; + enable = mkEnableOption "Lightning Network Daemon"; dataDir = mkOption { type = types.path; default = "/var/lib/lnd"; @@ -89,7 +83,7 @@ in { tor-socks = mkOption { type = types.nullOr types.str; default = if cfg.enforceTor then config.services.tor.client.socksListenAddress else null; - description = "Set a socks proxy to use to connect to Tor nodes"; + description = "Socks proxy for connecting to Tor nodes"; }; macaroons = mkOption { default = {}; @@ -118,7 +112,7 @@ in { example = '' autopilot.active=1 ''; - description = "Additional configurations to be appended to lnd.conf."; + description = "Extra lines appended to lnd.conf."; }; package = mkOption { type = types.package; @@ -156,9 +150,13 @@ in { services.bitcoind = { enable = true; + # Increase rpc thread count due to reports that lightning implementations fail # under high bitcoind rpc load rpc.threads = 16; + + zmqpubrawblock = "tcp://${bitcoindRpcAddress}:28332"; + zmqpubrawtx = "tcp://${bitcoindRpcAddress}:28333"; }; environment.systemPackages = [ cfg.package (hiPrio cfg.cli) ]; @@ -167,13 +165,7 @@ in { "d '${cfg.dataDir}' 0770 lnd lnd - -" ]; - services.bitcoind = { - zmqpubrawblock = "tcp://${bitcoindRpcAddress}:28332"; - zmqpubrawtx = "tcp://${bitcoindRpcAddress}:28333"; - }; - systemd.services.lnd = { - description = "Run LND"; wantedBy = [ "multi-user.target" ]; requires = [ "bitcoind.service" ]; after = [ "bitcoind.service" ]; @@ -193,12 +185,12 @@ in { User = "lnd"; Restart = "on-failure"; RestartSec = "10s"; - ReadWritePaths = "${cfg.dataDir}"; + ReadWritePaths = cfg.dataDir; ExecStartPost = let restUrl = "https://${cfg.restAddress}:${toString cfg.restPort}/v1"; in [ # Run fully privileged for secrets dir write access - "+${nbLib.script '' + (nbLib.privileged "lnd-create-mnemonic" '' attempts=250 while ! { exec 3>/dev/tcp/${cfg.restAddress}/${toString cfg.restPort} && exec 3>&-; } &>/dev/null; do ((attempts-- == 0)) && { echo "lnd REST service unreachable"; exit 1; } @@ -214,8 +206,8 @@ in { -X GET ${restUrl}/genseed | ${pkgs.jq}/bin/jq -c '.cipher_seed_mnemonic' > "$mnemonic" fi chown lnd: "$mnemonic" - ''}" - "${nbLib.script '' + '') + (nbLib.script "lnd-create-wallet" '' if [[ ! -f ${networkDir}/wallet.db ]]; then echo Create lnd wallet @@ -246,9 +238,9 @@ in { sleep 0.1 done - ''}" + '') # Run fully privileged for chown - "+${nbLib.script '' + (nbLib.privileged "lnd-create-macaroons" '' umask ug=r,o= ${lib.concatMapStrings (macaroon: '' echo "Create custom macaroon ${macaroon}" @@ -262,7 +254,7 @@ in { ${pkgs.jq}/bin/jq -c '.macaroon' | ${pkgs.xxd}/bin/xxd -p -r > "$macaroonPath" chown ${cfg.macaroons.${macaroon}.user}: "$macaroonPath" '') (attrNames cfg.macaroons)} - ''}" + '') ]; } // (if cfg.enforceTor then nbLib.allowTor @@ -271,7 +263,6 @@ in { }; users.users.lnd = { - description = "LND User"; group = "lnd"; extraGroups = [ "bitcoinrpc" ]; home = cfg.dataDir; # lnd creates .lnd dir in HOME diff --git a/modules/netns-isolation.nix b/modules/netns-isolation.nix index e4f02f1..30fb57c 100644 --- a/modules/netns-isolation.nix +++ b/modules/netns-isolation.nix @@ -105,7 +105,7 @@ in { source = config.nix-bitcoin.pkgs.netns-exec; capabilities = "cap_sys_admin=ep"; owner = cfg.allowedUser; - permissions = "u+rx,g+rx,o-rwx"; + permissions = "550"; }; systemd.services = { @@ -119,7 +119,7 @@ in { after = [ "network-pre.target" ]; serviceConfig = { Type = "oneshot"; - RemainAfterExit = "yes"; + RemainAfterExit = true; }; script = '' ${ip} link add name nb-br type bridge @@ -182,7 +182,7 @@ in { ''; serviceConfig = { Type = "oneshot"; - RemainAfterExit = "yes"; + RemainAfterExit = true; }; }; }; diff --git a/modules/recurring-donations.nix b/modules/recurring-donations.nix index 84ebea9..f1fa533 100644 --- a/modules/recurring-donations.nix +++ b/modules/recurring-donations.nix @@ -41,13 +41,7 @@ let ''; in { options.services.recurring-donations = { - enable = mkOption { - type = types.bool; - default = false; - description = '' - If enabled, the recurring-donations service will be installed. - ''; - }; + enable = mkEnableOption "recurring-donations"; tallycoin = mkOption { type = types.attrs; default = {}; @@ -81,15 +75,7 @@ in { config = mkIf cfg.enable { services.clightning.enable = true; - users.users.recurring-donations = { - description = "recurring-donations User"; - group = "recurring-donations"; - extraGroups = [ "clightning" ]; - }; - users.groups.recurring-donations = {}; - systemd.services.recurring-donations = { - description = "Run recurring-donations"; requires = [ "clightning.service" ]; after = [ "clightning.service" ]; path = with pkgs; [ nix-bitcoin.clightning curl sudo jq ]; @@ -111,5 +97,11 @@ in { }; wantedBy = [ "multi-user.target" ]; }; + + users.users.recurring-donations = { + group = "recurring-donations"; + extraGroups = [ "clightning" ]; + }; + users.groups.recurring-donations = {}; }; } diff --git a/modules/spark-wallet.nix b/modules/spark-wallet.nix index 3a46f89..419cb3c 100644 --- a/modules/spark-wallet.nix +++ b/modules/spark-wallet.nix @@ -23,13 +23,7 @@ let ''; in { options.services.spark-wallet = { - enable = mkOption { - type = types.bool; - default = false; - description = '' - If enabled, the spark-wallet service will be installed. - ''; - }; + enable = mkEnableOption "spark-wallet"; address = mkOption { type = types.str; default = "localhost"; @@ -61,14 +55,12 @@ in { services.clightning.enable = true; users.users.spark-wallet = { - description = "spark-wallet User"; group = "spark-wallet"; extraGroups = [ "clightning" ]; }; users.groups.spark-wallet = {}; systemd.services.spark-wallet = { - description = "Run spark-wallet"; wantedBy = [ "multi-user.target" ]; requires = [ "clightning.service" ]; after = [ "clightning.service" ]; diff --git a/pkgs/elementsd/default.nix b/pkgs/elementsd/default.nix index 1b9ae92..a46a1a3 100644 --- a/pkgs/elementsd/default.nix +++ b/pkgs/elementsd/default.nix @@ -3,20 +3,20 @@ , withGui }: with stdenv.lib; -stdenv.mkDerivation rec{ - name = "elements" + (toString (optional (!withGui) "d")) + "-" + version; +stdenv.mkDerivation rec { + pname = "elements${optionalString (!withGui) "d"}"; version = "0.18.1.9"; src = fetchurl { - urls = [ - "https://github.com/ElementsProject/elements/archive/elements-${version}.tar.gz" - ]; + url = "https://github.com/ElementsProject/elements/archive/elements-${version}.tar.gz"; + # Use ./get-sha256.sh to fetch latest (verified) sha256 sha256 = "c6f1b040a896a1aaa7340f5cd48e119c84fef88df5d4c17d5ad5c13783f5b6c7"; - }; + }; nativeBuildInputs = [ pkgconfig autoreconfHook ] ++ optional withGui wrapQtAppsHook; + buildInputs = [ openssl db48 boost zlib zeromq miniupnpc protobuf libevent] ++ optionals stdenv.isLinux [ utillinux ] @@ -27,10 +27,10 @@ stdenv.mkDerivation rec{ ] ++ optionals (!doCheck) [ "--disable-tests" "--disable-gui-tests" - ] - ++ optionals withGui [ "--with-gui=qt5" - "--with-qt-bindir=${qtbase.dev}/bin:${qttools.dev}/bin" - ]; + ] ++ optionals withGui [ + "--with-gui=qt5" + "--with-qt-bindir=${qtbase.dev}/bin:${qttools.dev}/bin" + ]; checkInputs = [ rapidcheck python3 ]; diff --git a/pkgs/lib.nix b/pkgs/lib.nix index ab8bfb0..b6919cf 100644 --- a/pkgs/lib.nix +++ b/pkgs/lib.nix @@ -28,8 +28,9 @@ let self = { CapabilityBoundingSet = ""; # @system-service whitelist and docker seccomp blacklist (except for "clone" # which is a core requirement for systemd services) + # @system-service is defined in src/shared/seccomp-util.c (systemd source) SystemCallFilter = [ "@system-service" "~add_key clone3 get_mempolicy kcmp keyctl mbind move_pages name_to_handle_at personality process_vm_readv process_vm_writev request_key set_mempolicy setns unshare userfaultfd" ]; - SystemCallArchitectures= "native"; + SystemCallArchitectures = "native"; }; # nodejs applications apparently rely on memory write execute @@ -51,13 +52,13 @@ let self = { ''; }; - script = src: pkgs.writers.writeBash "script" '' + script = name: src: pkgs.writers.writeBash name '' set -eo pipefail ${src} ''; # Used for ExecStart* - privileged = src: "+${self.script src}"; + privileged = name: src: "+${self.script name src}"; cliExec = mkOption { # Used by netns-isolation to execute the cli in the service's private netns diff --git a/pkgs/python-packages/chromalog/default.nix b/pkgs/python-packages/chromalog/default.nix index 44589a8..0d2e0c8 100644 --- a/pkgs/python-packages/chromalog/default.nix +++ b/pkgs/python-packages/chromalog/default.nix @@ -6,7 +6,7 @@ buildPythonPackage rec { src = fetchFromGitHub { owner = "freelan-developers"; repo = "chromalog"; - rev = "${version}"; + rev = version; sha256 = "0pj4s52rgwlvwkzrj85y92c5r9c84pz8gga45jl5spysrv41y9p0"; }; diff --git a/test/lib/copy-src.sh b/test/lib/copy-src.sh new file mode 100644 index 0000000..71bc3a2 --- /dev/null +++ b/test/lib/copy-src.sh @@ -0,0 +1,18 @@ +# Re-run run-tests.sh in a snapshot copy of the source. +# Maintain /tmp/nix-bitcoin-src as a source cache to minimize copies. + +tmp=$(mktemp -d '/tmp/nix-bitcoin-src.XXXXX') + +# Ignore errors from now on +set +e + +# Move source cache if it exists (atomic) +mv /tmp/nix-bitcoin-src $tmp/src 2>/dev/null + +rsync -a --delete --exclude='.git*' "$scriptDir/../" $tmp/src && \ + echo "Copied src" && \ + _nixBitcoinInCopySrc=1 $tmp/src/test/run-tests.sh "${args[@]}" + +# Set the current src as the source cache (atomic) +mv -T $tmp/src /tmp/nix-bitcoin-src 2>/dev/null +rm -rf $tmp diff --git a/test/run-tests.sh b/test/run-tests.sh index 2593a3f..3f30408 100755 --- a/test/run-tests.sh +++ b/test/run-tests.sh @@ -42,12 +42,20 @@ # For now, creating NixOS containers requires root permissions. # See ./lib/make-container.sh for a complete documentation. # +# Run tests from a snapshot copy of the source files +# ./run-tests.sh --copy-src|-c ... +# +# This allows you to continue editing the nix-bitcoin sources while tests are running +# and reading source files. +# Files are copied to /tmp, a caching scheme helps minimizing copies. +# # To add custom scenarios, set the environment variable `scenarioOverridesFile`. set -eo pipefail scriptDir=$(cd "${BASH_SOURCE[0]%/*}" && pwd) +args=("$@") scenario= outLinkPrefix= ciBuild= @@ -77,6 +85,13 @@ while :; do shift ciBuild=1 ;; + --copy-src|-c) + shift + if [[ ! $_nixBitcoinInCopySrc ]]; then + . "$scriptDir/lib/copy-src.sh" + exit + fi + ;; *) break esac @@ -172,7 +187,7 @@ doBuild() { # Run the test by building the test derivation buildTest() { - vmTestNixExpr | doBuild $scenario $outLinkArg "$@" - + vmTestNixExpr | doBuild $scenario "$@" - } vmTestNixExpr() {