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