diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index 434cb69868c7..bc8bcc0cd8f6 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -536,6 +536,7 @@ ./services/networking/avahi-daemon.nix ./services/networking/babeld.nix ./services/networking/bind.nix + ./services/networking/bitcoind.nix ./services/networking/autossh.nix ./services/networking/bird.nix ./services/networking/bitlbee.nix diff --git a/nixos/modules/services/networking/bitcoind.nix b/nixos/modules/services/networking/bitcoind.nix new file mode 100644 index 000000000000..e94265564595 --- /dev/null +++ b/nixos/modules/services/networking/bitcoind.nix @@ -0,0 +1,195 @@ +{ config, pkgs, lib, ... }: + +with lib; + +let + cfg = config.services.bitcoind; + pidFile = "${cfg.dataDir}/bitcoind.pid"; + configFile = pkgs.writeText "bitcoin.conf" '' + ${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}"} + + # 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) + } + + # Extra config options (from bitcoind nixos service) + ${cfg.extraConfig} + ''; + cmdlineOptions = escapeShellArgs [ + "-conf=${cfg.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 <SALT-HEX>$<HMAC-HEX>. + ''; + }; + }; + config = { + name = mkDefault name; + }; + }; +in { + 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."; + }; + configFile = mkOption { + type = types.path; + default = configFile; + example = "/etc/bitcoind.conf"; + description = "The configuration file path to supply bitcoind."; + }; + extraConfig = mkOption { + type = types.lines; + default = ""; + example = '' + par=16 + rpcthreads=16 + logips=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.port; + default = null; + description = "Override the default port on which to listen for JSON-RPC connections."; + }; + users = mkOption { + default = {}; + example = literalExample '' + { + alice.passwordHMAC = "f7efda5c189b999524f151318c0c86$d5b51b3beffbc02b724e5d095828e0bc8b2456e9ac8757ae3211a5d9b16a22ae"; + bob.passwordHMAC = "b2dd077cb54591a2f3139e69a897ac$4e71f08d48b4347cf8eff3815c0e25ae2e9a4340474079f55705f40574f4ec99"; + } + ''; + type = with types; loaOf (submodule rpcUserOpts); + description = '' + RPC user information for JSON-RPC connnections. + ''; + }; + }; + + testnet = mkOption { + type = types.bool; + default = false; + description = "Whether to use the test chain."; + }; + port = mkOption { + type = types.nullOr types.port; + default = null; + description = "Override the default port on which to listen for connections."; + }; + dbCache = mkOption { + type = types.nullOr (types.ints.between 4 16384); + default = null; + 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 { + environment.systemPackages = [ cfg.package ]; + systemd.tmpfiles.rules = [ + "d '${cfg.dataDir}' 0770 '${cfg.user}' '${cfg.group}' - -" + "L '${cfg.dataDir}/bitcoin.conf' - - - - '${cfg.configFile}'" + ]; + systemd.services.bitcoind = { + description = "Bitcoin daemon"; + after = [ "network.target" ]; + wantedBy = [ "multi-user.target" ]; + serviceConfig = { + User = cfg.user; + Group = cfg.group; + ExecStart = "${cfg.package}/bin/bitcoind ${cmdlineOptions}"; + 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; + group = cfg.group; + description = "Bitcoin daemon user"; + home = cfg.dataDir; + }; + users.groups.${cfg.group} = { + name = cfg.group; + }; + }; +}