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