at master 9.1 kB view raw
1{ 2 config, 3 pkgs, 4 lib, 5 ... 6}: 7 8with lib; 9 10let 11 eachBitcoind = filterAttrs (bitcoindName: cfg: cfg.enable) config.services.bitcoind; 12 13 rpcUserOpts = 14 { name, ... }: 15 { 16 options = { 17 name = mkOption { 18 type = types.str; 19 example = "alice"; 20 description = '' 21 Username for JSON-RPC connections. 22 ''; 23 }; 24 passwordHMAC = mkOption { 25 type = types.uniq (types.strMatching "[0-9a-f]+\\$[0-9a-f]{64}"); 26 example = "f7efda5c189b999524f151318c0c86$d5b51b3beffbc02b724e5d095828e0bc8b2456e9ac8757ae3211a5d9b16a22ae"; 27 description = '' 28 Password HMAC-SHA-256 for JSON-RPC connections. Must be a string of the 29 format \<SALT-HEX\>$\<HMAC-HEX\>. 30 31 Tool (Python script) for HMAC generation is available here: 32 <https://github.com/bitcoin/bitcoin/blob/master/share/rpcauth/rpcauth.py> 33 ''; 34 }; 35 }; 36 config = { 37 name = mkDefault name; 38 }; 39 }; 40 41 bitcoindOpts = 42 { 43 config, 44 lib, 45 name, 46 ... 47 }: 48 { 49 options = { 50 51 enable = mkEnableOption "Bitcoin daemon"; 52 53 package = mkPackageOption pkgs "bitcoind" { }; 54 55 configFile = mkOption { 56 type = types.nullOr types.path; 57 default = null; 58 example = "/var/lib/${name}/bitcoin.conf"; 59 description = "The configuration file path to supply bitcoind."; 60 }; 61 62 extraConfig = mkOption { 63 type = types.lines; 64 default = ""; 65 example = '' 66 par=16 67 rpcthreads=16 68 logips=1 69 ''; 70 description = "Additional configurations to be appended to {file}`bitcoin.conf`."; 71 }; 72 73 dataDir = mkOption { 74 type = types.path; 75 default = "/var/lib/bitcoind-${name}"; 76 description = "The data directory for bitcoind."; 77 }; 78 79 user = mkOption { 80 type = types.str; 81 default = "bitcoind-${name}"; 82 description = "The user as which to run bitcoind."; 83 }; 84 85 group = mkOption { 86 type = types.str; 87 default = config.user; 88 description = "The group as which to run bitcoind."; 89 }; 90 91 rpc = { 92 port = mkOption { 93 type = types.nullOr types.port; 94 default = null; 95 description = "Override the default port on which to listen for JSON-RPC connections."; 96 }; 97 users = mkOption { 98 default = { }; 99 example = literalExpression '' 100 { 101 alice.passwordHMAC = "f7efda5c189b999524f151318c0c86$d5b51b3beffbc02b724e5d095828e0bc8b2456e9ac8757ae3211a5d9b16a22ae"; 102 bob.passwordHMAC = "b2dd077cb54591a2f3139e69a897ac$4e71f08d48b4347cf8eff3815c0e25ae2e9a4340474079f55705f40574f4ec99"; 103 } 104 ''; 105 type = types.attrsOf (types.submodule rpcUserOpts); 106 description = "RPC user information for JSON-RPC connections."; 107 }; 108 }; 109 110 pidFile = mkOption { 111 type = types.path; 112 default = "${config.dataDir}/bitcoind.pid"; 113 description = "Location of bitcoind pid file."; 114 }; 115 116 testnet = mkOption { 117 type = types.bool; 118 default = false; 119 description = "Whether to use the testnet instead of mainnet."; 120 }; 121 122 port = mkOption { 123 type = types.nullOr types.port; 124 default = null; 125 description = "Override the default port on which to listen for connections."; 126 }; 127 128 dbCache = mkOption { 129 type = types.nullOr (types.ints.between 4 16384); 130 default = null; 131 example = 4000; 132 description = "Override the default database cache size in MiB."; 133 }; 134 135 prune = mkOption { 136 type = types.nullOr ( 137 types.coercedTo (types.enum [ 138 "disable" 139 "manual" 140 ]) (x: if x == "disable" then 0 else 1) types.ints.unsigned 141 ); 142 default = null; 143 example = 10000; 144 description = '' 145 Reduce storage requirements by enabling pruning (deleting) of old 146 blocks. This allows the pruneblockchain RPC to be called to delete 147 specific blocks, and enables automatic pruning of old blocks if a 148 target size in MiB is provided. This mode is incompatible with -txindex 149 and -rescan. Warning: Reverting this setting requires re-downloading 150 the entire blockchain. ("disable" = disable pruning blocks, "manual" 151 = allow manual pruning via RPC, >=550 = automatically prune block files 152 to stay under the specified target size in MiB). 153 ''; 154 }; 155 156 extraCmdlineOptions = mkOption { 157 type = types.listOf types.str; 158 default = [ ]; 159 description = '' 160 Extra command line options to pass to bitcoind. 161 Run bitcoind --help to list all available options. 162 ''; 163 }; 164 }; 165 }; 166in 167{ 168 169 options = { 170 services.bitcoind = mkOption { 171 type = types.attrsOf (types.submodule bitcoindOpts); 172 default = { }; 173 description = "Specification of one or more bitcoind instances."; 174 }; 175 }; 176 177 config = mkIf (eachBitcoind != { }) { 178 179 assertions = flatten ( 180 mapAttrsToList (bitcoindName: cfg: [ 181 { 182 assertion = 183 (cfg.prune != null) 184 -> ( 185 builtins.elem cfg.prune [ 186 "disable" 187 "manual" 188 0 189 1 190 ] 191 || (builtins.isInt cfg.prune && cfg.prune >= 550) 192 ); 193 message = '' 194 If set, services.bitcoind.${bitcoindName}.prune has to be "disable", "manual", 0 , 1 or >= 550. 195 ''; 196 } 197 { 198 assertion = (cfg.rpc.users != { }) -> (cfg.configFile == null); 199 message = '' 200 You cannot set both services.bitcoind.${bitcoindName}.rpc.users and services.bitcoind.${bitcoindName}.configFile 201 as they are exclusive. RPC user setting would have no effect if custom configFile would be used. 202 ''; 203 } 204 ]) eachBitcoind 205 ); 206 207 environment.systemPackages = flatten ( 208 mapAttrsToList (bitcoindName: cfg: [ 209 cfg.package 210 ]) eachBitcoind 211 ); 212 213 systemd.services = mapAttrs' ( 214 bitcoindName: cfg: 215 (nameValuePair "bitcoind-${bitcoindName}" ( 216 let 217 configFile = pkgs.writeText "bitcoin.conf" '' 218 # If Testnet is enabled, we need to add [test] section 219 # otherwise, some options (e.g.: custom RPC port) will not work 220 ${optionalString cfg.testnet "[test]"} 221 # RPC users 222 ${concatMapStringsSep "\n" (rpcUser: "rpcauth=${rpcUser.name}:${rpcUser.passwordHMAC}") ( 223 attrValues cfg.rpc.users 224 )} 225 # Extra config options (from bitcoind nixos service) 226 ${cfg.extraConfig} 227 ''; 228 in 229 { 230 description = "Bitcoin daemon"; 231 wants = [ "network-online.target" ]; 232 after = [ "network-online.target" ]; 233 wantedBy = [ "multi-user.target" ]; 234 serviceConfig = { 235 User = cfg.user; 236 Group = cfg.group; 237 ExecStart = '' 238 ${cfg.package}/bin/bitcoind \ 239 ${if (cfg.configFile != null) then "-conf=${cfg.configFile}" else "-conf=${configFile}"} \ 240 -datadir=${cfg.dataDir} \ 241 -pid=${cfg.pidFile} \ 242 ${optionalString cfg.testnet "-testnet"}\ 243 ${optionalString (cfg.port != null) "-port=${toString cfg.port}"}\ 244 ${optionalString (cfg.prune != null) "-prune=${toString cfg.prune}"}\ 245 ${optionalString (cfg.dbCache != null) "-dbcache=${toString cfg.dbCache}"}\ 246 ${optionalString (cfg.rpc.port != null) "-rpcport=${toString cfg.rpc.port}"}\ 247 ${toString cfg.extraCmdlineOptions} 248 ''; 249 Restart = "on-failure"; 250 251 # Hardening measures 252 PrivateTmp = "true"; 253 ProtectSystem = "full"; 254 NoNewPrivileges = "true"; 255 PrivateDevices = "true"; 256 MemoryDenyWriteExecute = "true"; 257 }; 258 } 259 )) 260 ) eachBitcoind; 261 262 systemd.tmpfiles.rules = flatten ( 263 mapAttrsToList (bitcoindName: cfg: [ 264 "d '${cfg.dataDir}' 0770 '${cfg.user}' '${cfg.group}' - -" 265 ]) eachBitcoind 266 ); 267 268 users.users = mapAttrs' ( 269 bitcoindName: cfg: 270 (nameValuePair "bitcoind-${bitcoindName}" { 271 name = cfg.user; 272 group = cfg.group; 273 description = "Bitcoin daemon user"; 274 home = cfg.dataDir; 275 isSystemUser = true; 276 }) 277 ) eachBitcoind; 278 279 users.groups = mapAttrs' (bitcoindName: cfg: (nameValuePair "${cfg.group}" { })) eachBitcoind; 280 281 }; 282 283 meta.maintainers = with maintainers; [ _1000101 ]; 284 285}