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}