summary refs log blame commit diff
path: root/nixos/modules/services/misc/rippled.nix
blob: f6ec0677774b397611562287a5c882205895a452 (plain) (tree)
1
2
3
4
5
6
7
8
                                    
 
         


                                
                                 
 






                                                                                             
                                                                                              

                   

                 









                                                                      
                                         







                                                                      
             
                       
 


                                            
 


                                              
 

                                    
 

                                         
 

                                           
 

                   
 

                                 
 

                              
 

                                    
 

                                            
 






                                         


                                                             
 


                               

                        

        
                     


                                                  
        
 
                       

                                                    
        
 
                           

                                                                            

        
                       


                                                                                         

        
                           


                                                                                         
        
 
                        


                                                                      
        
 
             
























                                                                              
        
      
    
 
               





                                               
 



                                                        
                                                                     
        
 




                                                              
 

                                                                              
                                                                    
                                    
                                                                      
        
 

                                 

                                                                   



                                       
 




                                                
      
    
 







                        
                                        

                          


                                                      
                                                       


                        




















                                                           
        
 
                         











                                                        


                         


                                                            


                           


                                                                   


                           





                                                                   

        
                      











                                                                        


                           





                                                                          
 



                                                                                


                             











                                                                                     

        
                               




                                      

        
                                   





                                                                             

        
                                





                                                                                 

        
                             





                                                                                    
        
 
                              









                                                                               



                                           

                                                   
        
 
                
                                                                













                                                                                   

                              
                           


                                                                                 

        
                         

                                                           
                                                             
        
      






                            



                                           

                                
        
                              

                                



                                         

                                                                           

                               


        
                                                 
 

    
{ 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 ];

  };
}