diff --git a/configuration.nix b/configuration.nix index 274bed5..9aad899 100644 --- a/configuration.nix +++ b/configuration.nix @@ -40,10 +40,6 @@ in { # networking.firewall.allowedUDPPorts = [ ... ]; networking.firewall.enable = true; - services.bitcoin.enable = true; - # make an onion listen node - services.tor.enable = true; - services.tor.client.enable = true; #services.bitcoin.proxy = services.tor.client.socksListenAddress; services.nixbitcoin.enable = true; diff --git a/modules/bitcoind.nix b/modules/bitcoind.nix index 0bb4ee4..08db879 100644 --- a/modules/bitcoind.nix +++ b/modules/bitcoind.nix @@ -1,85 +1,226 @@ -{ config, lib, pkgs, ... }: +{ config, pkgs, lib, ... }: with lib; let - cfg = config.services.bitcoin; - home = "/var/lib/bitcoin"; + cfg = config.services.bitcoind; + pidFile = "${cfg.dataDir}/bitcoind.pid"; configFile = pkgs.writeText "bitcoin.conf" '' - listen=${if cfg.listen then "1" else "0"} - prune=2000 - assumevalid=0000000000000000000726d186d6298b5054b9a5c49639752294b322a305d240 - ${optionalString (cfg.proxy != null) "proxy=${cfg.proxy}"} - addnode=ecoc5q34tmbq54wl.onion - discover=0 + ${optionalString cfg.testnet "testnet=1"} + ${optionalString (cfg.dbCache != null) "dbcache=${toString cfg.dbCache}"} + ${optionalString (cfg.prune != null) "prune=${toString cfg.prune}"} + + # Connection options ${optionalString (cfg.port != null) "port=${toString cfg.port}"} + ${optionalString (cfg.proxy != null) "proxy=${cfg.proxy}"} + listen=${if cfg.listen then "1" else "0"} + + # RPC server options + ${optionalString (cfg.rpc.port != null) "rpcport=${toString cfg.rpc.port}"} + ${concatMapStringsSep "\n" + (rpcUser: "rpcauth=${rpcUser.name}:${rpcUser.passwordHMAC}") + (attrValues cfg.rpc.users) + } ${optionalString (cfg.rpcuser != null) "rpcuser=${cfg.rpcuser}"} ${optionalString (cfg.rpcpassword != null) "rpcpassword=${cfg.rpcpassword}"} - ''; + + # Extra config options (from bitcoind nixos service) + ${cfg.extraConfig} + ''; + cmdlineOptions = concatMapStringsSep " " (arg: "'${arg}'") [ + "-conf=${configFile}" + "-datadir=${cfg.dataDir}" + "-pid=${pidFile}" + ]; + hexStr = types.strMatching "[0-9a-f]+"; + rpcUserOpts = { name, ... }: { + options = { + name = mkOption { + type = types.str; + example = "alice"; + description = '' + Username for JSON-RPC connections. + ''; + }; + passwordHMAC = mkOption { + type = with types; uniq (strMatching "[0-9a-f]+\\$[0-9a-f]{64}"); + example = "f7efda5c189b999524f151318c0c86$d5b51b3beffbc02b724e5d095828e0bc8b2456e9ac8757ae3211a5d9b16a22ae"; + description = '' + Password HMAC-SHA-256 for JSON-RPC connections. Must be a string of the + format $. + ''; + }; + }; + config = { + name = mkDefault name; + }; + }; in { - options.services.bitcoin = { - enable = mkOption { - type = types.bool; - default = false; - description = '' - If enabled, the bitcoin service will be installed. - ''; - }; - listen = mkOption { - type = types.bool; - default = false; - description = '' - If enabled, the bitcoin service will listen. - ''; - }; - proxy = mkOption { - type = types.nullOr types.string; - default = null; - description = '' - proxy - ''; - }; - port = mkOption { + options = { + + services.bitcoind = { + enable = mkEnableOption "Bitcoin daemon"; + + package = mkOption { + type = types.package; + default = pkgs.altcoins.bitcoind; + defaultText = "pkgs.altcoins.bitcoind"; + description = "The package providing bitcoin binaries."; + }; + extraConfig = mkOption { + type = types.lines; + default = ""; + example = '' + par=16 + rpcthreads=16 + logips=1 + assumevalid=0000000000000000000726d186d6298b5054b9a5c49639752294b322a305d240 + addnode=ecoc5q34tmbq54wl.onion + discover=0 + printtoconsole=1 + ''; + description = "Additional configurations to be appended to bitcoin.conf."; + }; + dataDir = mkOption { + type = types.path; + default = "/var/lib/bitcoind"; + description = "The data directory for bitcoind."; + }; + + user = mkOption { + type = types.str; + default = "bitcoin"; + description = "The user as which to run bitcoind."; + }; + group = mkOption { + type = types.str; + default = cfg.user; + description = "The group as which to run bitcoind."; + }; + + rpc = { + port = mkOption { + type = types.nullOr types.ints.u16; + default = null; + description = "Override the default port on which to listen for JSON-RPC connections."; + }; + users = mkOption { + default = {}; + example = { + alice.passwordHMAC = "f7efda5c189b999524f151318c0c86$d5b51b3beffbc02b724e5d095828e0bc8b2456e9ac8757ae3211a5d9b16a22ae"; + bob.passwordHMAC = "b2dd077cb54591a2f3139e69a897ac$4e71f08d48b4347cf8eff3815c0e25ae2e9a4340474079f55705f40574f4ec99"; + }; + type = with types; loaOf (submodule rpcUserOpts); + description = '' + RPC user information for JSON-RPC connnections. + ''; + }; + }; + + rpcuser = mkOption { + type = types.nullOr types.string; + default = null; + description = "Username for JSON-RPC connections"; + }; + rpcpassword = mkOption { + type = types.nullOr types.string; + default = null; + description = "Password for JSON-RPC connections"; + }; + + testnet = mkOption { + type = types.bool; + default = false; + description = "Whether to use the test chain."; + }; + port = mkOption { type = types.nullOr types.ints.u16; default = null; description = "Override the default port on which to listen for connections."; - }; - rpcuser = mkOption { + }; + proxy = mkOption { type = types.nullOr types.string; default = null; - description = "Set bitcoin RPC user"; - }; - rpcpassword = mkOption { - type = types.nullOr types.string; + description = "Connect through SOCKS5 proxy"; + }; + listen = mkOption { + type = types.bool; + default = false; + description = '' + If enabled, the bitcoin service will listen. + ''; + }; + dbCache = mkOption { + type = types.nullOr (types.ints.between 4 16384); default = null; - description = "Set bitcoin RPC password"; + example = 4000; + description = "Override the default database cache size in megabytes."; + }; + prune = mkOption { + type = types.nullOr (types.coercedTo + (types.enum [ "disable" "manual" ]) + (x: if x == "disable" then 0 else 1) + types.ints.unsigned + ); + default = null; + 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) + ''; + }; }; }; + config = mkIf cfg.enable { - users.users.bitcoin = { - description = "Bitcoind User"; - createHome = true; - inherit home; - }; + environment.systemPackages = [ cfg.package ]; systemd.services.bitcoind = { - description = "Run bitcoind"; - path = [ pkgs.bitcoin ]; + description = "Bitcoin daemon"; + after = [ "network.target" ]; wantedBy = [ "multi-user.target" ]; preStart = '' - mkdir -p ${home}/.bitcoin - ln -sf ${configFile} ${home}/.bitcoin/bitcoin.conf - ''; + if ! test -e ${cfg.dataDir}; then + mkdir -m 0770 -p '${cfg.dataDir}' + chown '${cfg.user}:${cfg.group}' '${cfg.dataDir}' + ln -s '${configFile}' '${cfg.dataDir}/bitcoin.conf' + fi + ''; serviceConfig = { - ExecStart = "${pkgs.bitcoin}/bin/bitcoind"; - User = "bitcoin"; + Type = "simple"; + User = "${cfg.user}"; + Group = "${cfg.group}"; + ExecStart = "${cfg.package}/bin/bitcoind ${cmdlineOptions}"; + StateDirectory = "bitcoind"; + PIDFile = "${pidFile}"; Restart = "on-failure"; + # Hardening measures PrivateTmp = "true"; ProtectSystem = "full"; NoNewPrivileges = "true"; PrivateDevices = "true"; MemoryDenyWriteExecute = "true"; + + # Permission for preStart + PermissionsStartOnly = "true"; }; }; + users.users.${cfg.user} = { + name = cfg.user; + #uid = config.ids.uids.bitcoin; + group = cfg.group; + description = "Bitcoin daemon user"; + home = cfg.dataDir; + }; + users.groups.${cfg.group} = { + name = cfg.group; + #gid = config.ids.gids.bitcoin; + }; }; } diff --git a/modules/nixbitcoin.nix b/modules/nixbitcoin.nix index f69aa72..9601abd 100644 --- a/modules/nixbitcoin.nix +++ b/modules/nixbitcoin.nix @@ -29,23 +29,23 @@ in { services.tor.client.enable = true; services.tor.hiddenServices.bitcoind = { map = [{ - port = config.services.bitcoin.port; + port = config.services.bitcoind.port; }]; version = 3; }; - # bitcoin - services.bitcoin.enable = true; - services.bitcoin.listen = true; - services.bitcoin.proxy = config.services.tor.client.socksListenAddress; - services.bitcoin.port = 8333; - services.bitcoin.rpcuser = "bitcoinrpc"; - services.bitcoin.rpcpassword = secrets.bitcoinrpcpassword; + # bitcoind + services.bitcoind.enable = true; + services.bitcoind.listen = true; + services.bitcoind.proxy = config.services.tor.client.socksListenAddress; + services.bitcoind.port = 8333; + services.bitcoind.rpcuser = "bitcoinrpc"; + services.bitcoind.rpcpassword = secrets.bitcoinrpcpassword; # clightning services.clightning.enable = true; - services.clightning.bitcoin-rpcuser = config.services.bitcoin.rpcuser; - services.clightning.bitcoin-rpcpassword = config.services.bitcoin.rpcpassword; + services.clightning.bitcoin-rpcuser = config.services.bitcoind.rpcuser; + services.clightning.bitcoin-rpcpassword = config.services.bitcoind.rpcpassword; # nodeinfo systemd.services.nodeinfo = { diff --git a/network.nix b/network.nix index 70c13e7..2f44385 100644 --- a/network.nix +++ b/network.nix @@ -1,6 +1,4 @@ -let - secrets = import ./load-secrets.nix; -in { +{ network.description = "Bitcoin Core node"; bitcoin-node = import ./configuration.nix;