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 [ "snap" "fast" "full" "light" ];
87 default = "snap";
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 defaultText = literalExpression "pkgs.go-ethereum.geth";
112 type = types.package;
113 description = "Package to use as Go Ethereum node.";
114 };
115 };
116 };
117in
118
119{
120
121 ###### interface
122
123 options = {
124 services.geth = mkOption {
125 type = types.attrsOf (types.submodule gethOpts);
126 default = {};
127 description = "Specification of one or more geth instances.";
128 };
129 };
130
131 ###### implementation
132
133 config = mkIf (eachGeth != {}) {
134
135 environment.systemPackages = flatten (mapAttrsToList (gethName: cfg: [
136 cfg.package
137 ]) eachGeth);
138
139 systemd.services = mapAttrs' (gethName: cfg: (
140 nameValuePair "geth-${gethName}" (mkIf cfg.enable {
141 description = "Go Ethereum node (${gethName})";
142 wantedBy = [ "multi-user.target" ];
143 after = [ "network.target" ];
144
145 serviceConfig = {
146 DynamicUser = true;
147 Restart = "always";
148 StateDirectory = "goethereum/${gethName}/${if (cfg.network == null) then "mainnet" else cfg.network}";
149
150 # Hardening measures
151 PrivateTmp = "true";
152 ProtectSystem = "full";
153 NoNewPrivileges = "true";
154 PrivateDevices = "true";
155 MemoryDenyWriteExecute = "true";
156 };
157
158 script = ''
159 ${cfg.package}/bin/geth \
160 --nousb \
161 --ipcdisable \
162 ${optionalString (cfg.network != null) ''--${cfg.network}''} \
163 --syncmode ${cfg.syncmode} \
164 --gcmode ${cfg.gcmode} \
165 --port ${toString cfg.port} \
166 --maxpeers ${toString cfg.maxpeers} \
167 ${if cfg.http.enable then ''--http --http.addr ${cfg.http.address} --http.port ${toString cfg.http.port}'' else ""} \
168 ${optionalString (cfg.http.apis != null) ''--http.api ${lib.concatStringsSep "," cfg.http.apis}''} \
169 ${if cfg.websocket.enable then ''--ws --ws.addr ${cfg.websocket.address} --ws.port ${toString cfg.websocket.port}'' else ""} \
170 ${optionalString (cfg.websocket.apis != null) ''--ws.api ${lib.concatStringsSep "," cfg.websocket.apis}''} \
171 ${optionalString cfg.metrics.enable ''--metrics --metrics.addr ${cfg.metrics.address} --metrics.port ${toString cfg.metrics.port}''} \
172 ${lib.escapeShellArgs cfg.extraArgs} \
173 --datadir /var/lib/goethereum/${gethName}/${if (cfg.network == null) then "mainnet" else cfg.network}
174 '';
175 }))) eachGeth;
176
177 };
178
179}