1 { config, lib, options, pkgs, ... }:
6 cfg = config.services.rippled;
7 opt = options.services.rippled;
9 b2i = val: if val then "1" else "0";
14 ${optionalString (db.compression != null) ("compression=${b2i db.compression}") }
15 ${optionalString (db.onlineDelete != null) ("online_delete=${toString db.onlineDelete}")}
16 ${optionalString (db.advisoryDelete != null) ("advisory_delete=${b2i db.advisoryDelete}")}
22 ${concatMapStringsSep "\n" (n: "port_${n}") (attrNames cfg.ports)}
24 ${concatMapStrings (p: ''
27 port=${toString p.port}
28 protocol=${concatStringsSep "," p.protocol}
29 ${optionalString (p.user != "") "user=${p.user}"}
30 ${optionalString (p.password != "") "user=${p.password}"}
31 admin=${concatStringsSep "," p.admin}
32 ${optionalString (p.ssl.key != null) "ssl_key=${p.ssl.key}"}
33 ${optionalString (p.ssl.cert != null) "ssl_cert=${p.ssl.cert}"}
34 ${optionalString (p.ssl.chain != null) "ssl_chain=${p.ssl.chain}"}
35 '') (attrValues cfg.ports)}
43 ${optionalString (cfg.tempDb != null) ''
45 ${dbCfg cfg.tempDb}''}
47 ${optionalString (cfg.importDb != null) ''
49 ${dbCfg cfg.importDb}''}
52 ${concatStringsSep "\n" cfg.ips}
55 ${concatStringsSep "\n" cfg.ipsFixed}
58 ${concatStringsSep "\n" cfg.validators}
64 ${toString cfg.ledgerHistory}
67 ${toString cfg.fetchDepth}
70 ${toString cfg.validationQuorum}
73 ${concatStringsSep "\n" cfg.sntpServers}
75 ${optionalString cfg.statsd.enable ''
78 address=${cfg.statsd.address}
79 prefix=${cfg.statsd.prefix}
83 { "command": "log_level", "severity": "${cfg.logLevel}" }
86 portOptions = { name, ...}: {
94 default = "127.0.0.1";
95 description = lib.mdDoc "Ip where rippled listens.";
100 description = lib.mdDoc "Port where rippled listens.";
104 protocol = mkOption {
105 description = lib.mdDoc "Protocols expose by rippled.";
106 type = types.listOf (types.enum ["http" "https" "ws" "wss" "peer"]);
110 description = lib.mdDoc "When set, these credentials will be required on HTTP/S requests.";
115 password = mkOption {
116 description = lib.mdDoc "When set, these credentials will be required on HTTP/S requests.";
122 description = lib.mdDoc "A comma-separated list of admin IP addresses.";
123 type = types.listOf types.str;
124 default = ["127.0.0.1"];
129 description = lib.mdDoc ''
130 Specifies the filename holding the SSL key in PEM format.
133 type = types.nullOr types.path;
137 description = lib.mdDoc ''
138 Specifies the path to the SSL certificate file in PEM format.
139 This is not needed if the chain includes it.
142 type = types.nullOr types.path;
146 description = lib.mdDoc ''
147 If you need a certificate chain, specify the path to the
148 certificate chain here. The chain may include the end certificate.
151 type = types.nullOr types.path;
160 description = lib.mdDoc "Rippled database type.";
161 type = types.enum ["rocksdb" "nudb"];
166 description = lib.mdDoc "Location to store the database.";
168 default = cfg.databasePath;
169 defaultText = literalExpression "config.${opt.databasePath}";
172 compression = mkOption {
173 description = lib.mdDoc "Whether to enable snappy compression.";
174 type = types.nullOr types.bool;
178 onlineDelete = mkOption {
179 description = lib.mdDoc "Enable automatic purging of older ledger information.";
180 type = types.nullOr (types.addCheck types.int (v: v > 256));
181 default = cfg.ledgerHistory;
182 defaultText = literalExpression "config.${opt.ledgerHistory}";
185 advisoryDelete = mkOption {
186 description = lib.mdDoc ''
187 If set, then require administrative RPC call "can_delete"
188 to enable online deletion of ledger records.
190 type = types.nullOr types.bool;
194 extraOpts = mkOption {
195 description = lib.mdDoc "Extra database options.";
210 enable = mkEnableOption (lib.mdDoc "rippled");
213 description = lib.mdDoc "Which rippled package to use.";
214 type = types.package;
215 default = pkgs.rippled;
216 defaultText = literalExpression "pkgs.rippled";
220 description = lib.mdDoc "Ports exposed by rippled";
221 type = with types; attrsOf (submodule portOptions);
225 admin = ["127.0.0.1"];
238 protocol = ["ws" "wss"];
244 description = lib.mdDoc "Rippled main database options.";
245 type = with types; nullOr (submodule dbOptions);
259 description = lib.mdDoc "Rippled temporary database options.";
260 type = with types; nullOr (submodule dbOptions);
264 importDb = mkOption {
265 description = lib.mdDoc "Settings for performing a one-time import.";
266 type = with types; nullOr (submodule dbOptions);
270 nodeSize = mkOption {
271 description = lib.mdDoc ''
272 Rippled size of the node you are running.
273 "tiny", "small", "medium", "large", and "huge"
275 type = types.enum ["tiny" "small" "medium" "large" "huge"];
280 description = lib.mdDoc ''
281 List of hostnames or ips where the Ripple protocol is served.
282 For a starter list, you can either copy entries from:
283 https://ripple.com/ripple.txt or if you prefer you can let it
284 default to r.ripple.com 51235
286 A port may optionally be specified after adding a space to the
287 address. By convention, if known, IPs are listed in from most
290 type = types.listOf types.str;
291 default = ["r.ripple.com 51235"];
294 ipsFixed = mkOption {
295 description = lib.mdDoc ''
296 List of IP addresses or hostnames to which rippled should always
297 attempt to maintain peer connections with. This is useful for
298 manually forming private networks, for example to configure a
299 validation server that connects to the Ripple network through a
300 public-facing server, or for building a set of cluster peers.
302 A port may optionally be specified after adding a space to the address
304 type = types.listOf types.str;
308 validators = mkOption {
309 description = lib.mdDoc ''
310 List of nodes to always accept as validators. Nodes are specified by domain
313 type = types.listOf types.str;
315 "n949f75evCHwgyP4fPVgaHqNHxUVN15PsJEZ3B3HnXPcPjcZAoy7 RL1"
316 "n9MD5h24qrQqiyBC8aeqqCWvpiBiYQ3jxSr91uiDvmrkyHRdYLUj RL2"
317 "n9L81uNCaPgtUJfaHh89gmdvXKAmSt5Gdsw2g1iPWaPkAHW5Nm4C RL3"
318 "n9KiYM9CgngLvtRCQHZwgC2gjpdaZcCcbt3VboxiNFcKuwFVujzS RL4"
319 "n9LdgEtkmGB9E2h3K4Vp7iGUaKuq23Zr32ehxiU8FWY7xoxbWTSA RL5"
323 databasePath = mkOption {
324 description = lib.mdDoc ''
325 Path to the ripple database.
328 default = "/var/lib/rippled";
331 validationQuorum = mkOption {
332 description = lib.mdDoc ''
333 The minimum number of trusted validations a ledger must have before
334 the server considers it fully validated.
340 ledgerHistory = mkOption {
341 description = lib.mdDoc ''
342 The number of past ledgers to acquire on server startup and the minimum
343 to maintain while running.
345 type = types.either types.int (types.enum ["full"]);
346 default = 1296000; # 1 month
349 fetchDepth = mkOption {
350 description = lib.mdDoc ''
351 The number of past ledgers to serve to other peers that request historical
352 ledger data (or "full" for no limit).
354 type = types.either types.int (types.enum ["full"]);
358 sntpServers = mkOption {
359 description = lib.mdDoc ''
360 IP address or domain of NTP servers to use for time synchronization.;
362 type = types.listOf types.str;
371 logLevel = mkOption {
372 description = lib.mdDoc "Logging verbosity.";
373 type = types.enum ["debug" "error" "info"];
378 enable = mkEnableOption (lib.mdDoc "statsd monitoring for rippled");
381 description = lib.mdDoc "The UDP address and port of the listening StatsD server.";
382 default = "127.0.0.1:8125";
387 description = lib.mdDoc "A string prepended to each collected metric.";
393 extraConfig = mkOption {
396 description = lib.mdDoc ''
397 Extra lines to be added verbatim to the rippled.cfg configuration file.
403 default = pkgs.writeText "rippled.conf" rippledCfg;
404 defaultText = literalMD "generated config file";
410 ###### implementation
412 config = mkIf cfg.enable {
414 users.users.rippled = {
415 description = "Ripple server user";
418 home = cfg.databasePath;
421 users.groups.rippled = {};
423 systemd.services.rippled = {
424 after = [ "network.target" ];
425 wantedBy = [ "multi-user.target" ];
428 ExecStart = "${cfg.package}/bin/rippled --fg --conf ${cfg.config}";
430 Restart = "on-failure";
435 environment.systemPackages = [ cfg.package ];