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 "Go Ethereum Node";
13
14 port = mkOption {
15 type = types.port;
16 default = 30303;
17 description = "Port number Go Ethereum will be listening on, both TCP and UDP.";
18 };
19
20 http = {
21 enable = lib.mkEnableOption "Go Ethereum HTTP API";
22 address = mkOption {
23 type = types.str;
24 default = "127.0.0.1";
25 description = "Listen address of Go Ethereum HTTP API.";
26 };
27
28 port = mkOption {
29 type = types.port;
30 default = 8545;
31 description = "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 = "APIs to enable over WebSocket";
38 example = ["net" "eth"];
39 };
40 };
41
42 websocket = {
43 enable = lib.mkEnableOption "Go Ethereum WebSocket API";
44 address = mkOption {
45 type = types.str;
46 default = "127.0.0.1";
47 description = "Listen address of Go Ethereum WebSocket API.";
48 };
49
50 port = mkOption {
51 type = types.port;
52 default = 8546;
53 description = "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 = "APIs to enable over WebSocket";
60 example = ["net" "eth"];
61 };
62 };
63
64 metrics = {
65 enable = lib.mkEnableOption "Go Ethereum prometheus metrics";
66 address = mkOption {
67 type = types.str;
68 default = "127.0.0.1";
69 description = "Listen address of Go Ethereum metrics service.";
70 };
71
72 port = mkOption {
73 type = types.port;
74 default = 6060;
75 description = "Port number of Go Ethereum metrics service.";
76 };
77 };
78
79 network = mkOption {
80 type = types.nullOr (types.enum [ "goerli" "rinkeby" "yolov2" "ropsten" ]);
81 default = null;
82 description = "The network to connect to. Mainnet (null) is the default ethereum network.";
83 };
84
85 syncmode = mkOption {
86 type = types.enum [ "fast" "full" "light" ];
87 default = "fast";
88 description = "Blockchain sync mode.";
89 };
90
91 gcmode = mkOption {
92 type = types.enum [ "full" "archive" ];
93 default = "full";
94 description = "Blockchain garbage collection mode.";
95 };
96
97 maxpeers = mkOption {
98 type = types.int;
99 default = 50;
100 description = "Maximum peers to connect to.";
101 };
102
103 extraArgs = mkOption {
104 type = types.listOf types.str;
105 description = "Additional arguments passed to Go Ethereum.";
106 default = [];
107 };
108
109 package = mkOption {
110 default = pkgs.go-ethereum.geth;
111 type = types.package;
112 description = "Package to use as Go Ethereum node.";
113 };
114 };
115 };
116in
117
118{
119
120 ###### interface
121
122 options = {
123 services.geth = mkOption {
124 type = types.attrsOf (types.submodule gethOpts);
125 default = {};
126 description = "Specification of one or more geth instances.";
127 };
128 };
129
130 ###### implementation
131
132 config = mkIf (eachGeth != {}) {
133
134 environment.systemPackages = flatten (mapAttrsToList (gethName: cfg: [
135 cfg.package
136 ]) eachGeth);
137
138 systemd.services = mapAttrs' (gethName: cfg: (
139 nameValuePair "geth-${gethName}" (mkIf cfg.enable {
140 description = "Go Ethereum node (${gethName})";
141 wantedBy = [ "multi-user.target" ];
142 after = [ "network.target" ];
143
144 serviceConfig = {
145 DynamicUser = true;
146 Restart = "always";
147 StateDirectory = "goethereum/${gethName}/${if (cfg.network == null) then "mainnet" else cfg.network}";
148
149 # Hardening measures
150 PrivateTmp = "true";
151 ProtectSystem = "full";
152 NoNewPrivileges = "true";
153 PrivateDevices = "true";
154 MemoryDenyWriteExecute = "true";
155 };
156
157 script = ''
158 ${cfg.package}/bin/geth \
159 --nousb \
160 --ipcdisable \
161 ${optionalString (cfg.network != null) ''--${cfg.network}''} \
162 --syncmode ${cfg.syncmode} \
163 --gcmode ${cfg.gcmode} \
164 --port ${toString cfg.port} \
165 --maxpeers ${toString cfg.maxpeers} \
166 ${if cfg.http.enable then ''--http --http.addr ${cfg.http.address} --http.port ${toString cfg.http.port}'' else ""} \
167 ${optionalString (cfg.http.apis != null) ''--http.api ${lib.concatStringsSep "," cfg.http.apis}''} \
168 ${if cfg.websocket.enable then ''--ws --ws.addr ${cfg.websocket.address} --ws.port ${toString cfg.websocket.port}'' else ""} \
169 ${optionalString (cfg.websocket.apis != null) ''--ws.api ${lib.concatStringsSep "," cfg.websocket.apis}''} \
170 ${optionalString cfg.metrics.enable ''--metrics --metrics.addr ${cfg.metrics.address} --metrics.port ${toString cfg.metrics.port}''} \
171 ${lib.escapeShellArgs cfg.extraArgs} \
172 --datadir /var/lib/goethereum/${gethName}/${if (cfg.network == null) then "mainnet" else cfg.network}
173 '';
174 }))) eachGeth;
175
176 };
177
178}