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