Merge pull request #6917 from offlinehacker/rippled/module/awesome

rippled: expose more options, make compatible with new rippled version

+300 -205
+300 -205
nixos/modules/services/misc/rippled.nix
··· 1 - # configuration building is commented out until better tested. 2 - 3 1 { config, lib, pkgs, ... }: 4 2 5 3 with lib; ··· 7 5 let 8 6 cfg = config.services.rippled; 9 7 10 - rippledStateCfgFile = "/var/lib/rippled/rippled.cfg"; 8 + 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 + ''; 11 18 12 19 rippledCfg = '' 13 - [node_db] 14 - type=HyperLevelDB 15 - path=/var/lib/rippled/db/hyperldb 20 + [server] 21 + ${concatMapStringsSep "\n" (n: "port_${n}") (attrNames cfg.ports)} 16 22 17 - [debug_logfile] 18 - /var/log/rippled/debug.log 23 + ${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)} 19 35 20 - '' 21 - + optionalString (cfg.peerIp != null) '' 22 - [peer_ip] 23 - ${cfg.peerIp} 36 + [database_path] 37 + ${cfg.databasePath} 24 38 25 - [peer_port] 26 - ${toString cfg.peerPort} 39 + [node_db] 40 + ${dbCfg cfg.nodeDb} 27 41 28 - '' 29 - + cfg.extraConfig; 42 + ${optionalString (cfg.tempDb != null) '' 43 + [temp_db] 44 + ${dbCfg cfg.tempDb}''} 30 45 31 - rippledCfgFile = pkgs.writeText "rippled.cfg" rippledCfg; 32 - 33 - in 46 + ${optionalString (cfg.importDb != null) '' 47 + [import_db] 48 + ${dbCfg cfg.importDb}''} 34 49 35 - { 50 + [ips] 51 + ${concatStringsSep "\n" cfg.ips} 36 52 37 - ###### interface 53 + [ips_fixed] 54 + ${concatStringsSep "\n" cfg.ipsFixed} 38 55 39 - options = { 56 + [validators] 57 + ${concatStringsSep "\n" cfg.validators} 40 58 41 - services.rippled = { 59 + [node_size] 60 + ${cfg.nodeSize} 42 61 43 - enable = mkOption { 44 - default = false; 45 - description = "Whether to enable rippled"; 46 - }; 62 + [ledger_history] 63 + ${toString cfg.ledgerHistory} 47 64 48 - # 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 - # 65 + [fetch_depth] 66 + ${toString cfg.fetchDepth} 57 67 58 - /* 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 68 + [validation_quorum] 69 + ${toString cfg.validationQuorum} 67 70 68 - 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 - }; 71 + [sntp_servers] 72 + ${concatStringsSep "\n" cfg.sntpServers} 73 73 74 - 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. 74 + [rpc_startup] 75 + { "command": "log_level", "severity": "${cfg.logLevel}" } 76 + '' + cfg.extraConfig; 83 77 84 - A port may optionally be specified after adding a space to the address 85 - ''; 78 + portOptions = { name, ...}: { 79 + options = { 80 + name = mkOption { 81 + internal = true; 82 + default = name; 86 83 }; 87 - */ 88 84 89 - 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 - ''; 85 + ip = mkOption { 86 + default = "127.0.0.1"; 87 + description = "Ip where rippled listens."; 88 + type = types.str; 96 89 }; 97 90 98 - peerPort = mkOption { 99 - default = 51235; 100 - description = '' 101 - If peerIp is supplied, corresponding port to bind to for peer connections. 102 - ''; 91 + port = mkOption { 92 + description = "Port where rippled listens."; 93 + type = types.int; 103 94 }; 104 95 105 - /* 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): 96 + protocol = mkOption { 97 + description = "Protocols expose by rippled."; 98 + type = types.listOf (types.enum ["http" "https" "ws" "wss" "peer"]); 99 + }; 113 100 114 - http://haproxy.1wt.eu/download/1.5/doc/proxy-protocol.txt 101 + user = mkOption { 102 + description = "When set, these credentials will be required on HTTP/S requests."; 103 + type = types.str; 104 + default = ""; 105 + }; 115 106 116 - 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. 107 + password = mkOption { 108 + description = "When set, these credentials will be required on HTTP/S requests."; 109 + type = types.str; 110 + default = ""; 111 + }; 119 112 120 - 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 - ''; 113 + admin = mkOption { 114 + description = "Controls whether or not administrative commands are allowed."; 115 + type = types.bool; 116 + default = false; 125 117 }; 126 118 127 - 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 - }; 119 + 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 + }; 135 127 136 - 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): 128 + 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 + }; 143 136 144 - 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 137 + 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 + }; 145 145 146 - The default setting of "ALL:!LOW:!EXP:!MD5:@STRENGTH", which allows 147 - non-authenticated peer connections (they are, however, secure). 148 - ''; 146 + }; 149 147 }; 148 + }; 150 149 151 - 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 - ''; 150 + dbOptions = { 151 + type = mkOption { 152 + description = "Rippled database type."; 153 + type = types.enum ["rocksdb" "nudb" "sqlite"]; 154 + default = "rocksdb"; 159 155 }; 160 156 161 - 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 - ''; 157 + path = mkOption { 158 + description = "Location to store the database."; 159 + type = types.path; 160 + default = cfg.databasePath; 170 161 }; 171 162 172 - sntpServers = mkOption { 163 + compression = mkOption { 164 + description = "Whether to enable snappy compression."; 165 + type = types.nullOr types.bool; 173 166 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 167 }; 179 168 180 - # TODO: websocket options 169 + 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 + }; 181 174 182 - rpcAllowRemote = mkOption { 183 - default = false; 175 + advisoryDelete = mkOption { 184 176 description = '' 185 - false: Allow RPC connections only from 127.0.0.1. [default] 186 - true: Allow RPC connections from any IP. 177 + If set, then require administrative RPC call "can_delete" 178 + to enable online deletion of ledger records. 187 179 ''; 180 + type = types.nullOr types.bool; 181 + default = null; 188 182 }; 189 183 190 - rpcAdminAllow = mkOption { 191 - example = [ "10.0.0.4" ]; 192 - description = '' 193 - List of IP addresses allowed to have admin access. 194 - ''; 184 + extraOpts = mkOption { 185 + description = "Extra database options."; 186 + type = types.lines; 187 + default = ""; 195 188 }; 189 + }; 196 190 197 - 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 - }; 191 + in 192 + 193 + { 194 + 195 + ###### interface 196 + 197 + options = { 198 + services.rippled = { 199 + enable = mkEnableOption "Whether to enable rippled"; 206 200 207 - 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 - }; 201 + package = mkOption { 202 + description = "Which rippled package to use."; 203 + type = types.package; 204 + default = pkgs.rippled; 205 + }; 216 206 217 - rpcIp = mkOption { 218 - type = types.str; 207 + 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 { 219 263 description = '' 220 - IP address or domain to bind to allow insecure RPC connections. 221 - Defaults to not binding, which disallows RPC connections. 264 + Rippled size of the node you are running. 265 + "tiny", "small", "medium", "large", and "huge" 222 266 ''; 267 + type = types.enum ["tiny" "small" "medium" "large" "huge"]; 268 + default = "small"; 223 269 }; 224 270 225 - rpcPort = mkOption { 226 - type = types.int; 227 - description = '' 228 - If rpcIp is supplied, corresponding port to bind to for peer connections. 229 - ''; 271 + 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"]; 230 284 }; 231 285 232 - rpcUser = mkOption { 233 - type = types.str; 286 + ipsFixed = mkOption { 234 287 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. 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 240 295 ''; 296 + type = types.listOf types.str; 297 + default = []; 241 298 }; 242 299 243 - rpcPassword = mkOption { 244 - type = types.str; 300 + validators = mkOption { 245 301 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. 302 + List of nodes to always accept as validators. Nodes are specified by domain 303 + or public key. 251 304 ''; 305 + type = types.listOf types.str; 306 + default = [ 307 + "n949f75evCHwgyP4fPVgaHqNHxUVN15PsJEZ3B3HnXPcPjcZAoy7 RL1" 308 + "n9MD5h24qrQqiyBC8aeqqCWvpiBiYQ3jxSr91uiDvmrkyHRdYLUj RL2" 309 + "n9L81uNCaPgtUJfaHh89gmdvXKAmSt5Gdsw2g1iPWaPkAHW5Nm4C RL3" 310 + "n9KiYM9CgngLvtRCQHZwgC2gjpdaZcCcbt3VboxiNFcKuwFVujzS RL4" 311 + "n9LdgEtkmGB9E2h3K4Vp7iGUaKuq23Zr32ehxiU8FWY7xoxbWTSA RL5" 312 + ]; 252 313 }; 253 314 254 - rpcStartup = mkOption { 255 - example = [ ''"command" : "log_level"'' ''"partition" : "ripplecalc"'' ''"severity" : "trace"'' ]; 256 - description = "List of RPC commands to run at startup."; 315 + databasePath = mkOption { 316 + description = '' 317 + Path to the ripple database. 318 + ''; 319 + type = types.path; 320 + default = "/var/lib/rippled/db"; 257 321 }; 258 322 259 - rpcSecure = mkOption { 260 - default = false; 323 + validationQuorum = mkOption { 261 324 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. 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 + }; 264 331 265 - 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 332 + ledgerHistory = mkOption { 333 + description = '' 334 + The number of past ledgers to acquire on server startup and the minimum 335 + to maintain while running. 267 336 ''; 337 + type = types.either types.int (types.enum ["full"]); 338 + default = 1296000; # 1 month 268 339 }; 269 - */ 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 + }; 270 368 271 369 extraConfig = mkOption { 272 370 default = ""; ··· 275 373 ''; 276 374 }; 277 375 376 + config = mkOption { 377 + internal = true; 378 + default = pkgs.writeText "rippled.conf" rippledCfg; 379 + }; 278 380 }; 279 - 280 381 }; 281 382 282 383 ··· 288 389 { name = "rippled"; 289 390 description = "Ripple server user"; 290 391 uid = config.ids.uids.rippled; 291 - home = "/var/lib/rippled"; 392 + home = cfg.databasePath; 393 + createHome = true; 292 394 }; 293 395 294 396 systemd.services.rippled = { 295 - path = [ pkgs.rippled ]; 296 - 297 397 after = [ "network.target" ]; 298 398 wantedBy = [ "multi-user.target" ]; 299 399 300 400 serviceConfig = { 301 - ExecStart = "${pkgs.rippled}/bin/rippled --fg -q --conf ${rippledStateCfgFile}"; 302 - WorkingDirectory = "/var/lib/rippled"; 401 + ExecStart = "${cfg.package}/bin/rippled --fg --conf ${cfg.config}"; 402 + User = "rippled"; 303 403 }; 304 404 }; 305 405 306 - networking.firewall.allowedTCPPorts = mkIf (cfg.peerIp != null) [ cfg.peerPort ]; 406 + environment.systemPackages = [ cfg.package ]; 307 407 308 - 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 408 }; 314 409 }