diff options
Diffstat (limited to 'nixos/modules/services/misc/rippled.nix')
-rw-r--r-- | nixos/modules/services/misc/rippled.nix | 438 |
1 files changed, 438 insertions, 0 deletions
diff --git a/nixos/modules/services/misc/rippled.nix b/nixos/modules/services/misc/rippled.nix new file mode 100644 index 00000000000..f6ec0677774 --- /dev/null +++ b/nixos/modules/services/misc/rippled.nix @@ -0,0 +1,438 @@ +{ config, lib, options, pkgs, ... }: + +with lib; + +let + cfg = config.services.rippled; + opt = options.services.rippled; + + b2i = val: if val then "1" else "0"; + + dbCfg = db: '' + type=${db.type} + path=${db.path} + ${optionalString (db.compression != null) ("compression=${b2i db.compression}") } + ${optionalString (db.onlineDelete != null) ("online_delete=${toString db.onlineDelete}")} + ${optionalString (db.advisoryDelete != null) ("advisory_delete=${b2i db.advisoryDelete}")} + ${db.extraOpts} + ''; + + rippledCfg = '' + [server] + ${concatMapStringsSep "\n" (n: "port_${n}") (attrNames cfg.ports)} + + ${concatMapStrings (p: '' + [port_${p.name}] + ip=${p.ip} + port=${toString p.port} + protocol=${concatStringsSep "," p.protocol} + ${optionalString (p.user != "") "user=${p.user}"} + ${optionalString (p.password != "") "user=${p.password}"} + admin=${concatStringsSep "," p.admin} + ${optionalString (p.ssl.key != null) "ssl_key=${p.ssl.key}"} + ${optionalString (p.ssl.cert != null) "ssl_cert=${p.ssl.cert}"} + ${optionalString (p.ssl.chain != null) "ssl_chain=${p.ssl.chain}"} + '') (attrValues cfg.ports)} + + [database_path] + ${cfg.databasePath} + + [node_db] + ${dbCfg cfg.nodeDb} + + ${optionalString (cfg.tempDb != null) '' + [temp_db] + ${dbCfg cfg.tempDb}''} + + ${optionalString (cfg.importDb != null) '' + [import_db] + ${dbCfg cfg.importDb}''} + + [ips] + ${concatStringsSep "\n" cfg.ips} + + [ips_fixed] + ${concatStringsSep "\n" cfg.ipsFixed} + + [validators] + ${concatStringsSep "\n" cfg.validators} + + [node_size] + ${cfg.nodeSize} + + [ledger_history] + ${toString cfg.ledgerHistory} + + [fetch_depth] + ${toString cfg.fetchDepth} + + [validation_quorum] + ${toString cfg.validationQuorum} + + [sntp_servers] + ${concatStringsSep "\n" cfg.sntpServers} + + ${optionalString cfg.statsd.enable '' + [insight] + server=statsd + address=${cfg.statsd.address} + prefix=${cfg.statsd.prefix} + ''} + + [rpc_startup] + { "command": "log_level", "severity": "${cfg.logLevel}" } + '' + cfg.extraConfig; + + portOptions = { name, ...}: { + options = { + name = mkOption { + internal = true; + default = name; + }; + + ip = mkOption { + default = "127.0.0.1"; + description = "Ip where rippled listens."; + type = types.str; + }; + + port = mkOption { + description = "Port where rippled listens."; + type = types.int; + }; + + protocol = mkOption { + description = "Protocols expose by rippled."; + type = types.listOf (types.enum ["http" "https" "ws" "wss" "peer"]); + }; + + user = mkOption { + description = "When set, these credentials will be required on HTTP/S requests."; + type = types.str; + default = ""; + }; + + password = mkOption { + description = "When set, these credentials will be required on HTTP/S requests."; + type = types.str; + default = ""; + }; + + admin = mkOption { + description = "A comma-separated list of admin IP addresses."; + type = types.listOf types.str; + default = ["127.0.0.1"]; + }; + + ssl = { + key = mkOption { + description = '' + Specifies the filename holding the SSL key in PEM format. + ''; + default = null; + type = types.nullOr types.path; + }; + + cert = mkOption { + description = '' + Specifies the path to the SSL certificate file in PEM format. + This is not needed if the chain includes it. + ''; + default = null; + type = types.nullOr types.path; + }; + + chain = mkOption { + description = '' + If you need a certificate chain, specify the path to the + certificate chain here. The chain may include the end certificate. + ''; + default = null; + type = types.nullOr types.path; + }; + }; + }; + }; + + dbOptions = { + options = { + type = mkOption { + description = "Rippled database type."; + type = types.enum ["rocksdb" "nudb"]; + default = "rocksdb"; + }; + + path = mkOption { + description = "Location to store the database."; + type = types.path; + default = cfg.databasePath; + defaultText = literalExpression "config.${opt.databasePath}"; + }; + + compression = mkOption { + description = "Whether to enable snappy compression."; + type = types.nullOr types.bool; + default = null; + }; + + onlineDelete = mkOption { + description = "Enable automatic purging of older ledger information."; + type = types.nullOr (types.addCheck types.int (v: v > 256)); + default = cfg.ledgerHistory; + defaultText = literalExpression "config.${opt.ledgerHistory}"; + }; + + advisoryDelete = mkOption { + description = '' + If set, then require administrative RPC call "can_delete" + to enable online deletion of ledger records. + ''; + type = types.nullOr types.bool; + default = null; + }; + + extraOpts = mkOption { + description = "Extra database options."; + type = types.lines; + default = ""; + }; + }; + }; + +in + +{ + + ###### interface + + options = { + services.rippled = { + enable = mkEnableOption "rippled"; + + package = mkOption { + description = "Which rippled package to use."; + type = types.package; + default = pkgs.rippled; + defaultText = literalExpression "pkgs.rippled"; + }; + + ports = mkOption { + description = "Ports exposed by rippled"; + type = with types; attrsOf (submodule portOptions); + default = { + rpc = { + port = 5005; + admin = ["127.0.0.1"]; + protocol = ["http"]; + }; + + peer = { + port = 51235; + ip = "0.0.0.0"; + protocol = ["peer"]; + }; + + ws_public = { + port = 5006; + ip = "0.0.0.0"; + protocol = ["ws" "wss"]; + }; + }; + }; + + nodeDb = mkOption { + description = "Rippled main database options."; + type = with types; nullOr (submodule dbOptions); + default = { + type = "rocksdb"; + extraOpts = '' + open_files=2000 + filter_bits=12 + cache_mb=256 + file_size_pb=8 + file_size_mult=2; + ''; + }; + }; + + tempDb = mkOption { + description = "Rippled temporary database options."; + type = with types; nullOr (submodule dbOptions); + default = null; + }; + + importDb = mkOption { + description = "Settings for performing a one-time import."; + type = with types; nullOr (submodule dbOptions); + default = null; + }; + + nodeSize = mkOption { + description = '' + Rippled size of the node you are running. + "tiny", "small", "medium", "large", and "huge" + ''; + type = types.enum ["tiny" "small" "medium" "large" "huge"]; + default = "small"; + }; + + ips = mkOption { + description = '' + List of hostnames or ips where the Ripple protocol is served. + For a starter list, you can either copy entries from: + https://ripple.com/ripple.txt or if you prefer you can let it + default to r.ripple.com 51235 + + A port may optionally be specified after adding a space to the + address. By convention, if known, IPs are listed in from most + to least trusted. + ''; + type = types.listOf types.str; + default = ["r.ripple.com 51235"]; + }; + + ipsFixed = mkOption { + description = '' + List of IP addresses or hostnames to which rippled should always + attempt to maintain peer connections with. This is useful for + manually forming private networks, for example to configure a + validation server that connects to the Ripple network through a + public-facing server, or for building a set of cluster peers. + + A port may optionally be specified after adding a space to the address + ''; + type = types.listOf types.str; + default = []; + }; + + validators = mkOption { + description = '' + List of nodes to always accept as validators. Nodes are specified by domain + or public key. + ''; + type = types.listOf types.str; + default = [ + "n949f75evCHwgyP4fPVgaHqNHxUVN15PsJEZ3B3HnXPcPjcZAoy7 RL1" + "n9MD5h24qrQqiyBC8aeqqCWvpiBiYQ3jxSr91uiDvmrkyHRdYLUj RL2" + "n9L81uNCaPgtUJfaHh89gmdvXKAmSt5Gdsw2g1iPWaPkAHW5Nm4C RL3" + "n9KiYM9CgngLvtRCQHZwgC2gjpdaZcCcbt3VboxiNFcKuwFVujzS RL4" + "n9LdgEtkmGB9E2h3K4Vp7iGUaKuq23Zr32ehxiU8FWY7xoxbWTSA RL5" + ]; + }; + + databasePath = mkOption { + description = '' + Path to the ripple database. + ''; + type = types.path; + default = "/var/lib/rippled"; + }; + + validationQuorum = mkOption { + description = '' + The minimum number of trusted validations a ledger must have before + the server considers it fully validated. + ''; + type = types.int; + default = 3; + }; + + ledgerHistory = mkOption { + description = '' + The number of past ledgers to acquire on server startup and the minimum + to maintain while running. + ''; + type = types.either types.int (types.enum ["full"]); + default = 1296000; # 1 month + }; + + fetchDepth = mkOption { + description = '' + The number of past ledgers to serve to other peers that request historical + ledger data (or "full" for no limit). + ''; + type = types.either types.int (types.enum ["full"]); + default = "full"; + }; + + sntpServers = mkOption { + description = '' + IP address or domain of NTP servers to use for time synchronization.; + ''; + type = types.listOf types.str; + default = [ + "time.windows.com" + "time.apple.com" + "time.nist.gov" + "pool.ntp.org" + ]; + }; + + logLevel = mkOption { + description = "Logging verbosity."; + type = types.enum ["debug" "error" "info"]; + default = "error"; + }; + + statsd = { + enable = mkEnableOption "statsd monitoring for rippled"; + + address = mkOption { + description = "The UDP address and port of the listening StatsD server."; + default = "127.0.0.1:8125"; + type = types.str; + }; + + prefix = mkOption { + description = "A string prepended to each collected metric."; + default = ""; + type = types.str; + }; + }; + + extraConfig = mkOption { + default = ""; + type = types.lines; + description = '' + Extra lines to be added verbatim to the rippled.cfg configuration file. + ''; + }; + + config = mkOption { + internal = true; + default = pkgs.writeText "rippled.conf" rippledCfg; + defaultText = literalDocBook "generated config file"; + }; + }; + }; + + + ###### implementation + + config = mkIf cfg.enable { + + users.users.rippled = { + description = "Ripple server user"; + isSystemUser = true; + group = "rippled"; + home = cfg.databasePath; + createHome = true; + }; + users.groups.rippled = {}; + + systemd.services.rippled = { + after = [ "network.target" ]; + wantedBy = [ "multi-user.target" ]; + + serviceConfig = { + ExecStart = "${cfg.package}/bin/rippled --fg --conf ${cfg.config}"; + User = "rippled"; + Restart = "on-failure"; + LimitNOFILE=10000; + }; + }; + + environment.systemPackages = [ cfg.package ]; + + }; +} |