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}