1 { config, pkgs, lib, ... }:
7 eachBitcoind = config.services.bitcoind;
9 rpcUserOpts = { name, ... }: {
14 description = lib.mdDoc ''
15 Username for JSON-RPC connections.
18 passwordHMAC = mkOption {
19 type = types.uniq (types.strMatching "[0-9a-f]+\\$[0-9a-f]{64}");
20 example = "f7efda5c189b999524f151318c0c86$d5b51b3beffbc02b724e5d095828e0bc8b2456e9ac8757ae3211a5d9b16a22ae";
21 description = lib.mdDoc ''
22 Password HMAC-SHA-256 for JSON-RPC connections. Must be a string of the
23 format \<SALT-HEX\>$\<HMAC-HEX\>.
25 Tool (Python script) for HMAC generation is available here:
26 <https://github.com/bitcoin/bitcoin/blob/master/share/rpcauth/rpcauth.py>
31 name = mkDefault name;
35 bitcoindOpts = { config, lib, name, ...}: {
38 enable = mkEnableOption (lib.mdDoc "Bitcoin daemon");
42 default = pkgs.bitcoind;
43 defaultText = literalExpression "pkgs.bitcoind";
44 description = lib.mdDoc "The package providing bitcoin binaries.";
47 configFile = mkOption {
48 type = types.nullOr types.path;
50 example = "/var/lib/${name}/bitcoin.conf";
51 description = lib.mdDoc "The configuration file path to supply bitcoind.";
54 extraConfig = mkOption {
62 description = lib.mdDoc "Additional configurations to be appended to {file}`bitcoin.conf`.";
67 default = "/var/lib/bitcoind-${name}";
68 description = lib.mdDoc "The data directory for bitcoind.";
73 default = "bitcoind-${name}";
74 description = lib.mdDoc "The user as which to run bitcoind.";
79 default = config.user;
80 description = lib.mdDoc "The group as which to run bitcoind.";
85 type = types.nullOr types.port;
87 description = lib.mdDoc "Override the default port on which to listen for JSON-RPC connections.";
91 example = literalExpression ''
93 alice.passwordHMAC = "f7efda5c189b999524f151318c0c86$d5b51b3beffbc02b724e5d095828e0bc8b2456e9ac8757ae3211a5d9b16a22ae";
94 bob.passwordHMAC = "b2dd077cb54591a2f3139e69a897ac$4e71f08d48b4347cf8eff3815c0e25ae2e9a4340474079f55705f40574f4ec99";
97 type = types.attrsOf (types.submodule rpcUserOpts);
98 description = lib.mdDoc "RPC user information for JSON-RPC connnections.";
104 default = "${config.dataDir}/bitcoind.pid";
105 description = lib.mdDoc "Location of bitcoind pid file.";
111 description = lib.mdDoc "Whether to use the testnet instead of mainnet.";
115 type = types.nullOr types.port;
117 description = lib.mdDoc "Override the default port on which to listen for connections.";
121 type = types.nullOr (types.ints.between 4 16384);
124 description = lib.mdDoc "Override the default database cache size in MiB.";
128 type = types.nullOr (types.coercedTo
129 (types.enum [ "disable" "manual" ])
130 (x: if x == "disable" then 0 else 1)
135 description = lib.mdDoc ''
136 Reduce storage requirements by enabling pruning (deleting) of old
137 blocks. This allows the pruneblockchain RPC to be called to delete
138 specific blocks, and enables automatic pruning of old blocks if a
139 target size in MiB is provided. This mode is incompatible with -txindex
140 and -rescan. Warning: Reverting this setting requires re-downloading
141 the entire blockchain. ("disable" = disable pruning blocks, "manual"
142 = allow manual pruning via RPC, >=550 = automatically prune block files
143 to stay under the specified target size in MiB).
147 extraCmdlineOptions = mkOption {
148 type = types.listOf types.str;
150 description = lib.mdDoc ''
151 Extra command line options to pass to bitcoind.
152 Run bitcoind --help to list all available options.
161 services.bitcoind = mkOption {
162 type = types.attrsOf (types.submodule bitcoindOpts);
164 description = lib.mdDoc "Specification of one or more bitcoind instances.";
168 config = mkIf (eachBitcoind != {}) {
170 assertions = flatten (mapAttrsToList (bitcoindName: cfg: [
172 assertion = (cfg.prune != null) -> (builtins.elem cfg.prune [ "disable" "manual" 0 1 ] || (builtins.isInt cfg.prune && cfg.prune >= 550));
174 If set, services.bitcoind.${bitcoindName}.prune has to be "disable", "manual", 0 , 1 or >= 550.
178 assertion = (cfg.rpc.users != {}) -> (cfg.configFile == null);
180 You cannot set both services.bitcoind.${bitcoindName}.rpc.users and services.bitcoind.${bitcoindName}.configFile
181 as they are exclusive. RPC user setting would have no effect if custom configFile would be used.
186 environment.systemPackages = flatten (mapAttrsToList (bitcoindName: cfg: [
190 systemd.services = mapAttrs' (bitcoindName: cfg: (
191 nameValuePair "bitcoind-${bitcoindName}" (
193 configFile = pkgs.writeText "bitcoin.conf" ''
194 # If Testnet is enabled, we need to add [test] section
195 # otherwise, some options (e.g.: custom RPC port) will not work
196 ${optionalString cfg.testnet "[test]"}
198 ${concatMapStringsSep "\n"
199 (rpcUser: "rpcauth=${rpcUser.name}:${rpcUser.passwordHMAC}")
200 (attrValues cfg.rpc.users)
202 # Extra config options (from bitcoind nixos service)
206 description = "Bitcoin daemon";
207 after = [ "network-online.target" ];
208 wantedBy = [ "multi-user.target" ];
213 ${cfg.package}/bin/bitcoind \
214 ${if (cfg.configFile != null) then
215 "-conf=${cfg.configFile}"
217 "-conf=${configFile}"
219 -datadir=${cfg.dataDir} \
220 -pid=${cfg.pidFile} \
221 ${optionalString cfg.testnet "-testnet"}\
222 ${optionalString (cfg.port != null) "-port=${toString cfg.port}"}\
223 ${optionalString (cfg.prune != null) "-prune=${toString cfg.prune}"}\
224 ${optionalString (cfg.dbCache != null) "-dbcache=${toString cfg.dbCache}"}\
225 ${optionalString (cfg.rpc.port != null) "-rpcport=${toString cfg.rpc.port}"}\
226 ${toString cfg.extraCmdlineOptions}
228 Restart = "on-failure";
232 ProtectSystem = "full";
233 NoNewPrivileges = "true";
234 PrivateDevices = "true";
235 MemoryDenyWriteExecute = "true";
240 systemd.tmpfiles.rules = flatten (mapAttrsToList (bitcoindName: cfg: [
241 "d '${cfg.dataDir}' 0770 '${cfg.user}' '${cfg.group}' - -"
244 users.users = mapAttrs' (bitcoindName: cfg: (
245 nameValuePair "bitcoind-${bitcoindName}" {
248 description = "Bitcoin daemon user";
253 users.groups = mapAttrs' (bitcoindName: cfg: (
254 nameValuePair "${cfg.group}" { }
259 meta.maintainers = with maintainers; [ _1000101 ];