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