1{ config, lib, pkgs, ... }: 2 3with lib; 4 5let 6 eachGeth = config.services.geth; 7 8 gethOpts = { config, lib, name, ...}: { 9 10 options = { 11 12 enable = lib.mkEnableOption (lib.mdDoc "Go Ethereum Node"); 13 14 port = mkOption { 15 type = types.port; 16 default = 30303; 17 description = lib.mdDoc "Port number Go Ethereum will be listening on, both TCP and UDP."; 18 }; 19 20 http = { 21 enable = lib.mkEnableOption (lib.mdDoc "Go Ethereum HTTP API"); 22 address = mkOption { 23 type = types.str; 24 default = "127.0.0.1"; 25 description = lib.mdDoc "Listen address of Go Ethereum HTTP API."; 26 }; 27 28 port = mkOption { 29 type = types.port; 30 default = 8545; 31 description = lib.mdDoc "Port number of Go Ethereum HTTP API."; 32 }; 33 34 apis = mkOption { 35 type = types.nullOr (types.listOf types.str); 36 default = null; 37 description = lib.mdDoc "APIs to enable over WebSocket"; 38 example = ["net" "eth"]; 39 }; 40 }; 41 42 websocket = { 43 enable = lib.mkEnableOption (lib.mdDoc "Go Ethereum WebSocket API"); 44 address = mkOption { 45 type = types.str; 46 default = "127.0.0.1"; 47 description = lib.mdDoc "Listen address of Go Ethereum WebSocket API."; 48 }; 49 50 port = mkOption { 51 type = types.port; 52 default = 8546; 53 description = lib.mdDoc "Port number of Go Ethereum WebSocket API."; 54 }; 55 56 apis = mkOption { 57 type = types.nullOr (types.listOf types.str); 58 default = null; 59 description = lib.mdDoc "APIs to enable over WebSocket"; 60 example = ["net" "eth"]; 61 }; 62 }; 63 64 authrpc = { 65 enable = lib.mkEnableOption (lib.mdDoc "Go Ethereum Auth RPC API"); 66 address = mkOption { 67 type = types.str; 68 default = "127.0.0.1"; 69 description = lib.mdDoc "Listen address of Go Ethereum Auth RPC API."; 70 }; 71 72 port = mkOption { 73 type = types.port; 74 default = 8551; 75 description = lib.mdDoc "Port number of Go Ethereum Auth RPC API."; 76 }; 77 78 vhosts = mkOption { 79 type = types.nullOr (types.listOf types.str); 80 default = ["localhost"]; 81 description = lib.mdDoc "List of virtual hostnames from which to accept requests."; 82 example = ["localhost" "geth.example.org"]; 83 }; 84 85 jwtsecret = mkOption { 86 type = types.str; 87 default = ""; 88 description = lib.mdDoc "Path to a JWT secret for authenticated RPC endpoint."; 89 example = "/var/run/geth/jwtsecret"; 90 }; 91 }; 92 93 metrics = { 94 enable = lib.mkEnableOption (lib.mdDoc "Go Ethereum prometheus metrics"); 95 address = mkOption { 96 type = types.str; 97 default = "127.0.0.1"; 98 description = lib.mdDoc "Listen address of Go Ethereum metrics service."; 99 }; 100 101 port = mkOption { 102 type = types.port; 103 default = 6060; 104 description = lib.mdDoc "Port number of Go Ethereum metrics service."; 105 }; 106 }; 107 108 network = mkOption { 109 type = types.nullOr (types.enum [ "goerli" "rinkeby" "yolov2" "ropsten" ]); 110 default = null; 111 description = lib.mdDoc "The network to connect to. Mainnet (null) is the default ethereum network."; 112 }; 113 114 syncmode = mkOption { 115 type = types.enum [ "snap" "fast" "full" "light" ]; 116 default = "snap"; 117 description = lib.mdDoc "Blockchain sync mode."; 118 }; 119 120 gcmode = mkOption { 121 type = types.enum [ "full" "archive" ]; 122 default = "full"; 123 description = lib.mdDoc "Blockchain garbage collection mode."; 124 }; 125 126 maxpeers = mkOption { 127 type = types.int; 128 default = 50; 129 description = lib.mdDoc "Maximum peers to connect to."; 130 }; 131 132 extraArgs = mkOption { 133 type = types.listOf types.str; 134 description = lib.mdDoc "Additional arguments passed to Go Ethereum."; 135 default = []; 136 }; 137 138 package = mkOption { 139 default = pkgs.go-ethereum.geth; 140 defaultText = literalExpression "pkgs.go-ethereum.geth"; 141 type = types.package; 142 description = lib.mdDoc "Package to use as Go Ethereum node."; 143 }; 144 }; 145 }; 146in 147 148{ 149 150 ###### interface 151 152 options = { 153 services.geth = mkOption { 154 type = types.attrsOf (types.submodule gethOpts); 155 default = {}; 156 description = lib.mdDoc "Specification of one or more geth instances."; 157 }; 158 }; 159 160 ###### implementation 161 162 config = mkIf (eachGeth != {}) { 163 164 environment.systemPackages = flatten (mapAttrsToList (gethName: cfg: [ 165 cfg.package 166 ]) eachGeth); 167 168 systemd.services = mapAttrs' (gethName: cfg: let 169 stateDir = "goethereum/${gethName}/${if (cfg.network == null) then "mainnet" else cfg.network}"; 170 dataDir = "/var/lib/${stateDir}"; 171 in ( 172 nameValuePair "geth-${gethName}" (mkIf cfg.enable { 173 description = "Go Ethereum node (${gethName})"; 174 wantedBy = [ "multi-user.target" ]; 175 after = [ "network.target" ]; 176 177 serviceConfig = { 178 DynamicUser = true; 179 Restart = "always"; 180 StateDirectory = stateDir; 181 182 # Hardening measures 183 PrivateTmp = "true"; 184 ProtectSystem = "full"; 185 NoNewPrivileges = "true"; 186 PrivateDevices = "true"; 187 MemoryDenyWriteExecute = "true"; 188 }; 189 190 script = '' 191 ${cfg.package}/bin/geth \ 192 --nousb \ 193 --ipcdisable \ 194 ${optionalString (cfg.network != null) ''--${cfg.network}''} \ 195 --syncmode ${cfg.syncmode} \ 196 --gcmode ${cfg.gcmode} \ 197 --port ${toString cfg.port} \ 198 --maxpeers ${toString cfg.maxpeers} \ 199 ${optionalString cfg.http.enable ''--http --http.addr ${cfg.http.address} --http.port ${toString cfg.http.port}''} \ 200 ${optionalString (cfg.http.apis != null) ''--http.api ${lib.concatStringsSep "," cfg.http.apis}''} \ 201 ${optionalString cfg.websocket.enable ''--ws --ws.addr ${cfg.websocket.address} --ws.port ${toString cfg.websocket.port}''} \ 202 ${optionalString (cfg.websocket.apis != null) ''--ws.api ${lib.concatStringsSep "," cfg.websocket.apis}''} \ 203 ${optionalString cfg.metrics.enable ''--metrics --metrics.addr ${cfg.metrics.address} --metrics.port ${toString cfg.metrics.port}''} \ 204 --authrpc.addr ${cfg.authrpc.address} --authrpc.port ${toString cfg.authrpc.port} --authrpc.vhosts ${lib.concatStringsSep "," cfg.authrpc.vhosts} \ 205 ${if (cfg.authrpc.jwtsecret != "") then ''--authrpc.jwtsecret ${cfg.authrpc.jwtsecret}'' else ''--authrpc.jwtsecret ${dataDir}/geth/jwtsecret''} \ 206 ${lib.escapeShellArgs cfg.extraArgs} \ 207 --datadir ${dataDir} 208 ''; 209 }))) eachGeth; 210 211 }; 212 213}