summary refs log tree commit diff
path: root/nixos/modules/services/misc/rippled.nix
diff options
context:
space:
mode:
Diffstat (limited to 'nixos/modules/services/misc/rippled.nix')
-rw-r--r--nixos/modules/services/misc/rippled.nix438
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 ];
+
+  };
+}