···1-# configuration building is commented out until better tested.
2-3{ config, lib, pkgs, ... }:
45with lib;
···7let
8 cfg = config.services.rippled;
910- rippledStateCfgFile = "/var/lib/rippled/rippled.cfg";
0000000001112 rippledCfg = ''
13- [node_db]
14- type=HyperLevelDB
15- path=/var/lib/rippled/db/hyperldb
1617- [debug_logfile]
18- /var/log/rippled/debug.log
00000000001920- ''
21- + optionalString (cfg.peerIp != null) ''
22- [peer_ip]
23- ${cfg.peerIp}
2425- [peer_port]
26- ${toString cfg.peerPort}
2728- ''
29- + cfg.extraConfig;
03031- rippledCfgFile = pkgs.writeText "rippled.cfg" rippledCfg;
32-33-in
3435-{
03637- ###### interface
03839- options = {
04041- services.rippled = {
04243- enable = mkOption {
44- default = false;
45- description = "Whether to enable rippled";
46- };
4748- #
49- # Rippled has a simple configuration file layout that is easy to
50- # build with nix. Many of the options are defined here but are
51- # commented out until the code to append them to the config above
52- # is written and they are tested.
53- #
54- # If you find a yourself implementing more options, please submit a
55- # pull request.
56- #
5758- /*
59- ips = mkOption {
60- default = [ "r.ripple.com 51235" ];
61- example = [ "192.168.0.1" "192.168.0.1 3939" "r.ripple.com 51235" ];
62- description = ''
63- List of hostnames or ips where the Ripple protocol is served.
64- For a starter list, you can either copy entries from:
65- https://ripple.com/ripple.txt or if you prefer you can let it
66- default to r.ripple.com 51235
6768- A port may optionally be specified after adding a space to the
69- address. By convention, if known, IPs are listed in from most
70- to least trusted.
71- '';
72- };
7374- ipsFixed = mkOption {
75- default = null;
76- example = [ "192.168.0.1" "192.168.0.1 3939" "r.ripple.com 51235" ];
77- description = ''
78- List of IP addresses or hostnames to which rippled should always
79- attempt to maintain peer connections with. This is useful for
80- manually forming private networks, for example to configure a
81- validation server that connects to the Ripple network through a
82- public-facing server, or for building a set of cluster peers.
8384- A port may optionally be specified after adding a space to the address
85- '';
00086 };
87- */
8889- peerIp = mkOption {
90- default = null;
91- example = "0.0.0.0";
92- description = ''
93- IP address or domain to bind to allow external connections from peers.
94- Defaults to not binding, which disallows external connections from peers.
95- '';
96 };
9798- peerPort = mkOption {
99- default = 51235;
100- description = ''
101- If peerIp is supplied, corresponding port to bind to for peer connections.
102- '';
103 };
104105- /*
106- peerPortProxy = mkOption {
107- type = types.int;
108- example = 51236;
109- description = ''
110- An optional, additional listening port number for peers. Incoming
111- connections on this port will be required to provide a PROXY Protocol
112- handshake, described in this document (external link):
113114- http://haproxy.1wt.eu/download/1.5/doc/proxy-protocol.txt
0000115116- The PROXY Protocol is a popular method used by elastic load balancing
117- service providers such as Amazon, to identify the true IP address and
118- port number of external incoming connections.
00119120- In addition to enabling this setting, it will also be required to
121- use your provider-specific control panel or administrative web page
122- to configure your server instance to receive PROXY Protocol handshakes,
123- and also to restrict access to your instance to the Elastic Load Balancer.
124- '';
125 };
126127- peerPrivate = mkOption {
128- default = null;
129- example = 0;
130- description = ''
131- 0: Request peers to broadcast your address. Normal outbound peer connections [default]
132- 1: Request peers not broadcast your address. Only connect to configured peers.
133- '';
134- };
135136- peerSslCipherList = mkOption {
137- default = null;
138- example = "ALL:!LOW:!EXP:!MD5:@STRENGTH";
139- description = ''
140- A colon delimited string with the allowed SSL cipher modes for peer. The
141- choices for for ciphers are defined by the OpenSSL API function
142- SSL_CTX_set_cipher_list, documented here (external link):
0143144- http://pic.dhe.ibm.com/infocenter/tpfhelp/current/index.jsp?topic=%2Fcom.ibm.ztpf-ztpfdf.doc_put.cur%2Fgtpc2%2Fcpp_ssl_ctx_set_cipher_list.html
0000000145146- The default setting of "ALL:!LOW:!EXP:!MD5:@STRENGTH", which allows
147- non-authenticated peer connections (they are, however, secure).
148- '';
149 };
0150151- nodeSeed = mkOption {
152- default = null;
153- example = "RASH BUSH MILK LOOK BAD BRIM AVID GAFF BAIT ROT POD LOVE";
154- description = ''
155- This is used for clustering. To force a particular node seed or key, the
156- key can be set here. The format is the same as the validation_seed field.
157- To obtain a validation seed, use the rippled validation_create command.
158- '';
159 };
160161- clusterNodes = mkOption {
162- default = null;
163- example = [ "n9KorY8QtTdRx7TVDpwnG9NvyxsDwHUKUEeDLY3AkiGncVaSXZi5" ];
164- description = ''
165- To extend full trust to other nodes, place their node public keys here.
166- Generally, you should only do this for nodes under common administration.
167- Node public keys start with an 'n'. To give a node a name for identification
168- place a space after the public key and then the name.
169- '';
170 };
171172- sntpServers = mkOption {
00173 default = null;
174- example = [ "time.nist.gov" "pool.ntp.org" ];
175- description = ''
176- IP address or domain of NTP servers to use for time synchronization.
177- '';
178 };
179180- # TODO: websocket options
0000181182- rpcAllowRemote = mkOption {
183- default = false;
184 description = ''
185- false: Allow RPC connections only from 127.0.0.1. [default]
186- true: Allow RPC connections from any IP.
187 '';
00188 };
189190- rpcAdminAllow = mkOption {
191- example = [ "10.0.0.4" ];
192- description = ''
193- List of IP addresses allowed to have admin access.
194- '';
195 };
0196197- rpcAdminUser = mkOption {
198- type = types.str;
199- description = ''
200- As a server, require this as the admin user to be specified. Also, require
201- rpc_admin_user and rpc_admin_password to be checked for RPC admin functions.
202- The request must specify these as the admin_user and admin_password in the
203- request object.
204- '';
205- };
206207- rpcAdminPassword = mkOption {
208- type = types.str;
209- description = ''
210- As a server, require this as the admin pasword to be specified. Also,
211- require rpc_admin_user and rpc_admin_password to be checked for RPC admin
212- functions. The request must specify these as the admin_user and
213- admin_password in the request object.
214- '';
215- };
216217- rpcIp = mkOption {
218- type = types.str;
000000000000000000000000000000000000000000000000000000219 description = ''
220- IP address or domain to bind to allow insecure RPC connections.
221- Defaults to not binding, which disallows RPC connections.
222 '';
00223 };
224225- rpcPort = mkOption {
226- type = types.int;
227- description = ''
228- If rpcIp is supplied, corresponding port to bind to for peer connections.
229- '';
00000000230 };
231232- rpcUser = mkOption {
233- type = types.str;
234 description = ''
235- Require a this user to specified and require rpcPassword to
236- be checked for RPC access via the rpcIp and rpcPort. The user and password
237- must be specified via HTTP's basic authentication method.
238- As a client, supply this to the server via HTTP's basic authentication
239- method.
00240 '';
00241 };
242243- rpcPassword = mkOption {
244- type = types.str;
245 description = ''
246- Require a this password to specified and require rpc_user to
247- be checked for RPC access via the rpcIp and rpcPort. The user and password
248- must be specified via HTTP's basic authentication method.
249- As a client, supply this to the server via HTTP's basic authentication
250- method.
251 '';
00000000252 };
253254- rpcStartup = mkOption {
255- example = [ ''"command" : "log_level"'' ''"partition" : "ripplecalc"'' ''"severity" : "trace"'' ];
256- description = "List of RPC commands to run at startup.";
000257 };
258259- rpcSecure = mkOption {
260- default = false;
261 description = ''
262- false: Server certificates are not provided for RPC clients using SSL [default]
263- true: Client RPC connections wil be provided with SSL certificates.
0000264265- Note that if rpc_secure is enabled, it will also be necessasry to configure the
266- certificate file settings located in rpcSslCert, rpcSslChain, and rpcSslKey
00267 '';
00268 };
269- */
000000000000000000000000000270271 extraConfig = mkOption {
272 default = "";
···275 '';
276 };
2770000278 };
279-280 };
281282···288 { name = "rippled";
289 description = "Ripple server user";
290 uid = config.ids.uids.rippled;
291- home = "/var/lib/rippled";
0292 };
293294 systemd.services.rippled = {
295- path = [ pkgs.rippled ];
296-297 after = [ "network.target" ];
298 wantedBy = [ "multi-user.target" ];
299300 serviceConfig = {
301- ExecStart = "${pkgs.rippled}/bin/rippled --fg -q --conf ${rippledStateCfgFile}";
302- WorkingDirectory = "/var/lib/rippled";
303 };
304 };
305306- networking.firewall.allowedTCPPorts = mkIf (cfg.peerIp != null) [ cfg.peerPort ];
307308- system.activationScripts.rippled = ''
309- mkdir -p /var/{lib,log}/rippled
310- chown -R rippled /var/{lib,log}/rippled
311- ln -sf ${rippledCfgFile} ${rippledStateCfgFile}
312- '';
313 };
314}
···001{ config, lib, pkgs, ... }:
23with lib;
···5let
6 cfg = config.services.rippled;
78+ b2i = val: if val then "1" else "0";
9+10+ dbCfg = db: ''
11+ type=${db.type}
12+ path=${db.path}
13+ ${optionalString (db.compression != null) ("compression=${b2i db.compression}") }
14+ ${optionalString (db.onlineDelete != null) ("online_delete=${toString db.onlineDelete}")}
15+ ${optionalString (db.advisoryDelete != null) ("advisory_delete=${toString db.advisoryDelete}")}
16+ ${db.extraOpts}
17+ '';
1819 rippledCfg = ''
20+ [server]
21+ ${concatMapStringsSep "\n" (n: "port_${n}") (attrNames cfg.ports)}
02223+ ${concatMapStrings (p: ''
24+ [port_${p.name}]
25+ ip=${p.ip}
26+ port=${toString p.port}
27+ protocol=${concatStringsSep "," p.protocol}
28+ ${optionalString (p.user != "") "user=${p.user}"}
29+ ${optionalString (p.password != "") "user=${p.password}"}
30+ admin=${if p.admin then "allow" else "no"}
31+ ${optionalString (p.ssl.key != null) "ssl_key=${p.ssl.key}"}
32+ ${optionalString (p.ssl.cert != null) "ssl_cert=${p.ssl.cert}"}
33+ ${optionalString (p.ssl.chain != null) "ssl_chain=${p.ssl.chain}"}
34+ '') (attrValues cfg.ports)}
3536+ [database_path]
37+ ${cfg.databasePath}
003839+ [node_db]
40+ ${dbCfg cfg.nodeDb}
4142+ ${optionalString (cfg.tempDb != null) ''
43+ [temp_db]
44+ ${dbCfg cfg.tempDb}''}
4546+ ${optionalString (cfg.importDb != null) ''
47+ [import_db]
48+ ${dbCfg cfg.importDb}''}
4950+ [ips]
51+ ${concatStringsSep "\n" cfg.ips}
5253+ [ips_fixed]
54+ ${concatStringsSep "\n" cfg.ipsFixed}
5556+ [validators]
57+ ${concatStringsSep "\n" cfg.validators}
5859+ [node_size]
60+ ${cfg.nodeSize}
6162+ [ledger_history]
63+ ${toString cfg.ledgerHistory}
006465+ [fetch_depth]
66+ ${toString cfg.fetchDepth}
00000006768+ [validation_quorum]
69+ ${toString cfg.validationQuorum}
00000007071+ [sntp_servers]
72+ ${concatStringsSep "\n" cfg.sntpServers}
0007374+ [rpc_startup]
75+ { "command": "log_level", "severity": "${cfg.logLevel}" }
76+ '' + cfg.extraConfig;
0000007778+ portOptions = { name, ...}: {
79+ options = {
80+ name = mkOption {
81+ internal = true;
82+ default = name;
83 };
08485+ ip = mkOption {
86+ default = "127.0.0.1";
87+ description = "Ip where rippled listens.";
88+ type = types.str;
00089 };
9091+ port = mkOption {
92+ description = "Port where rippled listens.";
93+ type = types.int;
0094 };
9596+ protocol = mkOption {
97+ description = "Protocols expose by rippled.";
98+ type = types.listOf (types.enum ["http" "https" "ws" "wss" "peer"]);
99+ };
0000100101+ user = mkOption {
102+ description = "When set, these credentials will be required on HTTP/S requests.";
103+ type = types.str;
104+ default = "";
105+ };
106107+ password = mkOption {
108+ description = "When set, these credentials will be required on HTTP/S requests.";
109+ type = types.str;
110+ default = "";
111+ };
112113+ admin = mkOption {
114+ description = "Controls whether or not administrative commands are allowed.";
115+ type = types.bool;
116+ default = false;
0117 };
118119+ ssl = {
120+ key = mkOption {
121+ description = ''
122+ Specifies the filename holding the SSL key in PEM format.
123+ '';
124+ default = null;
125+ type = types.nullOr types.path;
126+ };
127128+ cert = mkOption {
129+ description = ''
130+ Specifies the path to the SSL certificate file in PEM format.
131+ This is not needed if the chain includes it.
132+ '';
133+ default = null;
134+ type = types.nullOr types.path;
135+ };
136137+ chain = mkOption {
138+ description = ''
139+ If you need a certificate chain, specify the path to the
140+ certificate chain here. The chain may include the end certificate.
141+ '';
142+ default = null;
143+ type = types.nullOr types.path;
144+ };
145146+ };
00147 };
148+ };
149150+ dbOptions = {
151+ type = mkOption {
152+ description = "Rippled database type.";
153+ type = types.enum ["rocksdb" "nudb" "sqlite"];
154+ default = "rocksdb";
000155 };
156157+ path = mkOption {
158+ description = "Location to store the database.";
159+ type = types.path;
160+ default = cfg.databasePath;
00000161 };
162163+ compression = mkOption {
164+ description = "Whether to enable snappy compression.";
165+ type = types.nullOr types.bool;
166 default = null;
0000167 };
168169+ onlineDelete = mkOption {
170+ description = "Enable automatic purging of older ledger information.";
171+ type = types.addCheck (types.nullOr types.int) (v: v > 256);
172+ default = cfg.ledgerHistory;
173+ };
174175+ advisoryDelete = mkOption {
0176 description = ''
177+ If set, then require administrative RPC call "can_delete"
178+ to enable online deletion of ledger records.
179 '';
180+ type = types.nullOr types.bool;
181+ default = null;
182 };
183184+ extraOpts = mkOption {
185+ description = "Extra database options.";
186+ type = types.lines;
187+ default = "";
0188 };
189+ };
190191+in
192+193+{
194+195+ ###### interface
196+197+ options = {
198+ services.rippled = {
199+ enable = mkEnableOption "Whether to enable rippled";
200201+ package = mkOption {
202+ description = "Which rippled package to use.";
203+ type = types.package;
204+ default = pkgs.rippled;
205+ };
0000206207+ ports = mkOption {
208+ description = "Ports exposed by rippled";
209+ type = types.attrsOf types.optionSet;
210+ options = [portOptions];
211+ default = {
212+ rpc = {
213+ port = 5005;
214+ admin = true;
215+ protocol = ["http"];
216+ };
217+218+ peer = {
219+ port = 51235;
220+ ip = "0.0.0.0";
221+ protocol = ["peer"];
222+ };
223+224+ ws_public = {
225+ port = 5006;
226+ ip = "0.0.0.0";
227+ protocol = ["ws" "wss"];
228+ };
229+ };
230+ };
231+232+ nodeDb = mkOption {
233+ description = "Rippled main database options.";
234+ type = types.nullOr types.optionSet;
235+ options = [dbOptions];
236+ default = {
237+ type = "rocksdb";
238+ extraOpts = ''
239+ open_files=2000
240+ filter_bits=12
241+ cache_mb=256
242+ file_size_pb=8
243+ file_size_mult=2;
244+ '';
245+ };
246+ };
247+248+ tempDb = mkOption {
249+ description = "Rippled temporary database options.";
250+ type = types.nullOr types.optionSet;
251+ options = [dbOptions];
252+ default = null;
253+ };
254+255+ importDb = mkOption {
256+ description = "Settings for performing a one-time import.";
257+ type = types.nullOr types.optionSet;
258+ options = [dbOptions];
259+ default = null;
260+ };
261+262+ nodeSize = mkOption {
263 description = ''
264+ Rippled size of the node you are running.
265+ "tiny", "small", "medium", "large", and "huge"
266 '';
267+ type = types.enum ["tiny" "small" "medium" "large" "huge"];
268+ default = "small";
269 };
270271+ ips = 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
277+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 = types.listOf types.str;
283+ default = ["r.ripple.com 51235"];
284 };
285286+ ipsFixed = mkOption {
0287 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.
293+294+ A port may optionally be specified after adding a space to the address
295 '';
296+ type = types.listOf types.str;
297+ default = [];
298 };
299300+ validators = mkOption {
0301 description = ''
302+ List of nodes to always accept as validators. Nodes are specified by domain
303+ or public key.
000304 '';
305+ type = types.listOf types.str;
306+ default = [
307+ "n949f75evCHwgyP4fPVgaHqNHxUVN15PsJEZ3B3HnXPcPjcZAoy7 RL1"
308+ "n9MD5h24qrQqiyBC8aeqqCWvpiBiYQ3jxSr91uiDvmrkyHRdYLUj RL2"
309+ "n9L81uNCaPgtUJfaHh89gmdvXKAmSt5Gdsw2g1iPWaPkAHW5Nm4C RL3"
310+ "n9KiYM9CgngLvtRCQHZwgC2gjpdaZcCcbt3VboxiNFcKuwFVujzS RL4"
311+ "n9LdgEtkmGB9E2h3K4Vp7iGUaKuq23Zr32ehxiU8FWY7xoxbWTSA RL5"
312+ ];
313 };
314315+ databasePath = mkOption {
316+ description = ''
317+ Path to the ripple database.
318+ '';
319+ type = types.path;
320+ default = "/var/lib/rippled/db";
321 };
322323+ validationQuorum = mkOption {
0324 description = ''
325+ The minimum number of trusted validations a ledger must have before
326+ the server considers it fully validated.
327+ '';
328+ type = types.int;
329+ default = 3;
330+ };
331332+ ledgerHistory = 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 = types.either types.int (types.enum ["full"]);
338+ default = 1296000; # 1 month
339 };
340+341+ fetchDepth = 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 = types.either types.int (types.enum ["full"]);
347+ default = "full";
348+ };
349+350+ sntpServers = mkOption {
351+ description = ''
352+ IP address or domain of NTP servers to use for time synchronization.;
353+ '';
354+ type = types.listOf types.str;
355+ default = [
356+ "time.windows.com"
357+ "time.apple.com"
358+ "time.nist.gov"
359+ "pool.ntp.org"
360+ ];
361+ };
362+363+ logLevel = mkOption {
364+ description = "Logging verbosity.";
365+ type = types.enum ["debug" "error" "info"];
366+ default = "error";
367+ };
368369 extraConfig = mkOption {
370 default = "";
···373 '';
374 };
375376+ config = mkOption {
377+ internal = true;
378+ default = pkgs.writeText "rippled.conf" rippledCfg;
379+ };
380 };
0381 };
382383···389 { name = "rippled";
390 description = "Ripple server user";
391 uid = config.ids.uids.rippled;
392+ home = cfg.databasePath;
393+ createHome = true;
394 };
395396 systemd.services.rippled = {
00397 after = [ "network.target" ];
398 wantedBy = [ "multi-user.target" ];
399400 serviceConfig = {
401+ ExecStart = "${cfg.package}/bin/rippled --fg --conf ${cfg.config}";
402+ User = "rippled";
403 };
404 };
405406+ environment.systemPackages = [ cfg.package ];
40700000408 };
409}