btw i use nix
1{
2 pkgs,
3 config,
4 lib,
5 eon,
6 ...
7}@inputs:
8
9let
10 vpnRecords = [
11 {
12 name = "nix-cache.vpn.${config.networking.domain}.";
13 type = "A";
14 value = "100.64.0.9";
15 }
16 {
17 name = "jellyfin.vpn.${config.networking.domain}.";
18 type = "A";
19 value = "100.64.0.9";
20 }
21 {
22 name = "nextcloud.vpn.${config.networking.domain}.";
23 type = "A";
24 value = "100.64.0.9";
25 }
26 {
27 name = "transmission.vpn.${config.networking.domain}.";
28 type = "A";
29 value = "100.64.0.9";
30 }
31 {
32 name = "owntracks.vpn.${config.networking.domain}.";
33 type = "A";
34 value = "100.64.0.9";
35 }
36 {
37 name = "immich.vpn.${config.networking.domain}.";
38 type = "A";
39 value = "100.64.0.9";
40 }
41 ];
42in
43{
44 imports = [
45 ./hardware-configuration.nix
46 ./minimal.nix
47 ../../modules/colour-guesser.nix
48 ../../modules/ryan-website.nix
49 ../../modules/alec-website.nix
50 ../../modules/fn06-website.nix
51 ];
52
53 age.secrets.eon-capnp = {
54 file = ../../secrets/eon-capnp.age;
55 mode = "770";
56 owner = "eon";
57 group = "eon";
58 };
59 age.secrets.eon-sirref-primary = {
60 file = ../../secrets/eon-sirref-primary.cap.age;
61 mode = "770";
62 owner = "eon";
63 group = "eon";
64 };
65 services.eon = {
66 capnpSecretKeyFile = config.age.secrets.eon-capnp.path;
67 primaries = [ config.age.secrets.eon-sirref-primary.path ];
68 prod = true;
69 capnpAddress = "135.181.100.27";
70 logLevel = 0;
71 };
72
73 security.acme-eon = {
74 acceptTerms = true;
75 package = eon.defaultPackage.${config.nixpkgs.hostPlatform.system};
76 defaults.email = "${config.custom.username}@${config.networking.domain}";
77 defaults.capFile = "/var/lib/eon/caps/domain/freumh.org.cap";
78 certs = {
79 "fn06.org".capFile = "/var/lib/eon/caps/domain/fn06.org.cap";
80 "capybara.fn06.org".capFile = "/var/lib/eon/caps/domain/fn06.org.cap";
81 };
82 };
83
84 eilean = {
85 username = config.custom.username;
86 serverIpv4 = "135.181.100.27";
87 serverIpv6 = "2a01:4f9:c011:87ad:0:0:0:0";
88 acme-eon = true;
89 fail2ban.enable = true;
90 };
91 networking.domain = lib.mkDefault "freumh.org";
92 eilean.publicInterface = "enp1s0";
93 eilean.mailserver.enable = true;
94 eilean.radicale = {
95 enable = true;
96 users = null;
97 };
98 age.secrets.matrix-shared-secret = {
99 file = ../../secrets/matrix-shared-secret.age;
100 mode = "770";
101 owner = "${config.systemd.services.matrix-synapse.serviceConfig.User}";
102 group = "${config.systemd.services.matrix-synapse.serviceConfig.Group}";
103 };
104 eilean.matrix = {
105 enable = true;
106 registrationSecretFile = config.age.secrets.matrix-shared-secret.path;
107 bridges.whatsapp = true;
108 bridges.signal = true;
109 bridges.instagram = true;
110 bridges.messenger = true;
111 };
112 eilean.turn.enable = true;
113 eilean.mastodon.enable = true;
114 eilean.headscale.enable = true;
115 #eilean.dns.enable = lib.mkForce false;
116
117 systemd.services.matrix-as-meta = {
118 # voice messages need `ffmpeg`
119 path = [ pkgs.ffmpeg ];
120 };
121
122 custom = {
123 freumh.enable = true;
124 rmfakecloud.enable = true;
125 website = {
126 ryan = {
127 enable = true;
128 cname = "vps";
129 };
130 alec = {
131 enable = true;
132 cname = "vps";
133 };
134 fn06 = {
135 enable = true;
136 cname = "vps";
137 domain = "fn06.org";
138 };
139 colour-guesser = {
140 # enable = true;
141 cname = "vps";
142 };
143 };
144 };
145
146 eilean.dns.nameservers = [ "ns1" ];
147 eilean.services.dns.zones = {
148 ${config.networking.domain} = {
149 ttl = 300;
150 soa = {
151 serial = 2018011660;
152 refresh = 300;
153 };
154 records = [
155 {
156 name = "@";
157 type = "TXT";
158 value = "google-site-verification=rEvwSqf7RYKRQltY412qMtTuoxPp64O3L7jMotj9Jnc";
159 }
160 {
161 name = "_atproto.ryan";
162 type = "TXT";
163 value = "did=did:plc:3lfhu6ehlynzjgehef6alnvg";
164 }
165
166 {
167 name = "teapot";
168 type = "CNAME";
169 value = "vps";
170 }
171
172 {
173 name = "@";
174 type = "NS";
175 value = "ns1.sirref.org.";
176 }
177
178 {
179 name = "@";
180 type = "A";
181 value = config.eilean.serverIpv4;
182 }
183 {
184 name = "@";
185 type = "AAAA";
186 value = config.eilean.serverIpv6;
187 }
188 {
189 name = "vps";
190 type = "A";
191 value = config.eilean.serverIpv4;
192 }
193 {
194 name = "vps";
195 type = "AAAA";
196 value = config.eilean.serverIpv6;
197 }
198
199 {
200 name = "@";
201 type = "LOC";
202 value = "52 12 40.4 N 0 5 31.9 E 22m 10m 10m 10m";
203 }
204
205 {
206 name = "ns.cl";
207 type = "A";
208 value = "128.232.113.136";
209 }
210 {
211 name = "cl";
212 type = "NS";
213 value = "ns.cl";
214 }
215
216 {
217 name = "ns1.eilean";
218 type = "A";
219 value = "65.109.10.223";
220 }
221 {
222 name = "eilean";
223 type = "NS";
224 value = "ns1.eilean";
225 }
226
227 {
228 name = "shrew";
229 type = "CNAME";
230 value = "vps";
231 }
232
233 # generate with
234 # sudo openssl x509 -in /var/lib/acme/mail.freumh.org/fullchain.pem -pubkey -noout | openssl pkey -pubin -outform der | sha256sum | awk '{print "3 1 1", $1}'
235 {
236 name = "_25._tcp.mail";
237 type = "TLSA";
238 value = "3 1 1 2f0fd413f063c75141937dd196a9f4ab66139d599e0dcf2a7ce6d557647e26a6";
239 }
240 # generate with
241 # for i in r3 e1 r4-cross-signed e2
242 # openssl x509 -in ~/downloads/lets-encrypt-$i.pem -pubkey -noout | openssl pkey -pubin -outform der | sha256sum | awk '{print "2 1 1", $1}'
243 # LE R3
244 {
245 name = "_25._tcp.mail";
246 type = "TLSA";
247 value = "2 1 1 8d02536c887482bc34ff54e41d2ba659bf85b341a0a20afadb5813dcfbcf286d";
248 }
249 # LE E1
250 {
251 name = "_25._tcp.mail";
252 type = "TLSA";
253 value = "2 1 1 276fe8a8c4ec7611565bf9fce6dcace9be320c1b5bea27596b2204071ed04f10";
254 }
255 # LE R4
256 {
257 name = "_25._tcp.mail";
258 type = "TLSA";
259 value = "2 1 1 e5545e211347241891c554a03934cde9b749664a59d26d615fe58f77990f2d03";
260 }
261 # LE E2
262 {
263 name = "_25._tcp.mail";
264 type = "TLSA";
265 value = "2 1 1 bd936e72b212ef6f773102c6b77d38f94297322efc25396bc3279422e0c89270";
266 }
267 ] ++ vpnRecords;
268 };
269 "fn06.org" = {
270 soa.serial = 1706745602;
271 records = [
272 {
273 name = "@";
274 type = "NS";
275 value = "ns1";
276 }
277 {
278 name = "@";
279 type = "NS";
280 value = "ns2";
281 }
282
283 {
284 name = "ns1";
285 type = "A";
286 value = config.eilean.serverIpv4;
287 }
288 {
289 name = "ns1";
290 type = "AAAA";
291 value = config.eilean.serverIpv6;
292 }
293 {
294 name = "ns2";
295 type = "A";
296 value = config.eilean.serverIpv4;
297 }
298 {
299 name = "ns2";
300 type = "AAAA";
301 value = config.eilean.serverIpv6;
302 }
303
304 {
305 name = "@";
306 type = "A";
307 value = config.eilean.serverIpv4;
308 }
309 {
310 name = "@";
311 type = "AAAA";
312 value = config.eilean.serverIpv6;
313 }
314
315 {
316 name = "www.fn06.org.";
317 type = "CNAME";
318 value = "fn06.org.";
319 }
320
321 {
322 name = "@";
323 type = "LOC";
324 value = "52 12 40.4 N 0 5 31.9 E 22m 10m 10m 10m";
325 }
326
327 {
328 name = "capybara.fn06.org.";
329 type = "CNAME";
330 value = "fn06.org.";
331 }
332
333 {
334 name = "jellyfin.${config.networking.domain}.";
335 type = "AAAA";
336 value = "2a00:23c6:aa22:e401:8dff:9b9a:cb3c:3fcb";
337 }
338 {
339 name = "jellyseerr.${config.networking.domain}.";
340 type = "AAAA";
341 value = "2a00:23c6:aa22:e401:8dff:9b9a:cb3c:3fcb";
342 }
343 ];
344 };
345 };
346 services.bind.zones.${config.networking.domain}.extraConfig =
347 ''
348 dnssec-policy default;
349 inline-signing yes;
350 journal "${config.services.bind.directory}/${config.networking.domain}.signed.jnl";
351 ''
352 +
353 # dig ns org +short | xargs dig +short
354 # replace with `checkds true;` in bind 9.20
355 ''
356 parental-agents {
357 199.19.56.1;
358 199.249.112.1;
359 199.19.54.1;
360 199.249.120.1;
361 199.19.53.1;
362 199.19.57.1;
363 };
364 '';
365
366 services.nginx.commonHttpConfig = ''
367 add_header Strict-Transport-Security max-age=31536000 always;
368 add_header X-Frame-Options SAMEORIGIN always;
369 add_header X-Content-Type-Options nosniff always;
370 add_header Content-Security-Policy "default-src 'self' 'unsafe-inline' 'unsafe-eval' blob:; base-uri 'self'; frame-src 'self'; frame-ancestors 'self'; form-action 'self';" always;
371 add_header Referrer-Policy 'same-origin';
372 '';
373 services.nginx.virtualHosts."teapot.${config.networking.domain}" = {
374 extraConfig = ''
375 return 418;
376 '';
377 };
378 age.secrets.website-phd = {
379 file = ../../secrets/website-phd.age;
380 mode = "770";
381 owner = "${config.systemd.services.nginx.serviceConfig.User}";
382 group = "${config.systemd.services.nginx.serviceConfig.Group}";
383 };
384 services.nginx.virtualHosts."${config.custom.website.ryan.domain}" = {
385 locations."/phd/" = {
386 basicAuthFile = config.age.secrets.website-phd.path;
387 };
388 };
389
390 security.acme-eon.nginxCerts = [
391 "capybara.fn06.org"
392 "shrew.freumh.org"
393 ];
394 services.nginx.virtualHosts."capybara.fn06.org" = {
395 forceSSL = true;
396 locations."/" = {
397 proxyPass = ''
398 http://100.64.0.10:8123
399 '';
400 proxyWebsockets = true;
401 };
402 };
403 services.nginx.virtualHosts."shrew.freumh.org" = {
404 forceSSL = true;
405 locations."/" = {
406 # need to specify ip or there's a bootstrap problem with headscale
407 proxyPass = ''
408 http://100.64.0.6:8123
409 '';
410 proxyWebsockets = true;
411 };
412 };
413
414 services.mastodon = {
415 webProcesses = lib.mkForce 1;
416 webThreads = lib.mkForce 1;
417 sidekiqThreads = lib.mkForce 1;
418 streamingProcesses = lib.mkForce 1;
419 };
420
421 boot.kernel.sysctl = {
422 "net.ipv4.ip_forward" = 1;
423 "net.ipv6.conf.all.forwarding" = 1;
424 };
425
426 services.headscale.settings.dns = {
427 extra_records = vpnRecords;
428 base_domain = "vpn.freumh.org";
429 nameservers.global = config.networking.nameservers;
430 };
431
432 age.secrets.restic-owl.file = ../../secrets/restic-owl.age;
433 services.restic.backups.${config.networking.hostName} = {
434 repository = "rest:http://100.64.0.9:8000/${config.networking.hostName}/";
435 passwordFile = config.age.secrets.restic-owl.path;
436 initialize = true;
437 paths = [
438 "/var/"
439 "/run/"
440 "/etc/"
441 ];
442 timerConfig = {
443 OnCalendar = "03:00";
444 randomizedDelaySec = "1hr";
445 };
446 };
447
448 nix = {
449 gc = {
450 automatic = true;
451 dates = lib.mkForce "03:00";
452 randomizedDelaySec = "1hr";
453 options = lib.mkForce "--delete-older-than 2d";
454 };
455 };
456
457 age.secrets.email-ryan.file = ../../secrets/email-ryan.age;
458 age.secrets.email-system.file = ../../secrets/email-system.age;
459 eilean.mailserver.systemAccountPasswordFile = config.age.secrets.email-system.path;
460 mailserver.loginAccounts = {
461 "${config.eilean.username}@${config.networking.domain}" = {
462 passwordFile = config.age.secrets.email-ryan.path;
463 aliases = [
464 "dns@${config.networking.domain}"
465 "postmaster@${config.networking.domain}"
466 ];
467 sieveScript = ''
468 require ["fileinto", "mailbox"];
469
470 if header :contains ["to", "cc"] ["~rjarry/aerc-discuss@lists.sr.ht"] {
471 fileinto :create "lists.aerc";
472 stop;
473 }
474 '';
475 };
476 "misc@${config.networking.domain}" = {
477 passwordFile = config.age.secrets.email-ryan.path;
478 catchAll = [ "${config.networking.domain}" ];
479 };
480 "system@${config.networking.domain}" = {
481 aliases = [ "nas@${config.networking.domain}" ];
482 };
483 };
484
485 services.minecraft-server = {
486 enable = true;
487 package = pkgs.overlay-unstable.minecraft-server;
488 eula = true;
489 openFirewall = true;
490 };
491
492 networking.firewall.allowedTCPPorts = [ 7001 ];
493
494 services.openssh.openFirewall = true;
495}