···11-# configuration building is commented out until better tested.
22-31{ config, lib, pkgs, ... }:
4253with lib;
···75let
86 cfg = config.services.rippled;
971010- rippledStateCfgFile = "/var/lib/rippled/rippled.cfg";
88+ b2i = val: if val then "1" else "0";
99+1010+ dbCfg = db: ''
1111+ type=${db.type}
1212+ path=${db.path}
1313+ ${optionalString (db.compression != null) ("compression=${b2i db.compression}") }
1414+ ${optionalString (db.onlineDelete != null) ("online_delete=${toString db.onlineDelete}")}
1515+ ${optionalString (db.advisoryDelete != null) ("advisory_delete=${toString db.advisoryDelete}")}
1616+ ${db.extraOpts}
1717+ '';
11181219 rippledCfg = ''
1313- [node_db]
1414- type=HyperLevelDB
1515- path=/var/lib/rippled/db/hyperldb
2020+ [server]
2121+ ${concatMapStringsSep "\n" (n: "port_${n}") (attrNames cfg.ports)}
16221717- [debug_logfile]
1818- /var/log/rippled/debug.log
2323+ ${concatMapStrings (p: ''
2424+ [port_${p.name}]
2525+ ip=${p.ip}
2626+ port=${toString p.port}
2727+ protocol=${concatStringsSep "," p.protocol}
2828+ ${optionalString (p.user != "") "user=${p.user}"}
2929+ ${optionalString (p.password != "") "user=${p.password}"}
3030+ admin=${if p.admin then "allow" else "no"}
3131+ ${optionalString (p.ssl.key != null) "ssl_key=${p.ssl.key}"}
3232+ ${optionalString (p.ssl.cert != null) "ssl_cert=${p.ssl.cert}"}
3333+ ${optionalString (p.ssl.chain != null) "ssl_chain=${p.ssl.chain}"}
3434+ '') (attrValues cfg.ports)}
19352020- ''
2121- + optionalString (cfg.peerIp != null) ''
2222- [peer_ip]
2323- ${cfg.peerIp}
3636+ [database_path]
3737+ ${cfg.databasePath}
24382525- [peer_port]
2626- ${toString cfg.peerPort}
3939+ [node_db]
4040+ ${dbCfg cfg.nodeDb}
27412828- ''
2929- + cfg.extraConfig;
4242+ ${optionalString (cfg.tempDb != null) ''
4343+ [temp_db]
4444+ ${dbCfg cfg.tempDb}''}
30453131- rippledCfgFile = pkgs.writeText "rippled.cfg" rippledCfg;
3232-3333-in
4646+ ${optionalString (cfg.importDb != null) ''
4747+ [import_db]
4848+ ${dbCfg cfg.importDb}''}
34493535-{
5050+ [ips]
5151+ ${concatStringsSep "\n" cfg.ips}
36523737- ###### interface
5353+ [ips_fixed]
5454+ ${concatStringsSep "\n" cfg.ipsFixed}
38553939- options = {
5656+ [validators]
5757+ ${concatStringsSep "\n" cfg.validators}
40584141- services.rippled = {
5959+ [node_size]
6060+ ${cfg.nodeSize}
42614343- enable = mkOption {
4444- default = false;
4545- description = "Whether to enable rippled";
4646- };
6262+ [ledger_history]
6363+ ${toString cfg.ledgerHistory}
47644848- #
4949- # Rippled has a simple configuration file layout that is easy to
5050- # build with nix. Many of the options are defined here but are
5151- # commented out until the code to append them to the config above
5252- # is written and they are tested.
5353- #
5454- # If you find a yourself implementing more options, please submit a
5555- # pull request.
5656- #
6565+ [fetch_depth]
6666+ ${toString cfg.fetchDepth}
57675858- /*
5959- ips = mkOption {
6060- default = [ "r.ripple.com 51235" ];
6161- example = [ "192.168.0.1" "192.168.0.1 3939" "r.ripple.com 51235" ];
6262- description = ''
6363- List of hostnames or ips where the Ripple protocol is served.
6464- For a starter list, you can either copy entries from:
6565- https://ripple.com/ripple.txt or if you prefer you can let it
6666- default to r.ripple.com 51235
6868+ [validation_quorum]
6969+ ${toString cfg.validationQuorum}
67706868- A port may optionally be specified after adding a space to the
6969- address. By convention, if known, IPs are listed in from most
7070- to least trusted.
7171- '';
7272- };
7171+ [sntp_servers]
7272+ ${concatStringsSep "\n" cfg.sntpServers}
73737474- ipsFixed = mkOption {
7575- default = null;
7676- example = [ "192.168.0.1" "192.168.0.1 3939" "r.ripple.com 51235" ];
7777- description = ''
7878- List of IP addresses or hostnames to which rippled should always
7979- attempt to maintain peer connections with. This is useful for
8080- manually forming private networks, for example to configure a
8181- validation server that connects to the Ripple network through a
8282- public-facing server, or for building a set of cluster peers.
7474+ [rpc_startup]
7575+ { "command": "log_level", "severity": "${cfg.logLevel}" }
7676+ '' + cfg.extraConfig;
83778484- A port may optionally be specified after adding a space to the address
8585- '';
7878+ portOptions = { name, ...}: {
7979+ options = {
8080+ name = mkOption {
8181+ internal = true;
8282+ default = name;
8683 };
8787- */
88848989- peerIp = mkOption {
9090- default = null;
9191- example = "0.0.0.0";
9292- description = ''
9393- IP address or domain to bind to allow external connections from peers.
9494- Defaults to not binding, which disallows external connections from peers.
9595- '';
8585+ ip = mkOption {
8686+ default = "127.0.0.1";
8787+ description = "Ip where rippled listens.";
8888+ type = types.str;
9689 };
97909898- peerPort = mkOption {
9999- default = 51235;
100100- description = ''
101101- If peerIp is supplied, corresponding port to bind to for peer connections.
102102- '';
9191+ port = mkOption {
9292+ description = "Port where rippled listens.";
9393+ type = types.int;
10394 };
10495105105- /*
106106- peerPortProxy = mkOption {
107107- type = types.int;
108108- example = 51236;
109109- description = ''
110110- An optional, additional listening port number for peers. Incoming
111111- connections on this port will be required to provide a PROXY Protocol
112112- handshake, described in this document (external link):
9696+ protocol = mkOption {
9797+ description = "Protocols expose by rippled.";
9898+ type = types.listOf (types.enum ["http" "https" "ws" "wss" "peer"]);
9999+ };
113100114114- http://haproxy.1wt.eu/download/1.5/doc/proxy-protocol.txt
101101+ user = mkOption {
102102+ description = "When set, these credentials will be required on HTTP/S requests.";
103103+ type = types.str;
104104+ default = "";
105105+ };
115106116116- The PROXY Protocol is a popular method used by elastic load balancing
117117- service providers such as Amazon, to identify the true IP address and
118118- port number of external incoming connections.
107107+ password = mkOption {
108108+ description = "When set, these credentials will be required on HTTP/S requests.";
109109+ type = types.str;
110110+ default = "";
111111+ };
119112120120- In addition to enabling this setting, it will also be required to
121121- use your provider-specific control panel or administrative web page
122122- to configure your server instance to receive PROXY Protocol handshakes,
123123- and also to restrict access to your instance to the Elastic Load Balancer.
124124- '';
113113+ admin = mkOption {
114114+ description = "Controls whether or not administrative commands are allowed.";
115115+ type = types.bool;
116116+ default = false;
125117 };
126118127127- peerPrivate = mkOption {
128128- default = null;
129129- example = 0;
130130- description = ''
131131- 0: Request peers to broadcast your address. Normal outbound peer connections [default]
132132- 1: Request peers not broadcast your address. Only connect to configured peers.
133133- '';
134134- };
119119+ ssl = {
120120+ key = mkOption {
121121+ description = ''
122122+ Specifies the filename holding the SSL key in PEM format.
123123+ '';
124124+ default = null;
125125+ type = types.nullOr types.path;
126126+ };
135127136136- peerSslCipherList = mkOption {
137137- default = null;
138138- example = "ALL:!LOW:!EXP:!MD5:@STRENGTH";
139139- description = ''
140140- A colon delimited string with the allowed SSL cipher modes for peer. The
141141- choices for for ciphers are defined by the OpenSSL API function
142142- SSL_CTX_set_cipher_list, documented here (external link):
128128+ cert = mkOption {
129129+ description = ''
130130+ Specifies the path to the SSL certificate file in PEM format.
131131+ This is not needed if the chain includes it.
132132+ '';
133133+ default = null;
134134+ type = types.nullOr types.path;
135135+ };
143136144144- 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
137137+ chain = mkOption {
138138+ description = ''
139139+ If you need a certificate chain, specify the path to the
140140+ certificate chain here. The chain may include the end certificate.
141141+ '';
142142+ default = null;
143143+ type = types.nullOr types.path;
144144+ };
145145146146- The default setting of "ALL:!LOW:!EXP:!MD5:@STRENGTH", which allows
147147- non-authenticated peer connections (they are, however, secure).
148148- '';
146146+ };
149147 };
148148+ };
150149151151- nodeSeed = mkOption {
152152- default = null;
153153- example = "RASH BUSH MILK LOOK BAD BRIM AVID GAFF BAIT ROT POD LOVE";
154154- description = ''
155155- This is used for clustering. To force a particular node seed or key, the
156156- key can be set here. The format is the same as the validation_seed field.
157157- To obtain a validation seed, use the rippled validation_create command.
158158- '';
150150+ dbOptions = {
151151+ type = mkOption {
152152+ description = "Rippled database type.";
153153+ type = types.enum ["rocksdb" "nudb" "sqlite"];
154154+ default = "rocksdb";
159155 };
160156161161- clusterNodes = mkOption {
162162- default = null;
163163- example = [ "n9KorY8QtTdRx7TVDpwnG9NvyxsDwHUKUEeDLY3AkiGncVaSXZi5" ];
164164- description = ''
165165- To extend full trust to other nodes, place their node public keys here.
166166- Generally, you should only do this for nodes under common administration.
167167- Node public keys start with an 'n'. To give a node a name for identification
168168- place a space after the public key and then the name.
169169- '';
157157+ path = mkOption {
158158+ description = "Location to store the database.";
159159+ type = types.path;
160160+ default = cfg.databasePath;
170161 };
171162172172- sntpServers = mkOption {
163163+ compression = mkOption {
164164+ description = "Whether to enable snappy compression.";
165165+ type = types.nullOr types.bool;
173166 default = null;
174174- example = [ "time.nist.gov" "pool.ntp.org" ];
175175- description = ''
176176- IP address or domain of NTP servers to use for time synchronization.
177177- '';
178167 };
179168180180- # TODO: websocket options
169169+ onlineDelete = mkOption {
170170+ description = "Enable automatic purging of older ledger information.";
171171+ type = types.addCheck (types.nullOr types.int) (v: v > 256);
172172+ default = cfg.ledgerHistory;
173173+ };
181174182182- rpcAllowRemote = mkOption {
183183- default = false;
175175+ advisoryDelete = mkOption {
184176 description = ''
185185- false: Allow RPC connections only from 127.0.0.1. [default]
186186- true: Allow RPC connections from any IP.
177177+ If set, then require administrative RPC call "can_delete"
178178+ to enable online deletion of ledger records.
187179 '';
180180+ type = types.nullOr types.bool;
181181+ default = null;
188182 };
189183190190- rpcAdminAllow = mkOption {
191191- example = [ "10.0.0.4" ];
192192- description = ''
193193- List of IP addresses allowed to have admin access.
194194- '';
184184+ extraOpts = mkOption {
185185+ description = "Extra database options.";
186186+ type = types.lines;
187187+ default = "";
195188 };
189189+ };
196190197197- rpcAdminUser = mkOption {
198198- type = types.str;
199199- description = ''
200200- As a server, require this as the admin user to be specified. Also, require
201201- rpc_admin_user and rpc_admin_password to be checked for RPC admin functions.
202202- The request must specify these as the admin_user and admin_password in the
203203- request object.
204204- '';
205205- };
191191+in
192192+193193+{
194194+195195+ ###### interface
196196+197197+ options = {
198198+ services.rippled = {
199199+ enable = mkEnableOption "Whether to enable rippled";
206200207207- rpcAdminPassword = mkOption {
208208- type = types.str;
209209- description = ''
210210- As a server, require this as the admin pasword to be specified. Also,
211211- require rpc_admin_user and rpc_admin_password to be checked for RPC admin
212212- functions. The request must specify these as the admin_user and
213213- admin_password in the request object.
214214- '';
215215- };
201201+ package = mkOption {
202202+ description = "Which rippled package to use.";
203203+ type = types.package;
204204+ default = pkgs.rippled;
205205+ };
216206217217- rpcIp = mkOption {
218218- type = types.str;
207207+ ports = mkOption {
208208+ description = "Ports exposed by rippled";
209209+ type = types.attrsOf types.optionSet;
210210+ options = [portOptions];
211211+ default = {
212212+ rpc = {
213213+ port = 5005;
214214+ admin = true;
215215+ protocol = ["http"];
216216+ };
217217+218218+ peer = {
219219+ port = 51235;
220220+ ip = "0.0.0.0";
221221+ protocol = ["peer"];
222222+ };
223223+224224+ ws_public = {
225225+ port = 5006;
226226+ ip = "0.0.0.0";
227227+ protocol = ["ws" "wss"];
228228+ };
229229+ };
230230+ };
231231+232232+ nodeDb = mkOption {
233233+ description = "Rippled main database options.";
234234+ type = types.nullOr types.optionSet;
235235+ options = [dbOptions];
236236+ default = {
237237+ type = "rocksdb";
238238+ extraOpts = ''
239239+ open_files=2000
240240+ filter_bits=12
241241+ cache_mb=256
242242+ file_size_pb=8
243243+ file_size_mult=2;
244244+ '';
245245+ };
246246+ };
247247+248248+ tempDb = mkOption {
249249+ description = "Rippled temporary database options.";
250250+ type = types.nullOr types.optionSet;
251251+ options = [dbOptions];
252252+ default = null;
253253+ };
254254+255255+ importDb = mkOption {
256256+ description = "Settings for performing a one-time import.";
257257+ type = types.nullOr types.optionSet;
258258+ options = [dbOptions];
259259+ default = null;
260260+ };
261261+262262+ nodeSize = mkOption {
219263 description = ''
220220- IP address or domain to bind to allow insecure RPC connections.
221221- Defaults to not binding, which disallows RPC connections.
264264+ Rippled size of the node you are running.
265265+ "tiny", "small", "medium", "large", and "huge"
222266 '';
267267+ type = types.enum ["tiny" "small" "medium" "large" "huge"];
268268+ default = "small";
223269 };
224270225225- rpcPort = mkOption {
226226- type = types.int;
227227- description = ''
228228- If rpcIp is supplied, corresponding port to bind to for peer connections.
229229- '';
271271+ ips = mkOption {
272272+ description = ''
273273+ List of hostnames or ips where the Ripple protocol is served.
274274+ For a starter list, you can either copy entries from:
275275+ https://ripple.com/ripple.txt or if you prefer you can let it
276276+ default to r.ripple.com 51235
277277+278278+ A port may optionally be specified after adding a space to the
279279+ address. By convention, if known, IPs are listed in from most
280280+ to least trusted.
281281+ '';
282282+ type = types.listOf types.str;
283283+ default = ["r.ripple.com 51235"];
230284 };
231285232232- rpcUser = mkOption {
233233- type = types.str;
286286+ ipsFixed = mkOption {
234287 description = ''
235235- Require a this user to specified and require rpcPassword to
236236- be checked for RPC access via the rpcIp and rpcPort. The user and password
237237- must be specified via HTTP's basic authentication method.
238238- As a client, supply this to the server via HTTP's basic authentication
239239- method.
288288+ List of IP addresses or hostnames to which rippled should always
289289+ attempt to maintain peer connections with. This is useful for
290290+ manually forming private networks, for example to configure a
291291+ validation server that connects to the Ripple network through a
292292+ public-facing server, or for building a set of cluster peers.
293293+294294+ A port may optionally be specified after adding a space to the address
240295 '';
296296+ type = types.listOf types.str;
297297+ default = [];
241298 };
242299243243- rpcPassword = mkOption {
244244- type = types.str;
300300+ validators = mkOption {
245301 description = ''
246246- Require a this password to specified and require rpc_user to
247247- be checked for RPC access via the rpcIp and rpcPort. The user and password
248248- must be specified via HTTP's basic authentication method.
249249- As a client, supply this to the server via HTTP's basic authentication
250250- method.
302302+ List of nodes to always accept as validators. Nodes are specified by domain
303303+ or public key.
251304 '';
305305+ type = types.listOf types.str;
306306+ default = [
307307+ "n949f75evCHwgyP4fPVgaHqNHxUVN15PsJEZ3B3HnXPcPjcZAoy7 RL1"
308308+ "n9MD5h24qrQqiyBC8aeqqCWvpiBiYQ3jxSr91uiDvmrkyHRdYLUj RL2"
309309+ "n9L81uNCaPgtUJfaHh89gmdvXKAmSt5Gdsw2g1iPWaPkAHW5Nm4C RL3"
310310+ "n9KiYM9CgngLvtRCQHZwgC2gjpdaZcCcbt3VboxiNFcKuwFVujzS RL4"
311311+ "n9LdgEtkmGB9E2h3K4Vp7iGUaKuq23Zr32ehxiU8FWY7xoxbWTSA RL5"
312312+ ];
252313 };
253314254254- rpcStartup = mkOption {
255255- example = [ ''"command" : "log_level"'' ''"partition" : "ripplecalc"'' ''"severity" : "trace"'' ];
256256- description = "List of RPC commands to run at startup.";
315315+ databasePath = mkOption {
316316+ description = ''
317317+ Path to the ripple database.
318318+ '';
319319+ type = types.path;
320320+ default = "/var/lib/rippled/db";
257321 };
258322259259- rpcSecure = mkOption {
260260- default = false;
323323+ validationQuorum = mkOption {
261324 description = ''
262262- false: Server certificates are not provided for RPC clients using SSL [default]
263263- true: Client RPC connections wil be provided with SSL certificates.
325325+ The minimum number of trusted validations a ledger must have before
326326+ the server considers it fully validated.
327327+ '';
328328+ type = types.int;
329329+ default = 3;
330330+ };
264331265265- Note that if rpc_secure is enabled, it will also be necessasry to configure the
266266- certificate file settings located in rpcSslCert, rpcSslChain, and rpcSslKey
332332+ ledgerHistory = mkOption {
333333+ description = ''
334334+ The number of past ledgers to acquire on server startup and the minimum
335335+ to maintain while running.
267336 '';
337337+ type = types.either types.int (types.enum ["full"]);
338338+ default = 1296000; # 1 month
268339 };
269269- */
340340+341341+ fetchDepth = mkOption {
342342+ description = ''
343343+ The number of past ledgers to serve to other peers that request historical
344344+ ledger data (or "full" for no limit).
345345+ '';
346346+ type = types.either types.int (types.enum ["full"]);
347347+ default = "full";
348348+ };
349349+350350+ sntpServers = mkOption {
351351+ description = ''
352352+ IP address or domain of NTP servers to use for time synchronization.;
353353+ '';
354354+ type = types.listOf types.str;
355355+ default = [
356356+ "time.windows.com"
357357+ "time.apple.com"
358358+ "time.nist.gov"
359359+ "pool.ntp.org"
360360+ ];
361361+ };
362362+363363+ logLevel = mkOption {
364364+ description = "Logging verbosity.";
365365+ type = types.enum ["debug" "error" "info"];
366366+ default = "error";
367367+ };
270368271369 extraConfig = mkOption {
272370 default = "";
···275373 '';
276374 };
277375376376+ config = mkOption {
377377+ internal = true;
378378+ default = pkgs.writeText "rippled.conf" rippledCfg;
379379+ };
278380 };
279279-280381 };
281382282383···288389 { name = "rippled";
289390 description = "Ripple server user";
290391 uid = config.ids.uids.rippled;
291291- home = "/var/lib/rippled";
392392+ home = cfg.databasePath;
393393+ createHome = true;
292394 };
293395294396 systemd.services.rippled = {
295295- path = [ pkgs.rippled ];
296296-297397 after = [ "network.target" ];
298398 wantedBy = [ "multi-user.target" ];
299399300400 serviceConfig = {
301301- ExecStart = "${pkgs.rippled}/bin/rippled --fg -q --conf ${rippledStateCfgFile}";
302302- WorkingDirectory = "/var/lib/rippled";
401401+ ExecStart = "${cfg.package}/bin/rippled --fg --conf ${cfg.config}";
402402+ User = "rippled";
303403 };
304404 };
305405306306- networking.firewall.allowedTCPPorts = mkIf (cfg.peerIp != null) [ cfg.peerPort ];
406406+ environment.systemPackages = [ cfg.package ];
307407308308- system.activationScripts.rippled = ''
309309- mkdir -p /var/{lib,log}/rippled
310310- chown -R rippled /var/{lib,log}/rippled
311311- ln -sf ${rippledCfgFile} ${rippledStateCfgFile}
312312- '';
313408 };
314409}