grafana-alloy: don't build the frontend twice
[NixPkgs.git] / nixos / modules / services / misc / rippled.nix
blob5f011dbee4a6eeec9f4787dbb697e03774f68240
1 { config, lib, options, pkgs, ... }:
2 let
3   cfg = config.services.rippled;
4   opt = options.services.rippled;
6   b2i = val: if val then "1" else "0";
8   dbCfg = db: ''
9     type=${db.type}
10     path=${db.path}
11     ${lib.optionalString (db.compression != null) ("compression=${b2i db.compression}") }
12     ${lib.optionalString (db.onlineDelete != null) ("online_delete=${toString db.onlineDelete}")}
13     ${lib.optionalString (db.advisoryDelete != null) ("advisory_delete=${b2i db.advisoryDelete}")}
14     ${db.extraOpts}
15   '';
17   rippledCfg = ''
18     [server]
19     ${lib.concatMapStringsSep "\n" (n: "port_${n}") (lib.attrNames cfg.ports)}
21     ${lib.concatMapStrings (p: ''
22     [port_${p.name}]
23     ip=${p.ip}
24     port=${toString p.port}
25     protocol=${lib.concatStringsSep "," p.protocol}
26     ${lib.optionalString (p.user != "") "user=${p.user}"}
27     ${lib.optionalString (p.password != "") "user=${p.password}"}
28     admin=${lib.concatStringsSep "," p.admin}
29     ${lib.optionalString (p.ssl.key != null) "ssl_key=${p.ssl.key}"}
30     ${lib.optionalString (p.ssl.cert != null) "ssl_cert=${p.ssl.cert}"}
31     ${lib.optionalString (p.ssl.chain != null) "ssl_chain=${p.ssl.chain}"}
32     '') (lib.attrValues cfg.ports)}
34     [database_path]
35     ${cfg.databasePath}
37     [node_db]
38     ${dbCfg cfg.nodeDb}
40     ${lib.optionalString (cfg.tempDb != null) ''
41     [temp_db]
42     ${dbCfg cfg.tempDb}''}
44     ${lib.optionalString (cfg.importDb != null) ''
45     [import_db]
46     ${dbCfg cfg.importDb}''}
48     [ips]
49     ${lib.concatStringsSep "\n" cfg.ips}
51     [ips_fixed]
52     ${lib.concatStringsSep "\n" cfg.ipsFixed}
54     [validators]
55     ${lib.concatStringsSep "\n" cfg.validators}
57     [node_size]
58     ${cfg.nodeSize}
60     [ledger_history]
61     ${toString cfg.ledgerHistory}
63     [fetch_depth]
64     ${toString cfg.fetchDepth}
66     [validation_quorum]
67     ${toString cfg.validationQuorum}
69     [sntp_servers]
70     ${lib.concatStringsSep "\n" cfg.sntpServers}
72     ${lib.optionalString cfg.statsd.enable ''
73     [insight]
74     server=statsd
75     address=${cfg.statsd.address}
76     prefix=${cfg.statsd.prefix}
77     ''}
79     [rpc_startup]
80     { "command": "log_level", "severity": "${cfg.logLevel}" }
81   '' + cfg.extraConfig;
83   portOptions = { name, ...}: {
84     options = {
85       name = lib.mkOption {
86         internal = true;
87         default = name;
88       };
90       ip = lib.mkOption {
91         default = "127.0.0.1";
92         description = "Ip where rippled listens.";
93         type = lib.types.str;
94       };
96       port = lib.mkOption {
97         description = "Port where rippled listens.";
98         type = lib.types.port;
99       };
101       protocol = lib.mkOption {
102         description = "Protocols expose by rippled.";
103         type = lib.types.listOf (lib.types.enum ["http" "https" "ws" "wss" "peer"]);
104       };
106       user = lib.mkOption {
107         description = "When set, these credentials will be required on HTTP/S requests.";
108         type = lib.types.str;
109         default = "";
110       };
112       password = lib.mkOption {
113         description = "When set, these credentials will be required on HTTP/S requests.";
114         type = lib.types.str;
115         default = "";
116       };
118       admin = lib.mkOption {
119         description = "A comma-separated list of admin IP addresses.";
120         type = lib.types.listOf lib.types.str;
121         default = ["127.0.0.1"];
122       };
124       ssl = {
125         key = lib.mkOption {
126           description = ''
127             Specifies the filename holding the SSL key in PEM format.
128           '';
129           default = null;
130           type = lib.types.nullOr lib.types.path;
131         };
133         cert = lib.mkOption {
134           description = ''
135             Specifies the path to the SSL certificate file in PEM format.
136             This is not needed if the chain includes it.
137           '';
138           default = null;
139           type = lib.types.nullOr lib.types.path;
140         };
142         chain = lib.mkOption {
143           description = ''
144             If you need a certificate chain, specify the path to the
145             certificate chain here. The chain may include the end certificate.
146           '';
147           default = null;
148           type = lib.types.nullOr lib.types.path;
149         };
150       };
151     };
152   };
154   dbOptions = {
155     options = {
156       type = lib.mkOption {
157         description = "Rippled database type.";
158         type = lib.types.enum ["rocksdb" "nudb"];
159         default = "rocksdb";
160       };
162       path = lib.mkOption {
163         description = "Location to store the database.";
164         type = lib.types.path;
165         default = cfg.databasePath;
166         defaultText = lib.literalExpression "config.${opt.databasePath}";
167       };
169       compression = lib.mkOption {
170         description = "Whether to enable snappy compression.";
171         type = lib.types.nullOr lib.types.bool;
172         default = null;
173       };
175       onlineDelete = lib.mkOption {
176         description = "Enable automatic purging of older ledger information.";
177         type = lib.types.nullOr (lib.types.addCheck lib.types.int (v: v > 256));
178         default = cfg.ledgerHistory;
179         defaultText = lib.literalExpression "config.${opt.ledgerHistory}";
180       };
182       advisoryDelete = lib.mkOption {
183         description = ''
184           If set, then require administrative RPC call "can_delete"
185           to enable online deletion of ledger records.
186         '';
187         type = lib.types.nullOr lib.types.bool;
188         default = null;
189       };
191       extraOpts = lib.mkOption {
192         description = "Extra database options.";
193         type = lib.types.lines;
194         default = "";
195       };
196     };
197   };
203   ###### interface
205   options = {
206     services.rippled = {
207       enable = lib.mkEnableOption "rippled, a decentralized cryptocurrency blockchain daemon implementing the XRP Ledger protocol in C++";
209       package = lib.mkPackageOption pkgs "rippled" { };
211       ports = lib.mkOption {
212         description = "Ports exposed by rippled";
213         type = with lib.types; attrsOf (submodule portOptions);
214         default = {
215           rpc = {
216             port = 5005;
217             admin = ["127.0.0.1"];
218             protocol = ["http"];
219           };
221           peer = {
222             port = 51235;
223             ip = "0.0.0.0";
224             protocol = ["peer"];
225           };
227           ws_public = {
228             port = 5006;
229             ip = "0.0.0.0";
230             protocol = ["ws" "wss"];
231           };
232         };
233       };
235       nodeDb = lib.mkOption {
236         description = "Rippled main database options.";
237         type = with lib.types; nullOr (submodule dbOptions);
238         default = {
239           type = "rocksdb";
240           extraOpts = ''
241             open_files=2000
242             filter_bits=12
243             cache_mb=256
244             file_size_pb=8
245             file_size_mult=2;
246           '';
247         };
248       };
250       tempDb = lib.mkOption {
251         description = "Rippled temporary database options.";
252         type = with lib.types; nullOr (submodule dbOptions);
253         default = null;
254       };
256       importDb = lib.mkOption {
257         description = "Settings for performing a one-time import.";
258         type = with lib.types; nullOr (submodule dbOptions);
259         default = null;
260       };
262       nodeSize = lib.mkOption {
263         description = ''
264           Rippled size of the node you are running.
265           "tiny", "small", "medium", "large", and "huge"
266         '';
267         type = lib.types.enum ["tiny" "small" "medium" "large" "huge"];
268         default = "small";
269       };
271       ips = lib.mkOption {
272         description = ''
273           List of hostnames or ips where the Ripple protocol is served.
274           For a starter list, you can either copy entries from:
275           https://ripple.com/ripple.txt or if you prefer you can let it
276            default to r.ripple.com 51235
278           A port may optionally be specified after adding a space to the
279           address. By convention, if known, IPs are listed in from most
280           to least trusted.
281         '';
282         type = lib.types.listOf lib.types.str;
283         default = ["r.ripple.com 51235"];
284       };
286       ipsFixed = lib.mkOption {
287         description = ''
288           List of IP addresses or hostnames to which rippled should always
289           attempt to maintain peer connections with. This is useful for
290           manually forming private networks, for example to configure a
291           validation server that connects to the Ripple network through a
292           public-facing server, or for building a set of cluster peers.
294           A port may optionally be specified after adding a space to the address
295         '';
296         type = lib.types.listOf lib.types.str;
297         default = [];
298       };
300       validators = lib.mkOption {
301         description = ''
302           List of nodes to always accept as validators. Nodes are specified by domain
303           or public key.
304         '';
305         type = lib.types.listOf lib.types.str;
306         default = [
307           "n949f75evCHwgyP4fPVgaHqNHxUVN15PsJEZ3B3HnXPcPjcZAoy7  RL1"
308           "n9MD5h24qrQqiyBC8aeqqCWvpiBiYQ3jxSr91uiDvmrkyHRdYLUj  RL2"
309           "n9L81uNCaPgtUJfaHh89gmdvXKAmSt5Gdsw2g1iPWaPkAHW5Nm4C  RL3"
310           "n9KiYM9CgngLvtRCQHZwgC2gjpdaZcCcbt3VboxiNFcKuwFVujzS  RL4"
311           "n9LdgEtkmGB9E2h3K4Vp7iGUaKuq23Zr32ehxiU8FWY7xoxbWTSA  RL5"
312         ];
313       };
315       databasePath = lib.mkOption {
316         description = ''
317           Path to the ripple database.
318         '';
319         type = lib.types.path;
320         default = "/var/lib/rippled";
321       };
323       validationQuorum = lib.mkOption {
324         description = ''
325           The minimum number of trusted validations a ledger must have before
326           the server considers it fully validated.
327         '';
328         type = lib.types.int;
329         default = 3;
330       };
332       ledgerHistory = lib.mkOption {
333         description = ''
334           The number of past ledgers to acquire on server startup and the minimum
335           to maintain while running.
336         '';
337         type = lib.types.either lib.types.int (lib.types.enum ["full"]);
338         default = 1296000; # 1 month
339       };
341       fetchDepth = lib.mkOption {
342         description = ''
343           The number of past ledgers to serve to other peers that request historical
344           ledger data (or "full" for no limit).
345         '';
346         type = lib.types.either lib.types.int (lib.types.enum ["full"]);
347         default = "full";
348       };
350       sntpServers = lib.mkOption {
351         description = ''
352           IP address or domain of NTP servers to use for time synchronization.;
353         '';
354         type = lib.types.listOf lib.types.str;
355         default = [
356           "time.windows.com"
357           "time.apple.com"
358           "time.nist.gov"
359           "pool.ntp.org"
360         ];
361       };
363       logLevel = lib.mkOption {
364         description = "Logging verbosity.";
365         type = lib.types.enum ["debug" "error" "info"];
366         default = "error";
367       };
369       statsd = {
370         enable = lib.mkEnableOption "statsd monitoring for rippled";
372         address = lib.mkOption {
373           description = "The UDP address and port of the listening StatsD server.";
374           default = "127.0.0.1:8125";
375           type = lib.types.str;
376         };
378         prefix = lib.mkOption {
379           description = "A string prepended to each collected metric.";
380           default = "";
381           type = lib.types.str;
382         };
383       };
385       extraConfig = lib.mkOption {
386         default = "";
387         type = lib.types.lines;
388         description = ''
389           Extra lines to be added verbatim to the rippled.cfg configuration file.
390         '';
391       };
393       config = lib.mkOption {
394         internal = true;
395         default = pkgs.writeText "rippled.conf" rippledCfg;
396         defaultText = lib.literalMD "generated config file";
397       };
398     };
399   };
402   ###### implementation
404   config = lib.mkIf cfg.enable {
406     users.users.rippled = {
407         description = "Ripple server user";
408         isSystemUser = true;
409         group = "rippled";
410         home = cfg.databasePath;
411         createHome = true;
412       };
413     users.groups.rippled = {};
415     systemd.services.rippled = {
416       after = [ "network.target" ];
417       wantedBy = [ "multi-user.target" ];
419       serviceConfig = {
420         ExecStart = "${cfg.package}/bin/rippled --fg --conf ${cfg.config}";
421         User = "rippled";
422         Restart = "on-failure";
423         LimitNOFILE=10000;
424       };
425     };
427     environment.systemPackages = [ cfg.package ];
429   };