at 23.11-pre 12 kB view raw
1{ config, lib, options, pkgs, ... }: 2 3with lib; 4 5let 6 cfg = config.services.rippled; 7 opt = options.services.rippled; 8 9 b2i = val: if val then "1" else "0"; 10 11 dbCfg = db: '' 12 type=${db.type} 13 path=${db.path} 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}")} 17 ${db.extraOpts} 18 ''; 19 20 rippledCfg = '' 21 [server] 22 ${concatMapStringsSep "\n" (n: "port_${n}") (attrNames cfg.ports)} 23 24 ${concatMapStrings (p: '' 25 [port_${p.name}] 26 ip=${p.ip} 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)} 36 37 [database_path] 38 ${cfg.databasePath} 39 40 [node_db] 41 ${dbCfg cfg.nodeDb} 42 43 ${optionalString (cfg.tempDb != null) '' 44 [temp_db] 45 ${dbCfg cfg.tempDb}''} 46 47 ${optionalString (cfg.importDb != null) '' 48 [import_db] 49 ${dbCfg cfg.importDb}''} 50 51 [ips] 52 ${concatStringsSep "\n" cfg.ips} 53 54 [ips_fixed] 55 ${concatStringsSep "\n" cfg.ipsFixed} 56 57 [validators] 58 ${concatStringsSep "\n" cfg.validators} 59 60 [node_size] 61 ${cfg.nodeSize} 62 63 [ledger_history] 64 ${toString cfg.ledgerHistory} 65 66 [fetch_depth] 67 ${toString cfg.fetchDepth} 68 69 [validation_quorum] 70 ${toString cfg.validationQuorum} 71 72 [sntp_servers] 73 ${concatStringsSep "\n" cfg.sntpServers} 74 75 ${optionalString cfg.statsd.enable '' 76 [insight] 77 server=statsd 78 address=${cfg.statsd.address} 79 prefix=${cfg.statsd.prefix} 80 ''} 81 82 [rpc_startup] 83 { "command": "log_level", "severity": "${cfg.logLevel}" } 84 '' + cfg.extraConfig; 85 86 portOptions = { name, ...}: { 87 options = { 88 name = mkOption { 89 internal = true; 90 default = name; 91 }; 92 93 ip = mkOption { 94 default = "127.0.0.1"; 95 description = lib.mdDoc "Ip where rippled listens."; 96 type = types.str; 97 }; 98 99 port = mkOption { 100 description = lib.mdDoc "Port where rippled listens."; 101 type = types.port; 102 }; 103 104 protocol = mkOption { 105 description = lib.mdDoc "Protocols expose by rippled."; 106 type = types.listOf (types.enum ["http" "https" "ws" "wss" "peer"]); 107 }; 108 109 user = mkOption { 110 description = lib.mdDoc "When set, these credentials will be required on HTTP/S requests."; 111 type = types.str; 112 default = ""; 113 }; 114 115 password = mkOption { 116 description = lib.mdDoc "When set, these credentials will be required on HTTP/S requests."; 117 type = types.str; 118 default = ""; 119 }; 120 121 admin = mkOption { 122 description = lib.mdDoc "A comma-separated list of admin IP addresses."; 123 type = types.listOf types.str; 124 default = ["127.0.0.1"]; 125 }; 126 127 ssl = { 128 key = mkOption { 129 description = lib.mdDoc '' 130 Specifies the filename holding the SSL key in PEM format. 131 ''; 132 default = null; 133 type = types.nullOr types.path; 134 }; 135 136 cert = mkOption { 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. 140 ''; 141 default = null; 142 type = types.nullOr types.path; 143 }; 144 145 chain = mkOption { 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. 149 ''; 150 default = null; 151 type = types.nullOr types.path; 152 }; 153 }; 154 }; 155 }; 156 157 dbOptions = { 158 options = { 159 type = mkOption { 160 description = lib.mdDoc "Rippled database type."; 161 type = types.enum ["rocksdb" "nudb"]; 162 default = "rocksdb"; 163 }; 164 165 path = mkOption { 166 description = lib.mdDoc "Location to store the database."; 167 type = types.path; 168 default = cfg.databasePath; 169 defaultText = literalExpression "config.${opt.databasePath}"; 170 }; 171 172 compression = mkOption { 173 description = lib.mdDoc "Whether to enable snappy compression."; 174 type = types.nullOr types.bool; 175 default = null; 176 }; 177 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}"; 183 }; 184 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. 189 ''; 190 type = types.nullOr types.bool; 191 default = null; 192 }; 193 194 extraOpts = mkOption { 195 description = lib.mdDoc "Extra database options."; 196 type = types.lines; 197 default = ""; 198 }; 199 }; 200 }; 201 202in 203 204{ 205 206 ###### interface 207 208 options = { 209 services.rippled = { 210 enable = mkEnableOption (lib.mdDoc "rippled"); 211 212 package = mkOption { 213 description = lib.mdDoc "Which rippled package to use."; 214 type = types.package; 215 default = pkgs.rippled; 216 defaultText = literalExpression "pkgs.rippled"; 217 }; 218 219 ports = mkOption { 220 description = lib.mdDoc "Ports exposed by rippled"; 221 type = with types; attrsOf (submodule portOptions); 222 default = { 223 rpc = { 224 port = 5005; 225 admin = ["127.0.0.1"]; 226 protocol = ["http"]; 227 }; 228 229 peer = { 230 port = 51235; 231 ip = "0.0.0.0"; 232 protocol = ["peer"]; 233 }; 234 235 ws_public = { 236 port = 5006; 237 ip = "0.0.0.0"; 238 protocol = ["ws" "wss"]; 239 }; 240 }; 241 }; 242 243 nodeDb = mkOption { 244 description = lib.mdDoc "Rippled main database options."; 245 type = with types; nullOr (submodule dbOptions); 246 default = { 247 type = "rocksdb"; 248 extraOpts = '' 249 open_files=2000 250 filter_bits=12 251 cache_mb=256 252 file_size_pb=8 253 file_size_mult=2; 254 ''; 255 }; 256 }; 257 258 tempDb = mkOption { 259 description = lib.mdDoc "Rippled temporary database options."; 260 type = with types; nullOr (submodule dbOptions); 261 default = null; 262 }; 263 264 importDb = mkOption { 265 description = lib.mdDoc "Settings for performing a one-time import."; 266 type = with types; nullOr (submodule dbOptions); 267 default = null; 268 }; 269 270 nodeSize = mkOption { 271 description = lib.mdDoc '' 272 Rippled size of the node you are running. 273 "tiny", "small", "medium", "large", and "huge" 274 ''; 275 type = types.enum ["tiny" "small" "medium" "large" "huge"]; 276 default = "small"; 277 }; 278 279 ips = mkOption { 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 285 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 288 to least trusted. 289 ''; 290 type = types.listOf types.str; 291 default = ["r.ripple.com 51235"]; 292 }; 293 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. 301 302 A port may optionally be specified after adding a space to the address 303 ''; 304 type = types.listOf types.str; 305 default = []; 306 }; 307 308 validators = mkOption { 309 description = lib.mdDoc '' 310 List of nodes to always accept as validators. Nodes are specified by domain 311 or public key. 312 ''; 313 type = types.listOf types.str; 314 default = [ 315 "n949f75evCHwgyP4fPVgaHqNHxUVN15PsJEZ3B3HnXPcPjcZAoy7 RL1" 316 "n9MD5h24qrQqiyBC8aeqqCWvpiBiYQ3jxSr91uiDvmrkyHRdYLUj RL2" 317 "n9L81uNCaPgtUJfaHh89gmdvXKAmSt5Gdsw2g1iPWaPkAHW5Nm4C RL3" 318 "n9KiYM9CgngLvtRCQHZwgC2gjpdaZcCcbt3VboxiNFcKuwFVujzS RL4" 319 "n9LdgEtkmGB9E2h3K4Vp7iGUaKuq23Zr32ehxiU8FWY7xoxbWTSA RL5" 320 ]; 321 }; 322 323 databasePath = mkOption { 324 description = lib.mdDoc '' 325 Path to the ripple database. 326 ''; 327 type = types.path; 328 default = "/var/lib/rippled"; 329 }; 330 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. 335 ''; 336 type = types.int; 337 default = 3; 338 }; 339 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. 344 ''; 345 type = types.either types.int (types.enum ["full"]); 346 default = 1296000; # 1 month 347 }; 348 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). 353 ''; 354 type = types.either types.int (types.enum ["full"]); 355 default = "full"; 356 }; 357 358 sntpServers = mkOption { 359 description = lib.mdDoc '' 360 IP address or domain of NTP servers to use for time synchronization.; 361 ''; 362 type = types.listOf types.str; 363 default = [ 364 "time.windows.com" 365 "time.apple.com" 366 "time.nist.gov" 367 "pool.ntp.org" 368 ]; 369 }; 370 371 logLevel = mkOption { 372 description = lib.mdDoc "Logging verbosity."; 373 type = types.enum ["debug" "error" "info"]; 374 default = "error"; 375 }; 376 377 statsd = { 378 enable = mkEnableOption (lib.mdDoc "statsd monitoring for rippled"); 379 380 address = mkOption { 381 description = lib.mdDoc "The UDP address and port of the listening StatsD server."; 382 default = "127.0.0.1:8125"; 383 type = types.str; 384 }; 385 386 prefix = mkOption { 387 description = lib.mdDoc "A string prepended to each collected metric."; 388 default = ""; 389 type = types.str; 390 }; 391 }; 392 393 extraConfig = mkOption { 394 default = ""; 395 type = types.lines; 396 description = lib.mdDoc '' 397 Extra lines to be added verbatim to the rippled.cfg configuration file. 398 ''; 399 }; 400 401 config = mkOption { 402 internal = true; 403 default = pkgs.writeText "rippled.conf" rippledCfg; 404 defaultText = literalMD "generated config file"; 405 }; 406 }; 407 }; 408 409 410 ###### implementation 411 412 config = mkIf cfg.enable { 413 414 users.users.rippled = { 415 description = "Ripple server user"; 416 isSystemUser = true; 417 group = "rippled"; 418 home = cfg.databasePath; 419 createHome = true; 420 }; 421 users.groups.rippled = {}; 422 423 systemd.services.rippled = { 424 after = [ "network.target" ]; 425 wantedBy = [ "multi-user.target" ]; 426 427 serviceConfig = { 428 ExecStart = "${cfg.package}/bin/rippled --fg --conf ${cfg.config}"; 429 User = "rippled"; 430 Restart = "on-failure"; 431 LimitNOFILE=10000; 432 }; 433 }; 434 435 environment.systemPackages = [ cfg.package ]; 436 437 }; 438}