at 24.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 = "Ip where rippled listens."; 96 type = types.str; 97 }; 98 99 port = mkOption { 100 description = "Port where rippled listens."; 101 type = types.port; 102 }; 103 104 protocol = mkOption { 105 description = "Protocols expose by rippled."; 106 type = types.listOf (types.enum ["http" "https" "ws" "wss" "peer"]); 107 }; 108 109 user = mkOption { 110 description = "When set, these credentials will be required on HTTP/S requests."; 111 type = types.str; 112 default = ""; 113 }; 114 115 password = mkOption { 116 description = "When set, these credentials will be required on HTTP/S requests."; 117 type = types.str; 118 default = ""; 119 }; 120 121 admin = mkOption { 122 description = "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 = '' 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 = '' 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 = '' 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 = "Rippled database type."; 161 type = types.enum ["rocksdb" "nudb"]; 162 default = "rocksdb"; 163 }; 164 165 path = mkOption { 166 description = "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 = "Whether to enable snappy compression."; 174 type = types.nullOr types.bool; 175 default = null; 176 }; 177 178 onlineDelete = mkOption { 179 description = "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 = '' 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 = "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 "rippled, a decentralized cryptocurrency blockchain daemon implementing the XRP Ledger protocol in C++"; 211 212 package = mkPackageOption pkgs "rippled" { }; 213 214 ports = mkOption { 215 description = "Ports exposed by rippled"; 216 type = with types; attrsOf (submodule portOptions); 217 default = { 218 rpc = { 219 port = 5005; 220 admin = ["127.0.0.1"]; 221 protocol = ["http"]; 222 }; 223 224 peer = { 225 port = 51235; 226 ip = "0.0.0.0"; 227 protocol = ["peer"]; 228 }; 229 230 ws_public = { 231 port = 5006; 232 ip = "0.0.0.0"; 233 protocol = ["ws" "wss"]; 234 }; 235 }; 236 }; 237 238 nodeDb = mkOption { 239 description = "Rippled main database options."; 240 type = with types; nullOr (submodule dbOptions); 241 default = { 242 type = "rocksdb"; 243 extraOpts = '' 244 open_files=2000 245 filter_bits=12 246 cache_mb=256 247 file_size_pb=8 248 file_size_mult=2; 249 ''; 250 }; 251 }; 252 253 tempDb = mkOption { 254 description = "Rippled temporary database options."; 255 type = with types; nullOr (submodule dbOptions); 256 default = null; 257 }; 258 259 importDb = mkOption { 260 description = "Settings for performing a one-time import."; 261 type = with types; nullOr (submodule dbOptions); 262 default = null; 263 }; 264 265 nodeSize = mkOption { 266 description = '' 267 Rippled size of the node you are running. 268 "tiny", "small", "medium", "large", and "huge" 269 ''; 270 type = types.enum ["tiny" "small" "medium" "large" "huge"]; 271 default = "small"; 272 }; 273 274 ips = mkOption { 275 description = '' 276 List of hostnames or ips where the Ripple protocol is served. 277 For a starter list, you can either copy entries from: 278 https://ripple.com/ripple.txt or if you prefer you can let it 279 default to r.ripple.com 51235 280 281 A port may optionally be specified after adding a space to the 282 address. By convention, if known, IPs are listed in from most 283 to least trusted. 284 ''; 285 type = types.listOf types.str; 286 default = ["r.ripple.com 51235"]; 287 }; 288 289 ipsFixed = mkOption { 290 description = '' 291 List of IP addresses or hostnames to which rippled should always 292 attempt to maintain peer connections with. This is useful for 293 manually forming private networks, for example to configure a 294 validation server that connects to the Ripple network through a 295 public-facing server, or for building a set of cluster peers. 296 297 A port may optionally be specified after adding a space to the address 298 ''; 299 type = types.listOf types.str; 300 default = []; 301 }; 302 303 validators = mkOption { 304 description = '' 305 List of nodes to always accept as validators. Nodes are specified by domain 306 or public key. 307 ''; 308 type = types.listOf types.str; 309 default = [ 310 "n949f75evCHwgyP4fPVgaHqNHxUVN15PsJEZ3B3HnXPcPjcZAoy7 RL1" 311 "n9MD5h24qrQqiyBC8aeqqCWvpiBiYQ3jxSr91uiDvmrkyHRdYLUj RL2" 312 "n9L81uNCaPgtUJfaHh89gmdvXKAmSt5Gdsw2g1iPWaPkAHW5Nm4C RL3" 313 "n9KiYM9CgngLvtRCQHZwgC2gjpdaZcCcbt3VboxiNFcKuwFVujzS RL4" 314 "n9LdgEtkmGB9E2h3K4Vp7iGUaKuq23Zr32ehxiU8FWY7xoxbWTSA RL5" 315 ]; 316 }; 317 318 databasePath = mkOption { 319 description = '' 320 Path to the ripple database. 321 ''; 322 type = types.path; 323 default = "/var/lib/rippled"; 324 }; 325 326 validationQuorum = mkOption { 327 description = '' 328 The minimum number of trusted validations a ledger must have before 329 the server considers it fully validated. 330 ''; 331 type = types.int; 332 default = 3; 333 }; 334 335 ledgerHistory = mkOption { 336 description = '' 337 The number of past ledgers to acquire on server startup and the minimum 338 to maintain while running. 339 ''; 340 type = types.either types.int (types.enum ["full"]); 341 default = 1296000; # 1 month 342 }; 343 344 fetchDepth = mkOption { 345 description = '' 346 The number of past ledgers to serve to other peers that request historical 347 ledger data (or "full" for no limit). 348 ''; 349 type = types.either types.int (types.enum ["full"]); 350 default = "full"; 351 }; 352 353 sntpServers = mkOption { 354 description = '' 355 IP address or domain of NTP servers to use for time synchronization.; 356 ''; 357 type = types.listOf types.str; 358 default = [ 359 "time.windows.com" 360 "time.apple.com" 361 "time.nist.gov" 362 "pool.ntp.org" 363 ]; 364 }; 365 366 logLevel = mkOption { 367 description = "Logging verbosity."; 368 type = types.enum ["debug" "error" "info"]; 369 default = "error"; 370 }; 371 372 statsd = { 373 enable = mkEnableOption "statsd monitoring for rippled"; 374 375 address = mkOption { 376 description = "The UDP address and port of the listening StatsD server."; 377 default = "127.0.0.1:8125"; 378 type = types.str; 379 }; 380 381 prefix = mkOption { 382 description = "A string prepended to each collected metric."; 383 default = ""; 384 type = types.str; 385 }; 386 }; 387 388 extraConfig = mkOption { 389 default = ""; 390 type = types.lines; 391 description = '' 392 Extra lines to be added verbatim to the rippled.cfg configuration file. 393 ''; 394 }; 395 396 config = mkOption { 397 internal = true; 398 default = pkgs.writeText "rippled.conf" rippledCfg; 399 defaultText = literalMD "generated config file"; 400 }; 401 }; 402 }; 403 404 405 ###### implementation 406 407 config = mkIf cfg.enable { 408 409 users.users.rippled = { 410 description = "Ripple server user"; 411 isSystemUser = true; 412 group = "rippled"; 413 home = cfg.databasePath; 414 createHome = true; 415 }; 416 users.groups.rippled = {}; 417 418 systemd.services.rippled = { 419 after = [ "network.target" ]; 420 wantedBy = [ "multi-user.target" ]; 421 422 serviceConfig = { 423 ExecStart = "${cfg.package}/bin/rippled --fg --conf ${cfg.config}"; 424 User = "rippled"; 425 Restart = "on-failure"; 426 LimitNOFILE=10000; 427 }; 428 }; 429 430 environment.systemPackages = [ cfg.package ]; 431 432 }; 433}