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}