1{ config, lib, pkgs, ... }:
2
3with lib;
4let
5
6 cfg = config.services.lighthouse;
7in {
8
9 options = {
10 services.lighthouse = {
11 beacon = mkOption {
12 description = lib.mdDoc "Beacon node";
13 default = {};
14 type = types.submodule {
15 options = {
16 enable = lib.mkEnableOption (lib.mdDoc "Lightouse Beacon node");
17
18 dataDir = mkOption {
19 type = types.str;
20 default = "/var/lib/lighthouse-beacon";
21 description = lib.mdDoc ''
22 Directory where data will be stored. Each chain will be stored under it's own specific subdirectory.
23 '';
24 };
25
26 address = mkOption {
27 type = types.str;
28 default = "0.0.0.0";
29 description = lib.mdDoc ''
30 Listen address of Beacon node.
31 '';
32 };
33
34 port = mkOption {
35 type = types.port;
36 default = 9000;
37 description = lib.mdDoc ''
38 Port number the Beacon node will be listening on.
39 '';
40 };
41
42 openFirewall = mkOption {
43 type = types.bool;
44 default = false;
45 description = lib.mdDoc ''
46 Open the port in the firewall
47 '';
48 };
49
50 disableDepositContractSync = mkOption {
51 type = types.bool;
52 default = false;
53 description = lib.mdDoc ''
54 Explicitly disables syncing of deposit logs from the execution node.
55 This overrides any previous option that depends on it.
56 Useful if you intend to run a non-validating beacon node.
57 '';
58 };
59
60 execution = {
61 address = mkOption {
62 type = types.str;
63 default = "127.0.0.1";
64 description = lib.mdDoc ''
65 Listen address for the execution layer.
66 '';
67 };
68
69 port = mkOption {
70 type = types.port;
71 default = 8551;
72 description = lib.mdDoc ''
73 Port number the Beacon node will be listening on for the execution layer.
74 '';
75 };
76
77 jwtPath = mkOption {
78 type = types.str;
79 default = "";
80 description = lib.mdDoc ''
81 Path for the jwt secret required to connect to the execution layer.
82 '';
83 };
84 };
85
86 http = {
87 enable = lib.mkEnableOption (lib.mdDoc "Beacon node http api");
88 port = mkOption {
89 type = types.port;
90 default = 5052;
91 description = lib.mdDoc ''
92 Port number of Beacon node RPC service.
93 '';
94 };
95
96 address = mkOption {
97 type = types.str;
98 default = "127.0.0.1";
99 description = lib.mdDoc ''
100 Listen address of Beacon node RPC service.
101 '';
102 };
103 };
104
105 metrics = {
106 enable = lib.mkEnableOption (lib.mdDoc "Beacon node prometheus metrics");
107 address = mkOption {
108 type = types.str;
109 default = "127.0.0.1";
110 description = lib.mdDoc ''
111 Listen address of Beacon node metrics service.
112 '';
113 };
114
115 port = mkOption {
116 type = types.port;
117 default = 5054;
118 description = lib.mdDoc ''
119 Port number of Beacon node metrics service.
120 '';
121 };
122 };
123
124 extraArgs = mkOption {
125 type = types.str;
126 description = lib.mdDoc ''
127 Additional arguments passed to the lighthouse beacon command.
128 '';
129 default = "";
130 example = "";
131 };
132 };
133 };
134 };
135
136 validator = mkOption {
137 description = lib.mdDoc "Validator node";
138 default = {};
139 type = types.submodule {
140 options = {
141 enable = mkOption {
142 type = types.bool;
143 default = false;
144 description = lib.mdDoc "Enable Lightouse Validator node.";
145 };
146
147 dataDir = mkOption {
148 type = types.str;
149 default = "/var/lib/lighthouse-validator";
150 description = lib.mdDoc ''
151 Directory where data will be stored. Each chain will be stored under it's own specific subdirectory.
152 '';
153 };
154
155 beaconNodes = mkOption {
156 type = types.listOf types.str;
157 default = ["http://localhost:5052"];
158 description = lib.mdDoc ''
159 Beacon nodes to connect to.
160 '';
161 };
162
163 metrics = {
164 enable = lib.mkEnableOption (lib.mdDoc "Validator node prometheus metrics");
165 address = mkOption {
166 type = types.str;
167 default = "127.0.0.1";
168 description = lib.mdDoc ''
169 Listen address of Validator node metrics service.
170 '';
171 };
172
173 port = mkOption {
174 type = types.port;
175 default = 5056;
176 description = lib.mdDoc ''
177 Port number of Validator node metrics service.
178 '';
179 };
180 };
181
182 extraArgs = mkOption {
183 type = types.str;
184 description = lib.mdDoc ''
185 Additional arguments passed to the lighthouse validator command.
186 '';
187 default = "";
188 example = "";
189 };
190 };
191 };
192 };
193
194 network = mkOption {
195 type = types.enum [ "mainnet" "prater" "goerli" "gnosis" "kiln" "ropsten" "sepolia" ];
196 default = "mainnet";
197 description = lib.mdDoc ''
198 The network to connect to. Mainnet is the default ethereum network.
199 '';
200 };
201
202 extraArgs = mkOption {
203 type = types.str;
204 description = lib.mdDoc ''
205 Additional arguments passed to every lighthouse command.
206 '';
207 default = "";
208 example = "";
209 };
210 };
211 };
212
213 config = mkIf (cfg.beacon.enable || cfg.validator.enable) {
214
215 environment.systemPackages = [ pkgs.lighthouse ] ;
216
217 networking.firewall = mkIf cfg.beacon.enable {
218 allowedTCPPorts = mkIf cfg.beacon.openFirewall [ cfg.beacon.port ];
219 allowedUDPPorts = mkIf cfg.beacon.openFirewall [ cfg.beacon.port ];
220 };
221
222
223 systemd.services.lighthouse-beacon = mkIf cfg.beacon.enable {
224 description = "Lighthouse beacon node (connect to P2P nodes and verify blocks)";
225 wantedBy = [ "multi-user.target" ];
226 after = [ "network.target" ];
227
228 script = ''
229 # make sure the chain data directory is created on first run
230 mkdir -p ${cfg.beacon.dataDir}/${cfg.network}
231
232 ${pkgs.lighthouse}/bin/lighthouse beacon_node \
233 --disable-upnp \
234 ${lib.optionalString cfg.beacon.disableDepositContractSync "--disable-deposit-contract-sync"} \
235 --port ${toString cfg.beacon.port} \
236 --listen-address ${cfg.beacon.address} \
237 --network ${cfg.network} \
238 --datadir ${cfg.beacon.dataDir}/${cfg.network} \
239 --execution-endpoint http://${cfg.beacon.execution.address}:${toString cfg.beacon.execution.port} \
240 --execution-jwt ''${CREDENTIALS_DIRECTORY}/LIGHTHOUSE_JWT \
241 ${lib.optionalString cfg.beacon.http.enable '' --http --http-address ${cfg.beacon.http.address} --http-port ${toString cfg.beacon.http.port}''} \
242 ${lib.optionalString cfg.beacon.metrics.enable '' --metrics --metrics-address ${cfg.beacon.metrics.address} --metrics-port ${toString cfg.beacon.metrics.port}''} \
243 ${cfg.extraArgs} ${cfg.beacon.extraArgs}
244 '';
245 serviceConfig = {
246 LoadCredential = "LIGHTHOUSE_JWT:${cfg.beacon.execution.jwtPath}";
247 DynamicUser = true;
248 Restart = "on-failure";
249 StateDirectory = "lighthouse-beacon";
250 ReadWritePaths = [ cfg.beacon.dataDir ];
251 NoNewPrivileges = true;
252 PrivateTmp = true;
253 ProtectHome = true;
254 ProtectClock = true;
255 ProtectProc = "noaccess";
256 ProcSubset = "pid";
257 ProtectKernelLogs = true;
258 ProtectKernelModules = true;
259 ProtectKernelTunables = true;
260 ProtectControlGroups = true;
261 ProtectHostname = true;
262 RestrictSUIDSGID = true;
263 RestrictRealtime = true;
264 RestrictNamespaces = true;
265 LockPersonality = true;
266 RemoveIPC = true;
267 SystemCallFilter = [ "@system-service" "~@privileged" ];
268 };
269 };
270
271 systemd.services.lighthouse-validator = mkIf cfg.validator.enable {
272 description = "Lighthouse validtor node (manages validators, using data obtained from the beacon node via a HTTP API)";
273 wantedBy = [ "multi-user.target" ];
274 after = [ "network.target" ];
275
276 script = ''
277 # make sure the chain data directory is created on first run
278 mkdir -p ${cfg.validator.dataDir}/${cfg.network}
279
280 ${pkgs.lighthouse}/bin/lighthouse validator_client \
281 --network ${cfg.network} \
282 --beacon-nodes ${lib.concatStringsSep "," cfg.validator.beaconNodes} \
283 --datadir ${cfg.validator.dataDir}/${cfg.network} \
284 ${optionalString cfg.validator.metrics.enable ''--metrics --metrics-address ${cfg.validator.metrics.address} --metrics-port ${toString cfg.validator.metrics.port}''} \
285 ${cfg.extraArgs} ${cfg.validator.extraArgs}
286 '';
287
288 serviceConfig = {
289 Restart = "on-failure";
290 StateDirectory = "lighthouse-validator";
291 ReadWritePaths = [ cfg.validator.dataDir ];
292 CapabilityBoundingSet = "";
293 DynamicUser = true;
294 NoNewPrivileges = true;
295 PrivateTmp = true;
296 ProtectHome = true;
297 ProtectClock = true;
298 ProtectProc = "noaccess";
299 ProcSubset = "pid";
300 ProtectKernelLogs = true;
301 ProtectKernelModules = true;
302 ProtectKernelTunables = true;
303 ProtectControlGroups = true;
304 ProtectHostname = true;
305 RestrictSUIDSGID = true;
306 RestrictRealtime = true;
307 RestrictNamespaces = true;
308 LockPersonality = true;
309 RemoveIPC = true;
310 RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ];
311 SystemCallFilter = [ "@system-service" "~@privileged" ];
312 };
313 };
314 };
315}