at 23.05-pre 8.7 kB view raw
1{ config, pkgs, lib, ... }: 2 3with lib; 4 5let 6 7 eachBitcoind = config.services.bitcoind; 8 9 rpcUserOpts = { name, ... }: { 10 options = { 11 name = mkOption { 12 type = types.str; 13 example = "alice"; 14 description = lib.mdDoc '' 15 Username for JSON-RPC connections. 16 ''; 17 }; 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\>. 24 25 Tool (Python script) for HMAC generation is available here: 26 <https://github.com/bitcoin/bitcoin/blob/master/share/rpcauth/rpcauth.py> 27 ''; 28 }; 29 }; 30 config = { 31 name = mkDefault name; 32 }; 33 }; 34 35 bitcoindOpts = { config, lib, name, ...}: { 36 options = { 37 38 enable = mkEnableOption (lib.mdDoc "Bitcoin daemon"); 39 40 package = mkOption { 41 type = types.package; 42 default = pkgs.bitcoind; 43 defaultText = literalExpression "pkgs.bitcoind"; 44 description = lib.mdDoc "The package providing bitcoin binaries."; 45 }; 46 47 configFile = mkOption { 48 type = types.nullOr types.path; 49 default = null; 50 example = "/var/lib/${name}/bitcoin.conf"; 51 description = lib.mdDoc "The configuration file path to supply bitcoind."; 52 }; 53 54 extraConfig = mkOption { 55 type = types.lines; 56 default = ""; 57 example = '' 58 par=16 59 rpcthreads=16 60 logips=1 61 ''; 62 description = lib.mdDoc "Additional configurations to be appended to {file}`bitcoin.conf`."; 63 }; 64 65 dataDir = mkOption { 66 type = types.path; 67 default = "/var/lib/bitcoind-${name}"; 68 description = lib.mdDoc "The data directory for bitcoind."; 69 }; 70 71 user = mkOption { 72 type = types.str; 73 default = "bitcoind-${name}"; 74 description = lib.mdDoc "The user as which to run bitcoind."; 75 }; 76 77 group = mkOption { 78 type = types.str; 79 default = config.user; 80 description = lib.mdDoc "The group as which to run bitcoind."; 81 }; 82 83 rpc = { 84 port = mkOption { 85 type = types.nullOr types.port; 86 default = null; 87 description = lib.mdDoc "Override the default port on which to listen for JSON-RPC connections."; 88 }; 89 users = mkOption { 90 default = {}; 91 example = literalExpression '' 92 { 93 alice.passwordHMAC = "f7efda5c189b999524f151318c0c86$d5b51b3beffbc02b724e5d095828e0bc8b2456e9ac8757ae3211a5d9b16a22ae"; 94 bob.passwordHMAC = "b2dd077cb54591a2f3139e69a897ac$4e71f08d48b4347cf8eff3815c0e25ae2e9a4340474079f55705f40574f4ec99"; 95 } 96 ''; 97 type = types.attrsOf (types.submodule rpcUserOpts); 98 description = lib.mdDoc "RPC user information for JSON-RPC connnections."; 99 }; 100 }; 101 102 pidFile = mkOption { 103 type = types.path; 104 default = "${config.dataDir}/bitcoind.pid"; 105 description = lib.mdDoc "Location of bitcoind pid file."; 106 }; 107 108 testnet = mkOption { 109 type = types.bool; 110 default = false; 111 description = lib.mdDoc "Whether to use the testnet instead of mainnet."; 112 }; 113 114 port = mkOption { 115 type = types.nullOr types.port; 116 default = null; 117 description = lib.mdDoc "Override the default port on which to listen for connections."; 118 }; 119 120 dbCache = mkOption { 121 type = types.nullOr (types.ints.between 4 16384); 122 default = null; 123 example = 4000; 124 description = lib.mdDoc "Override the default database cache size in MiB."; 125 }; 126 127 prune = mkOption { 128 type = types.nullOr (types.coercedTo 129 (types.enum [ "disable" "manual" ]) 130 (x: if x == "disable" then 0 else 1) 131 types.ints.unsigned 132 ); 133 default = null; 134 example = 10000; 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). 144 ''; 145 }; 146 147 extraCmdlineOptions = mkOption { 148 type = types.listOf types.str; 149 default = []; 150 description = lib.mdDoc '' 151 Extra command line options to pass to bitcoind. 152 Run bitcoind --help to list all available options. 153 ''; 154 }; 155 }; 156 }; 157in 158{ 159 160 options = { 161 services.bitcoind = mkOption { 162 type = types.attrsOf (types.submodule bitcoindOpts); 163 default = {}; 164 description = lib.mdDoc "Specification of one or more bitcoind instances."; 165 }; 166 }; 167 168 config = mkIf (eachBitcoind != {}) { 169 170 assertions = flatten (mapAttrsToList (bitcoindName: cfg: [ 171 { 172 assertion = (cfg.prune != null) -> (builtins.elem cfg.prune [ "disable" "manual" 0 1 ] || (builtins.isInt cfg.prune && cfg.prune >= 550)); 173 message = '' 174 If set, services.bitcoind.${bitcoindName}.prune has to be "disable", "manual", 0 , 1 or >= 550. 175 ''; 176 } 177 { 178 assertion = (cfg.rpc.users != {}) -> (cfg.configFile == null); 179 message = '' 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. 182 ''; 183 } 184 ]) eachBitcoind); 185 186 environment.systemPackages = flatten (mapAttrsToList (bitcoindName: cfg: [ 187 cfg.package 188 ]) eachBitcoind); 189 190 systemd.services = mapAttrs' (bitcoindName: cfg: ( 191 nameValuePair "bitcoind-${bitcoindName}" ( 192 let 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]"} 197 # RPC users 198 ${concatMapStringsSep "\n" 199 (rpcUser: "rpcauth=${rpcUser.name}:${rpcUser.passwordHMAC}") 200 (attrValues cfg.rpc.users) 201 } 202 # Extra config options (from bitcoind nixos service) 203 ${cfg.extraConfig} 204 ''; 205 in { 206 description = "Bitcoin daemon"; 207 after = [ "network-online.target" ]; 208 wantedBy = [ "multi-user.target" ]; 209 serviceConfig = { 210 User = cfg.user; 211 Group = cfg.group; 212 ExecStart = '' 213 ${cfg.package}/bin/bitcoind \ 214 ${if (cfg.configFile != null) then 215 "-conf=${cfg.configFile}" 216 else 217 "-conf=${configFile}" 218 } \ 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} 227 ''; 228 Restart = "on-failure"; 229 230 # Hardening measures 231 PrivateTmp = "true"; 232 ProtectSystem = "full"; 233 NoNewPrivileges = "true"; 234 PrivateDevices = "true"; 235 MemoryDenyWriteExecute = "true"; 236 }; 237 } 238 ))) eachBitcoind; 239 240 systemd.tmpfiles.rules = flatten (mapAttrsToList (bitcoindName: cfg: [ 241 "d '${cfg.dataDir}' 0770 '${cfg.user}' '${cfg.group}' - -" 242 ]) eachBitcoind); 243 244 users.users = mapAttrs' (bitcoindName: cfg: ( 245 nameValuePair "bitcoind-${bitcoindName}" { 246 name = cfg.user; 247 group = cfg.group; 248 description = "Bitcoin daemon user"; 249 home = cfg.dataDir; 250 isSystemUser = true; 251 })) eachBitcoind; 252 253 users.groups = mapAttrs' (bitcoindName: cfg: ( 254 nameValuePair "${cfg.group}" { } 255 )) eachBitcoind; 256 257 }; 258 259 meta.maintainers = with maintainers; [ _1000101 ]; 260 261}