summaryrefslogtreecommitdiff
path: root/hosts
diff options
context:
space:
mode:
Diffstat (limited to 'hosts')
-rw-r--r--hosts/sif/default.nix96
-rw-r--r--hosts/sif/email/default.nix110
-rw-r--r--hosts/sif/email/relay.crt11
-rw-r--r--hosts/sif/email/relay.key19
-rw-r--r--hosts/sif/email/secrets.yaml (renamed from hosts/sif/mail/secrets.yaml)0
-rw-r--r--hosts/sif/greetd/default.nix5
-rw-r--r--hosts/sif/mail/default.nix70
-rw-r--r--hosts/sif/ruleset.nft8
-rw-r--r--hosts/surtr/audiobookshelf.nix66
-rw-r--r--hosts/surtr/bifrost/default.nix4
-rw-r--r--hosts/surtr/default.nix21
-rw-r--r--hosts/surtr/dns/Gupfile2
-rw-r--r--hosts/surtr/dns/default.nix8
-rw-r--r--hosts/surtr/dns/key.gup2
-rw-r--r--hosts/surtr/dns/keys/audiobookshelf.yggdrasil.li_acme19
-rw-r--r--hosts/surtr/dns/keys/hledger.yggdrasil.li_acme24
-rw-r--r--hosts/surtr/dns/keys/kimai.yggdrasil.li_acme19
-rw-r--r--hosts/surtr/dns/keys/paperless.yggdrasil.li_acme24
-rw-r--r--hosts/surtr/dns/zones/email.nights.soa8
-rw-r--r--hosts/surtr/dns/zones/li.141.soa11
-rw-r--r--hosts/surtr/dns/zones/li.kleen.soa9
-rw-r--r--hosts/surtr/dns/zones/li.synapse.soa2
-rw-r--r--hosts/surtr/dns/zones/li.xmpp.soa43
-rw-r--r--hosts/surtr/dns/zones/li.yggdrasil.soa39
-rw-r--r--hosts/surtr/dns/zones/org.dirty-haskell.soa34
-rw-r--r--hosts/surtr/dns/zones/org.praseodym.soa9
-rw-r--r--hosts/surtr/email/ccert-policy-server/ccert_policy_server/__main__.py16
-rw-r--r--hosts/surtr/email/default.nix145
-rw-r--r--hosts/surtr/email/internal-policy-server/.envrc4
-rw-r--r--hosts/surtr/email/internal-policy-server/.gitignore2
-rw-r--r--hosts/surtr/email/internal-policy-server/internal_policy_server/__init__.py0
-rw-r--r--hosts/surtr/email/internal-policy-server/internal_policy_server/__main__.py106
-rw-r--r--hosts/surtr/email/internal-policy-server/pyproject.toml18
-rw-r--r--hosts/surtr/email/internal-policy-server/uv.lock119
-rw-r--r--hosts/surtr/hledger.nix66
-rw-r--r--hosts/surtr/kimai.nix68
-rw-r--r--hosts/surtr/paperless.nix66
-rw-r--r--hosts/surtr/postgresql/default.nix62
-rw-r--r--hosts/surtr/tls/tsig_key.gup2
-rw-r--r--hosts/surtr/tls/tsig_keys/audiobookshelf.yggdrasil.li19
-rw-r--r--hosts/surtr/tls/tsig_keys/hledger.yggdrasil.li24
-rw-r--r--hosts/surtr/tls/tsig_keys/kimai.yggdrasil.li19
-rw-r--r--hosts/surtr/tls/tsig_keys/paperless.yggdrasil.li24
-rw-r--r--hosts/surtr/vpn/default.nix8
-rw-r--r--hosts/surtr/vpn/geri.pub2
-rw-r--r--hosts/surtr/zfs.nix2
-rw-r--r--hosts/vidhar/audiobookshelf/abs-podcast-autoplaylist-gkleen.toml19
-rw-r--r--hosts/vidhar/audiobookshelf/default.nix21
-rw-r--r--hosts/vidhar/default.nix2
-rw-r--r--hosts/vidhar/hledger/default.nix83
-rw-r--r--hosts/vidhar/hledger/htpasswd24
-rw-r--r--hosts/vidhar/kimai/default.nix89
-rw-r--r--hosts/vidhar/kimai/ruleset.nft149
-rw-r--r--hosts/vidhar/network/default.nix9
-rw-r--r--hosts/vidhar/network/dhcp/default.nix221
-rw-r--r--hosts/vidhar/network/ruleset.nft24
-rw-r--r--hosts/vidhar/paperless/default.nix25
-rw-r--r--hosts/vidhar/paperless/rootpw24
-rw-r--r--hosts/vidhar/prometheus/default.nix3
59 files changed, 1750 insertions, 378 deletions
diff --git a/hosts/sif/default.nix b/hosts/sif/default.nix
index 32651e14..6214569a 100644
--- a/hosts/sif/default.nix
+++ b/hosts/sif/default.nix
@@ -12,7 +12,7 @@ let
12in { 12in {
13 imports = with flake.nixosModules.systemProfiles; [ 13 imports = with flake.nixosModules.systemProfiles; [
14 ./hw.nix 14 ./hw.nix
15 ./mail ./libvirt ./greetd 15 ./email ./libvirt ./greetd
16 tmpfs-root bcachefs initrd-all-crypto-modules default-locale openssh rebuild-machines niri-unstable networkmanager 16 tmpfs-root bcachefs initrd-all-crypto-modules default-locale openssh rebuild-machines niri-unstable networkmanager
17 flakeInputs.nixos-hardware.nixosModules.lenovo-thinkpad-p1 17 flakeInputs.nixos-hardware.nixosModules.lenovo-thinkpad-p1
18 flakeInputs.impermanence.nixosModules.impermanence 18 flakeInputs.impermanence.nixosModules.impermanence
@@ -26,9 +26,6 @@ in {
26 allowUnfree = true; 26 allowUnfree = true;
27 pulseaudio = true; 27 pulseaudio = true;
28 }; 28 };
29 extraOverlays = [
30 flakeInputs.niri-flake.overlays.niri
31 ];
32 }; 29 };
33 30
34 time.timeZone = null; 31 time.timeZone = null;
@@ -55,6 +52,7 @@ in {
55 systemd-boot = { 52 systemd-boot = {
56 enable = true; 53 enable = true;
57 configurationLimit = 15; 54 configurationLimit = 15;
55 netbootxyz.enable = true;
58 }; 56 };
59 efi.canTouchEfiVariables = true; 57 efi.canTouchEfiVariables = true;
60 timeout = null; 58 timeout = null;
@@ -128,40 +126,16 @@ in {
128 rulesetFile = ./ruleset.nft; 126 rulesetFile = ./ruleset.nft;
129 }; 127 };
130 128
131 # firewall = {
132 # enable = true;
133 # allowedTCPPorts = [ 22 # ssh
134 # 8000 # quickserve
135 # ];
136 # };
137
138 # wlanInterfaces = {
139 # wlan0 = {
140 # device = "wlp82s0";
141 # };
142 # };
143
144 # bonds = {
145 # "lan" = {
146 # interfaces = [ "wlan0" "enp0s31f6" "dock0" ];
147 # driverOptions = {
148 # miimon = "1000";
149 # mode = "active-backup";
150 # primary_reselect = "always";
151 # };
152 # };
153 # };
154
155 useDHCP = false; 129 useDHCP = false;
156 useNetworkd = true; 130 useNetworkd = true;
157
158 # interfaces."tinc.yggdrasil" = {
159 # virtual = true;
160 # virtualType = config.services.tinc.networks.yggdrasil.interfaceType;
161 # macAddress = "5c:93:21:c3:61:39";
162 # };
163 }; 131 };
164 132
133 environment.etc."NetworkManager/dnsmasq.d/dnssec.conf" = {
134 text = ''
135 conf-file=${pkgs.dnsmasq}/share/dnsmasq/trust-anchors.conf
136 dnssec
137 '';
138 };
165 environment.etc."NetworkManager/dnsmasq.d/libvirt_dnsmasq.conf" = { 139 environment.etc."NetworkManager/dnsmasq.d/libvirt_dnsmasq.conf" = {
166 text = '' 140 text = ''
167 except-interface=virbr0 141 except-interface=virbr0
@@ -404,19 +378,6 @@ in {
404 ]; 378 ];
405 379
406 services = { 380 services = {
407 uucp = {
408 enable = true;
409 nodeName = "sif";
410 remoteNodes = {
411 "ymir" = {
412 publicKeys = ["ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIG6KNtsCOl5fsZ4rV7udTulGMphJweLBoKapzerWNoLY root@ymir"];
413 hostnames = ["ymir.yggdrasil.li" "ymir.niflheim.yggdrasil"];
414 };
415 };
416
417 defaultCommands = lib.mkForce [];
418 };
419
420 avahi.enable = true; 381 avahi.enable = true;
421 382
422 fwupd.enable = true; 383 fwupd.enable = true;
@@ -435,8 +396,8 @@ in {
435 396
436 logind = { 397 logind = {
437 lidSwitch = "suspend"; 398 lidSwitch = "suspend";
438 lidSwitchDocked = "lock"; 399 lidSwitchDocked = "ignore";
439 lidSwitchExternalPower = "lock"; 400 lidSwitchExternalPower = "ignore";
440 }; 401 };
441 402
442 atd = { 403 atd = {
@@ -472,15 +433,17 @@ in {
472 libinput.enable = true; 433 libinput.enable = true;
473 434
474 envfs.enable = false; 435 envfs.enable = false;
436
437 displayManager.defaultSession = "Niri";
475 }; 438 };
476 439
477 systemd.tmpfiles.settings = { 440 systemd.tmpfiles.settings = {
478 "10-localtime"."/etc/localtime".L.argument = "/.bcachefs/etc/localtime"; 441 "10-localtime"."/etc/localtime".L.argument = "/.bcachefs/etc/localtime";
479 442
480 "10-regreet"."/var/cache/regreet/cache.toml".C.argument = toString ((pkgs.formats.toml {}).generate "cache.toml" { 443 # "10-regreet"."/var/cache/regreet/cache.toml".C.argument = toString ((pkgs.formats.toml {}).generate "cache.toml" {
481 last_user = "gkleen"; 444 # last_user = "gkleen";
482 user_to_last_sess.gkleen = "niri"; 445 # user_to_last_sess.gkleen = "Niri";
483 }); 446 # });
484 }; 447 };
485 448
486 users = { 449 users = {
@@ -640,25 +603,6 @@ in {
640 603
641 environment.etc."X11/xorg.conf.d/50-wacom.conf".source = lib.mkForce ./wacom.conf; 604 environment.etc."X11/xorg.conf.d/50-wacom.conf".source = lib.mkForce ./wacom.conf;
642 605
643 systemd.services."ac-plugged" = {
644 description = "Inhibit handling of lid-switch and sleep";
645
646 path = with pkgs; [ systemd coreutils ];
647
648 script = ''
649 exec systemd-inhibit --what=handle-lid-switch --why="AC is connected" --mode=block sleep infinity
650 '';
651
652 serviceConfig = {
653 Type = "simple";
654 };
655 };
656
657 services.udev.extraRules = with pkgs; lib.mkAfter ''
658 SUBSYSTEM=="power_supply", ENV{POWER_SUPPLY_ONLINE}=="0", RUN+="${systemd}/bin/systemctl --no-block stop ac-plugged.service"
659 SUBSYSTEM=="power_supply", ENV{POWER_SUPPLY_ONLINE}=="1", RUN+="${systemd}/bin/systemctl --no-block start ac-plugged.service"
660 '';
661
662 systemd.services."nix-daemon".serviceConfig = { 606 systemd.services."nix-daemon".serviceConfig = {
663 MemoryAccounting = true; 607 MemoryAccounting = true;
664 MemoryHigh = "50%"; 608 MemoryHigh = "50%";
@@ -718,7 +662,7 @@ in {
718 directories = [ 662 directories = [
719 "/nix" 663 "/nix"
720 "/root" 664 "/root"
721 "/home" 665 "/home"
722 "/var/log" 666 "/var/log"
723 "/var/lib/sops-nix" 667 "/var/lib/sops-nix"
724 "/var/lib/nixos" 668 "/var/lib/nixos"
@@ -729,8 +673,6 @@ in {
729 "/var/lib/upower" 673 "/var/lib/upower"
730 "/var/lib/postfix" 674 "/var/lib/postfix"
731 "/etc/NetworkManager/system-connections" 675 "/etc/NetworkManager/system-connections"
732 { directory = "/var/uucp"; user = "uucp"; group = "uucp"; mode = "0700"; }
733 { directory = "/var/spool/uucp"; user = "uucp"; group = "uucp"; mode = "0750"; }
734 ]; 676 ];
735 files = [ 677 files = [
736 ]; 678 ];
@@ -751,10 +693,6 @@ in {
751 693
752 home-manager.sharedModules = [ flakeInputs.nixVirt.homeModules.default ]; 694 home-manager.sharedModules = [ flakeInputs.nixVirt.homeModules.default ];
753 695
754 environment.pathsToLink = [
755 "share/zsh"
756 ];
757
758 system.stateVersion = "24.11"; 696 system.stateVersion = "24.11";
759 }; 697 };
760} 698}
diff --git a/hosts/sif/email/default.nix b/hosts/sif/email/default.nix
new file mode 100644
index 00000000..4eda236e
--- /dev/null
+++ b/hosts/sif/email/default.nix
@@ -0,0 +1,110 @@
1{ config, lib, pkgs, ... }:
2{
3 services.postfix = {
4 enable = true;
5 enableSmtp = false;
6 enableSubmission = false;
7 setSendmail = true;
8 networksStyle = "host";
9 hostname = "sif.midgard.yggdrasil";
10 destination = [];
11 recipientDelimiter = "+";
12 config = {
13 mydomain = "yggdrasil.li";
14
15 local_transport = "error:5.1.1 No local delivery";
16 alias_database = [];
17 alias_maps = [];
18 local_recipient_maps = [];
19
20 inet_interfaces = "loopback-only";
21
22 message_size_limit = "0";
23
24 authorized_submit_users = "inline:{ gkleen= }";
25 authorized_flush_users = "inline:{ gkleen= }";
26 authorized_mailq_users = "inline:{ gkleen= }";
27
28 smtp_generic_maps = "inline:{ root=root+sif }";
29
30 mynetworks = ["127.0.0.0/8" "[::1]/128"];
31 smtpd_client_restrictions = ["permit_mynetworks" "reject"];
32 smtpd_relay_restrictions = ["permit_mynetworks" "reject"];
33
34 sender_dependent_default_transport_maps = ''regexp:${pkgs.writeText "sender_relay" ''
35 /@(cip|stud)\.ifi\.(lmu|uni-muenchen)\.de$/ smtp:smtp.ifi.lmu.de
36 /@ifi\.(lmu|uni-muenchen)\.de$/ smtp:smtpin1.ifi.lmu.de:587
37 /@math(ematik)?\.(lmu|uni-muenchen)\.de$/ smtps:smtp.math.lmu.de:465
38 /@(campus\.)?lmu\.de$/ smtp:postout.lrz.de
39 ''}'';
40 sender_bcc_maps = ''regexp:${pkgs.writeText "sender_bcc" ''
41 /^uni2work(-[^@]*)?@ifi\.lmu\.de$/ uni2work@ifi.lmu.de
42 /@ifi\.lmu\.de$/ gregor.kleen@ifi.lmu.de
43 ''}'';
44 relayhost = "[surtr.yggdrasil.li]:465";
45 default_transport = "relay";
46
47 smtp_sasl_auth_enable = true;
48 smtp_sender_dependent_authentication = true;
49 smtp_sasl_tls_security_options = "noanonymous";
50 smtp_sasl_mechanism_filter = ["plain"];
51 smtp_sasl_password_maps = "regexp:/run/credentials/postfix.service/sasl_passwd";
52 smtp_cname_overrides_servername = false;
53 smtp_always_send_ehlo = true;
54 smtp_tls_security_level = "dane";
55
56 smtp_tls_loglevel = "1";
57 smtp_dns_support_level = "dnssec";
58 };
59 masterConfig = {
60 submission = {
61 type = "inet";
62 private = false;
63 command = "smtpd";
64 args = [
65 "-o" "syslog_name=postfix/$service_name"
66 ];
67 };
68 smtp = { };
69 smtps = {
70 type = "unix";
71 private = true;
72 privileged = true;
73 chroot = false;
74 command = "smtp";
75 args = [
76 "-o" "smtp_tls_wrappermode=yes"
77 "-o" "smtp_tls_security_level=encrypt"
78 ];
79 };
80 relay = {
81 command = "smtp";
82 args = [
83 "-o" "smtp_fallback_relay="
84 "-o" "smtp_tls_security_level=verify"
85 "-o" "smtp_tls_wrappermode=yes"
86 "-o" "smtp_tls_cert_file=${./relay.crt}"
87 "-o" "smtp_tls_key_file=/run/credentials/postfix.service/relay.key"
88 ];
89 };
90 };
91 };
92
93 systemd.services.postfix = {
94 serviceConfig.LoadCredential = [
95 "sasl_passwd:${config.sops.secrets."postfix-sasl-passwd".path}"
96 "relay.key:${config.sops.secrets."relay-key".path}"
97 ];
98 };
99
100 sops.secrets = {
101 postfix-sasl-passwd = {
102 key = "sasl-passwd";
103 sopsFile = ./secrets.yaml;
104 };
105 relay-key = {
106 format = "binary";
107 sopsFile = ./relay.key;
108 };
109 };
110}
diff --git a/hosts/sif/email/relay.crt b/hosts/sif/email/relay.crt
new file mode 100644
index 00000000..ac13e7cb
--- /dev/null
+++ b/hosts/sif/email/relay.crt
@@ -0,0 +1,11 @@
1-----BEGIN CERTIFICATE-----
2MIIBjDCCAQygAwIBAgIPQAAAAGgLfNoL/PSMAsutMAUGAytlcTAXMRUwEwYDVQQD
3DAx5Z2dkcmFzaWwubGkwHhcNMjUwNDI1MTIwOTQ1WhcNMzUwNDI2MTIxNDQ1WjAR
4MQ8wDQYDVQQDDAZna2xlZW4wKjAFBgMrZXADIQB3outi3/3F4YO7Q97WAAaMHW0a
5m+Blldrgee+EZnWnD6N1MHMwHwYDVR0jBBgwFoAUTtn+VjMw6Ge1f68KD8dT1CWn
6l3YwHQYDVR0OBBYEFFOa4rYZYMbXUVdKv98NB504GUhjMA4GA1UdDwEB/wQEAwID
76DAMBgNVHRMBAf8EAjAAMBMGA1UdJQQMMAoGCCsGAQUFBwMCMAUGAytlcQNzABC0
80UgIt7gLZrU1TmzGoqPBris8R1DbKOJacicF5CU0MIIjHcX7mPFW8KtB4qm6KcPq
9kF6IaEPmgKpX3Nubk8HJik9vhIy9ysfINcVTvzXx8pO1bxbvREJRyA/apj10nzav
10yauId0cXHvN6g5RLAMsMAA==
11-----END CERTIFICATE-----
diff --git a/hosts/sif/email/relay.key b/hosts/sif/email/relay.key
new file mode 100644
index 00000000..412a44e0
--- /dev/null
+++ b/hosts/sif/email/relay.key
@@ -0,0 +1,19 @@
1{
2 "data": "ENC[AES256_GCM,data:lBlTuzOS75pvRmcTKT4KhHMH44RlE2SvCFAUP+GfsXws1Uai7DZ1MmbhvxxCa+pcLW19+sQYxrXLRNZWby1yOeKBJ2UQeYV5LOk9LSL/WIE3FZkCo5Dv0O0gSFKjjb61WN22a4JnHbLWADf/mLT3GZv91XfvFDo=,iv:ho8wQH3UNzX9JPW5gVcUGtxZzdVwsMFus0Z4KYe5t48=,tag:dAgZyHOva2xVVhE1nTl+lg==,type:str]",
3 "sops": {
4 "age": [
5 {
6 "recipient": "age1rmmhetcmllq0ahl5qznlr0eya2zdxwl9h6y5wnl97d2wtyx5t99sm2u866",
7 "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB6eTVRSUdFNUZGZmcxSUlT\nWmlsOGNyWXIzMGNTZjlKbXlhcEdZUXFRVkR3Cll0T0RMd0h2UW16QkR3SHlhYmNZ\nNDFrYXh3Rkp5NWsvcWc3UFJJaHVwT1UKLS0tIHhXVEI0VHBZVkpDQ1FzWENjMmJH\nb1FQWXVUUTBiZ1pKWG00MTNqVEo2SjAKK3VOU+QgRuxWYWEcrJiVMRFCprBICz4F\ngD+9zuPUzPezyJkYwTs+M+wX5GYkXppqm5W58yQLS2UDD38sr+SRjg==\n-----END AGE ENCRYPTED FILE-----\n"
8 },
9 {
10 "recipient": "age1fj65apkhfkrwyv5tx6zcs9nkjg8267fy733qph30sc7zfn7vapjqkd5kne",
11 "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBzWmJmZDVFazN2bDY1TkNG\nNXpJN2twMFFjZUxMTVdSNzJwQTFiYktrcGdrCjk4eFVHTko0bFVMSlFFWm9tbjMr\nbWNHMEQ1Rm1qUVhodlB1RGw2aDc4TUEKLS0tIERBK0J5NkN4OXJEZ1ZOZXhNc1Jm\naWNnUmZGbTIxdmNkYi9TZ2h2bGs3MVEKPQGaEf7M/5/xvSOfawpIp50fB3QfFSuz\nPgkrPMneaBeUx+uBYMyEFX4rpzLIBR3pnYMjAfoc+bjWaOtGQuEqyQ==\n-----END AGE ENCRYPTED FILE-----\n"
12 }
13 ],
14 "lastmodified": "2025-04-25T12:14:44Z",
15 "mac": "ENC[AES256_GCM,data:pObl2bJA93az9E3Ya+hA3ekI8TKKZ9NNTi0KzmWZBOiQwi9FuQYtpnmmT80L1KXWyOKJV6wGdAri3mNe/ue2S0TziSbQ/4+Dj4ubFKgkH7thb5q2dFyxw5FzhYzRQiXFqD/pxcNN9uL0lQI2Al0Eci0zX8Kcd1rAQ6RzLEoSmco=,iv:zo/3QFKTUEDxLy1k5yyU7Z1JMZ7cKdYUc6GHjaTTZKQ=,tag:f63Eja3lBfwJCYAOyEt56g==,type:str]",
16 "unencrypted_suffix": "_unencrypted",
17 "version": "3.10.2"
18 }
19}
diff --git a/hosts/sif/mail/secrets.yaml b/hosts/sif/email/secrets.yaml
index 3c74b710..3c74b710 100644
--- a/hosts/sif/mail/secrets.yaml
+++ b/hosts/sif/email/secrets.yaml
diff --git a/hosts/sif/greetd/default.nix b/hosts/sif/greetd/default.nix
index f609fc05..37ca13c5 100644
--- a/hosts/sif/greetd/default.nix
+++ b/hosts/sif/greetd/default.nix
@@ -11,6 +11,11 @@
11 # exec ${pkgs.dbus}/bin/dbus-run-session ${lib.getExe pkgs.cage} ${lib.escapeShellArgs cfg.cageArgs} -- ${lib.getExe cfg.package} 11 # exec ${pkgs.dbus}/bin/dbus-run-session ${lib.getExe pkgs.cage} ${lib.escapeShellArgs cfg.cageArgs} -- ${lib.getExe cfg.package}
12 # ''; 12 # '';
13 }; 13 };
14 systemd.services.greetd.environment = {
15 XKB_DEFAULT_LAYOUT = "us,us";
16 XKB_DEFAULT_VARIANT = "dvp,";
17 XKB_DEFAULT_OPTIONS = "compose:caps,grp:win_space_toggle";
18 };
14 programs.regreet = { 19 programs.regreet = {
15 enable = true; 20 enable = true;
16 theme = { 21 theme = {
diff --git a/hosts/sif/mail/default.nix b/hosts/sif/mail/default.nix
deleted file mode 100644
index 8d6cd705..00000000
--- a/hosts/sif/mail/default.nix
+++ /dev/null
@@ -1,70 +0,0 @@
1{ config, lib, pkgs, ... }:
2{
3 services.postfix = {
4 enable = true;
5 enableSmtp = true;
6 enableSubmission = false;
7 setSendmail = true;
8 networksStyle = "host";
9 hostname = "sif.midgard.yggdrasil";
10 destination = [];
11 relayHost = "uucp:ymir";
12 recipientDelimiter = "+";
13 masterConfig = {
14 uucp = {
15 type = "unix";
16 private = true;
17 privileged = true;
18 chroot = false;
19 command = "pipe";
20 args = [ "flags=Fqhu" "user=uucp" ''argv=${config.security.wrapperDir}/uux -z -a $sender - $nexthop!rmail ($recipient)'' ];
21 };
22 smtps = {
23 type = "unix";
24 private = true;
25 privileged = true;
26 chroot = false;
27 command = "smtp";
28 args = [ "-o" "smtp_tls_wrappermode=yes" "-o" "smtp_tls_security_level=encrypt" ];
29 };
30 };
31 config = {
32 default_transport = "uucp:ymir";
33
34 inet_interfaces = "loopback-only";
35
36 authorized_submit_users = ["!uucp" "static:anyone"];
37 message_size_limit = "0";
38
39 sender_dependent_default_transport_maps = ''regexp:${pkgs.writeText "sender_relay" ''
40 /@(cip|stud)\.ifi\.(lmu|uni-muenchen)\.de$/ smtp:smtp.ifi.lmu.de
41 /@ifi\.(lmu|uni-muenchen)\.de$/ smtp:smtpin1.ifi.lmu.de:587
42 /@math(ematik)?\.(lmu|uni-muenchen)\.de$/ smtps:smtp.math.lmu.de:465
43 /@(campus\.)?lmu\.de$/ smtp:postout.lrz.de
44 ''}'';
45 sender_bcc_maps = ''regexp:${pkgs.writeText "sender_bcc" ''
46 /^uni2work(-[^@]*)?@ifi\.lmu\.de$/ uni2work@ifi.lmu.de
47 /@ifi\.lmu\.de$/ gregor.kleen@ifi.lmu.de
48 ''}'';
49
50 smtp_sasl_auth_enable = true;
51 smtp_sender_dependent_authentication = true;
52 smtp_sasl_tls_security_options = "noanonymous";
53 smtp_sasl_mechanism_filter = ["plain"];
54 smtp_sasl_password_maps = "regexp:/var/db/postfix/sasl_passwd";
55 smtp_cname_overrides_servername = false;
56 smtp_always_send_ehlo = true;
57 smtp_tls_security_level = "dane";
58
59 smtp_tls_loglevel = "1";
60 smtp_dns_support_level = "dnssec";
61 };
62 };
63
64 sops.secrets.postfix-sasl-passwd = {
65 key = "sasl-passwd";
66 path = "/var/db/postfix/sasl_passwd";
67 owner = "postfix";
68 sopsFile = ./secrets.yaml;
69 };
70}
diff --git a/hosts/sif/ruleset.nft b/hosts/sif/ruleset.nft
index 2af8b2ee..62339f69 100644
--- a/hosts/sif/ruleset.nft
+++ b/hosts/sif/ruleset.nft
@@ -61,7 +61,7 @@ table inet filter {
61 counter mosh-rx {} 61 counter mosh-rx {}
62 counter wg-rx {} 62 counter wg-rx {}
63 counter yggdrasil-gre-rx {} 63 counter yggdrasil-gre-rx {}
64 counter quickserve-rx {} 64 counter miniserve-rx {}
65 counter ausweisapp2-rx {} 65 counter ausweisapp2-rx {}
66 66
67 counter established-rx {} 67 counter established-rx {}
@@ -81,7 +81,7 @@ table inet filter {
81 counter mosh-tx {} 81 counter mosh-tx {}
82 counter wg-tx {} 82 counter wg-tx {}
83 counter yggdrasil-gre-tx {} 83 counter yggdrasil-gre-tx {}
84 counter quickserve-tx {} 84 counter miniserve-tx {}
85 85
86 counter tx {} 86 counter tx {}
87 87
@@ -134,7 +134,7 @@ table inet filter {
134 tcp dport 22 counter name ssh-rx accept 134 tcp dport 22 counter name ssh-rx accept
135 udp dport 60000-61000 counter name mosh-rx accept 135 udp dport 60000-61000 counter name mosh-rx accept
136 136
137 tcp dport 8000 counter name quickserve-rx accept 137 tcp dport 8080 counter name miniserve-rx accept
138 udp dport 24727 counter name ausweisapp2-rx accept 138 udp dport 24727 counter name ausweisapp2-rx accept
139 139
140 udp dport 51820-51822 counter name wg-rx accept 140 udp dport 51820-51822 counter name wg-rx accept
@@ -173,7 +173,7 @@ table inet filter {
173 udp sport 51820-51822 counter name wg-tx 173 udp sport 51820-51822 counter name wg-tx
174 iifname "yggdrasil-wg-*" meta l4proto gre counter name yggdrasil-gre-tx 174 iifname "yggdrasil-wg-*" meta l4proto gre counter name yggdrasil-gre-tx
175 175
176 tcp sport 8000 counter name quickserve-tx accept 176 tcp sport 8080 counter name miniserve-tx accept
177 177
178 oifname virbr0 udp sport 67 counter name libvirt-dhcp accept 178 oifname virbr0 udp sport 67 counter name libvirt-dhcp accept
179 oifname virbr0 udp sport 547 counter name libvirt-dhcp accept 179 oifname virbr0 udp sport 547 counter name libvirt-dhcp accept
diff --git a/hosts/surtr/audiobookshelf.nix b/hosts/surtr/audiobookshelf.nix
new file mode 100644
index 00000000..728851a4
--- /dev/null
+++ b/hosts/surtr/audiobookshelf.nix
@@ -0,0 +1,66 @@
1{ config, ... }:
2
3{
4 config = {
5 security.acme.rfc2136Domains = {
6 "audiobookshelf.yggdrasil.li" = {
7 restartUnits = ["nginx.service"];
8 };
9 };
10
11 services.nginx = {
12 upstreams."audiobookshelf" = {
13 servers = {
14 "[2a03:4000:52:ada:4:1::]:28982" = {};
15 };
16 extraConfig = ''
17 keepalive 8;
18 '';
19 };
20 virtualHosts = {
21 "audiobookshelf.yggdrasil.li" = {
22 kTLS = true;
23 http3 = true;
24 forceSSL = true;
25 sslCertificate = "/run/credentials/nginx.service/audiobookshelf.yggdrasil.li.pem";
26 sslCertificateKey = "/run/credentials/nginx.service/audiobookshelf.yggdrasil.li.key.pem";
27 sslTrustedCertificate = "/run/credentials/nginx.service/audiobookshelf.yggdrasil.li.chain.pem";
28 extraConfig = ''
29 charset utf-8;
30 '';
31
32 locations = {
33 "/".extraConfig = ''
34 proxy_pass http://audiobookshelf;
35
36 proxy_http_version 1.1;
37 proxy_set_header Upgrade $http_upgrade;
38 proxy_set_header Connection "upgrade";
39
40 proxy_redirect off;
41 proxy_set_header Host $host;
42 proxy_set_header X-Real-IP $remote_addr;
43 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
44 proxy_set_header X-Forwarded-Host $server_name;
45 proxy_set_header X-Forwarded-Proto $scheme;
46
47 client_max_body_size 0;
48 proxy_request_buffering off;
49 proxy_buffering off;
50 '';
51 };
52 };
53 };
54 };
55
56 systemd.services.nginx = {
57 serviceConfig = {
58 LoadCredential = [
59 "audiobookshelf.yggdrasil.li.key.pem:${config.security.acme.certs."audiobookshelf.yggdrasil.li".directory}/key.pem"
60 "audiobookshelf.yggdrasil.li.pem:${config.security.acme.certs."audiobookshelf.yggdrasil.li".directory}/fullchain.pem"
61 "audiobookshelf.yggdrasil.li.chain.pem:${config.security.acme.certs."audiobookshelf.yggdrasil.li".directory}/chain.pem"
62 ];
63 };
64 };
65 };
66}
diff --git a/hosts/surtr/bifrost/default.nix b/hosts/surtr/bifrost/default.nix
index fbfde757..52ab43f5 100644
--- a/hosts/surtr/bifrost/default.nix
+++ b/hosts/surtr/bifrost/default.nix
@@ -18,7 +18,7 @@ in {
18 ListenPort = 51822; 18 ListenPort = 51822;
19 }; 19 };
20 wireguardPeers = [ 20 wireguardPeers = [
21 { AllowedIPs = [ "2a03:4000:52:ada:4:1::/96" ]; 21 { AllowedIPs = [ "2a03:4000:52:ada:4:1::/96" "2a03:4000:52:ada:6::/80" ];
22 PublicKey = trim (readFile ../../vidhar/network/bifrost/vidhar.pub); 22 PublicKey = trim (readFile ../../vidhar/network/bifrost/vidhar.pub);
23 } 23 }
24 ]; 24 ];
@@ -34,6 +34,8 @@ in {
34 routes = [ 34 routes = [
35 { Destination = "2a03:4000:52:ada:4::/80"; 35 { Destination = "2a03:4000:52:ada:4::/80";
36 } 36 }
37 { Destination = "2a03:4000:52:ada:6::/80";
38 }
37 ]; 39 ];
38 linkConfig = { 40 linkConfig = {
39 RequiredForOnline = false; 41 RequiredForOnline = false;
diff --git a/hosts/surtr/default.nix b/hosts/surtr/default.nix
index b8a639d5..9d3101c0 100644
--- a/hosts/surtr/default.nix
+++ b/hosts/surtr/default.nix
@@ -7,6 +7,7 @@ with lib;
7 tmpfs-root qemu-guest openssh rebuild-machines zfs 7 tmpfs-root qemu-guest openssh rebuild-machines zfs
8 ./zfs.nix ./dns ./tls ./http ./bifrost ./matrix ./postgresql 8 ./zfs.nix ./dns ./tls ./http ./bifrost ./matrix ./postgresql
9 ./prometheus ./email ./vpn ./borg.nix ./etebase ./immich.nix 9 ./prometheus ./email ./vpn ./borg.nix ./etebase ./immich.nix
10 ./paperless.nix ./hledger.nix ./audiobookshelf.nix ./kimai.nix
10 ]; 11 ];
11 12
12 config = { 13 config = {
@@ -14,9 +15,6 @@ with lib;
14 system = "x86_64-linux"; 15 system = "x86_64-linux";
15 }; 16 };
16 17
17 networking.hostId = "a64cf4d7";
18 environment.etc."machine-id".text = "a64cf4d793ab0a0ed3892ead609fc0bc";
19
20 boot = { 18 boot = {
21 loader.grub = { 19 loader.grub = {
22 enable = true; 20 enable = true;
@@ -30,6 +28,15 @@ with lib;
30 zfs.devNodes = "/dev"; # /dev/vda2 does not show up in /dev/disk/by-id 28 zfs.devNodes = "/dev"; # /dev/vda2 does not show up in /dev/disk/by-id
31 29
32 kernelModules = ["ptp_kvm"]; 30 kernelModules = ["ptp_kvm"];
31 kernelPatches = [
32 { name = "zswap-default";
33 patch = null;
34 extraStructuredConfig = with lib.kernel; {
35 ZSWAP_DEFAULT_ON = yes;
36 ZSWAP_SHRINKER_DEFAULT_ON = yes;
37 };
38 }
39 ];
33 }; 40 };
34 41
35 fileSystems = { 42 fileSystems = {
@@ -38,6 +45,9 @@ with lib;
38 fsType = "vfat"; 45 fsType = "vfat";
39 }; 46 };
40 }; 47 };
48 swapDevices = [
49 { label = "swap"; }
50 ];
41 51
42 networking = { 52 networking = {
43 hostName = "surtr"; 53 hostName = "surtr";
@@ -163,11 +173,6 @@ with lib;
163 stateful = true; 173 stateful = true;
164 }; 174 };
165 175
166 zramSwap = {
167 enable = true;
168 algorithm = "zstd";
169 };
170
171 systemd.sysusers.enable = false; 176 systemd.sysusers.enable = false;
172 system.etc.overlay.mutable = true; 177 system.etc.overlay.mutable = true;
173 boot.enableContainers = true; 178 boot.enableContainers = true;
diff --git a/hosts/surtr/dns/Gupfile b/hosts/surtr/dns/Gupfile
index ac96f620..70674cce 100644
--- a/hosts/surtr/dns/Gupfile
+++ b/hosts/surtr/dns/Gupfile
@@ -1,2 +1,2 @@
1key.gup: 1key.gup:
2 keys/*.yaml \ No newline at end of file 2 keys/* \ No newline at end of file
diff --git a/hosts/surtr/dns/default.nix b/hosts/surtr/dns/default.nix
index ee1d089d..8aca2b97 100644
--- a/hosts/surtr/dns/default.nix
+++ b/hosts/surtr/dns/default.nix
@@ -157,7 +157,7 @@ in {
157 ${concatMapStringsSep "\n" mkZone [ 157 ${concatMapStringsSep "\n" mkZone [
158 { domain = "yggdrasil.li"; 158 { domain = "yggdrasil.li";
159 addACLs = { "yggdrasil.li" = ["ymir_acme_acl"]; }; 159 addACLs = { "yggdrasil.li" = ["ymir_acme_acl"]; };
160 acmeDomains = ["surtr.yggdrasil.li" "yggdrasil.li" "etesync.yggdrasil.li" "immich.yggdrasil.li" "app.etesync.yggdrasil.li"]; 160 acmeDomains = ["surtr.yggdrasil.li" "yggdrasil.li" "etesync.yggdrasil.li" "immich.yggdrasil.li" "app.etesync.yggdrasil.li" "paperless.yggdrasil.li" "hledger.yggdrasil.li" "audiobookshelf.yggdrasil.li" "kimai.yggdrasil.li"];
161 } 161 }
162 { domain = "nights.email"; 162 { domain = "nights.email";
163 addACLs = { "nights.email" = ["ymir_acme_acl"]; }; 163 addACLs = { "nights.email" = ["ymir_acme_acl"]; };
@@ -169,15 +169,9 @@ in {
169 { domain = "kleen.li"; 169 { domain = "kleen.li";
170 addACLs = { "kleen.li" = ["ymir_acme_acl"]; }; 170 addACLs = { "kleen.li" = ["ymir_acme_acl"]; };
171 } 171 }
172 { domain = "xmpp.li";
173 addACLs = { "xmpp.li" = ["ymir_acme_acl"]; };
174 }
175 { domain = "synapse.li"; 172 { domain = "synapse.li";
176 acmeDomains = ["element.synapse.li" "turn.synapse.li" "synapse.li"]; 173 acmeDomains = ["element.synapse.li" "turn.synapse.li" "synapse.li"];
177 } 174 }
178 { domain = "dirty-haskell.org";
179 addACLs = { "dirty-haskell.org" = ["ymir_acme_acl"]; };
180 }
181 { domain = "praseodym.org"; 175 { domain = "praseodym.org";
182 addACLs = { "praseodym.org" = ["ymir_acme_acl"]; }; 176 addACLs = { "praseodym.org" = ["ymir_acme_acl"]; };
183 } 177 }
diff --git a/hosts/surtr/dns/key.gup b/hosts/surtr/dns/key.gup
index 32d4f7d6..5b5058b3 100644
--- a/hosts/surtr/dns/key.gup
+++ b/hosts/surtr/dns/key.gup
@@ -3,4 +3,4 @@
3keyName=${${2:t}%.yaml}_key 3keyName=${${2:t}%.yaml}_key
4 4
5keymgr -t ${keyName} > $1 5keymgr -t ${keyName} > $1
6sops -p '7ED22F4AA7BB55728B643DC5471B7D88E4EF66F8,30D3453B8CD02FE2A3E7C78C0FB536FB87AE8F51' --input-type=binary --output-type=binary -e -i $1 \ No newline at end of file 6sops --input-type=binary --output-type=binary -e -i $1
diff --git a/hosts/surtr/dns/keys/audiobookshelf.yggdrasil.li_acme b/hosts/surtr/dns/keys/audiobookshelf.yggdrasil.li_acme
new file mode 100644
index 00000000..e3af9966
--- /dev/null
+++ b/hosts/surtr/dns/keys/audiobookshelf.yggdrasil.li_acme
@@ -0,0 +1,19 @@
1{
2 "data": "ENC[AES256_GCM,data:5BLk/MZtQsLjpdKGiZjtOH1soIXB05MkEoPWyr5m27ho7H5udDjRZE0/OjvSlMzQNjFNJc+OwbeHwaJ8sPLCnXqoFHJXikAmi0gpdFC0uN0JGYCBT1EaK7j6yVHyiqFXo6xwVSCO/zP/fbjItiuqU+B4llGx5N2I4HQqRLFoW/35PZazkR15xI1zo8LeC8T+jm16apFQw2Ih/RqsJK7XlHqnXq9SzeA2qhgkCbJf6aJ6zDS7eQVFt7qHh2mVtV7Al++bZiIkJiNJ5SJO1ck18w2t9HwXBhfMFKXvfFW0I6kKur1qSJk=,iv:rxm5PwOzXDaK+nj2k3bUqlYbIFFA49ispyfamtQqU/A=,tag:7dOua39f+0JsLldLRLr1NQ==,type:str]",
3 "sops": {
4 "age": [
5 {
6 "recipient": "age1rmmhetcmllq0ahl5qznlr0eya2zdxwl9h6y5wnl97d2wtyx5t99sm2u866",
7 "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAwV1hIcWNNRVJXOG1xYlFK\nb1VxWG03dGVmS3dmQTltUTR0VmEzYjRFanlBCnE1M1BTZjNCQnpsNkU5ZlR5T3BR\nOHliWUV2bXMrK0w1K3JXbUE0dEhKdzgKLS0tIDdkUEZ4QUE0NUN2TXFYcklybjBS\nRVpxV2J6U1BnUng1dytWTGRkd0FrRGcKVaMCzcrX+BQqbmh95JEk3lRdfnv9uO8n\nwotKxp/+xX7GEF42BOzJ/mWcgyVjABlnWeVyaRxfpbCUrmNuFYO6XA==\n-----END AGE ENCRYPTED FILE-----\n"
8 },
9 {
10 "recipient": "age19a7j77w267z04zls7m28a8hj4a0g5af6ltye2d5wypg33c3l89csd4r9zq",
11 "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBkZmRFR0gwc1pjVVorK0t6\nVHU4U0xob09Dd2J5QUJXbndZdWp3cytpYWhRCnFnMlZ0cjNGdXN1RWEvQlVHSE1M\nMEdzSjlEb2NkaE1zMEJJOTlCMC9FVFEKLS0tIGJiTEJSRVZZbVUzZVFGeXQwYm1w\ndWVYQVdoVlJnRENTYzhnQm1BcWVRdW8K0ECfLVQBQuZWFFFjdHLcJBz+CDzsOOwh\nOA38qxHD4EWfXR1c3G8RDbow7nB2MGc1Zohc7qhtuTj2wL0qhVKWqA==\n-----END AGE ENCRYPTED FILE-----\n"
12 }
13 ],
14 "lastmodified": "2025-05-09T17:07:16Z",
15 "mac": "ENC[AES256_GCM,data:K5I6YXQXCUPHFBNVlXIdLLKqiNPVZh95KoHni2m16SdAvTyBab79SZ5xNvotrtKXp0iISCogEgdMm+OWbxYywEiZ+sUsxgx6RE5nAXruZOiAwzuyUr88qgCHTBZnKzgaDZlbYOgWB+LCzr8s5JbcTD4G6/RaXYyqmx7igygubHA=,iv:Zbij4eoDqoP5XYhAsDGBGqlcP5ACQAY/QngTmrJYRzs=,tag:WYxhUEZwmXkQmnpyOuP2Bg==,type:str]",
16 "unencrypted_suffix": "_unencrypted",
17 "version": "3.10.2"
18 }
19}
diff --git a/hosts/surtr/dns/keys/hledger.yggdrasil.li_acme b/hosts/surtr/dns/keys/hledger.yggdrasil.li_acme
new file mode 100644
index 00000000..b3f4cfb6
--- /dev/null
+++ b/hosts/surtr/dns/keys/hledger.yggdrasil.li_acme
@@ -0,0 +1,24 @@
1{
2 "data": "ENC[AES256_GCM,data:lCj8VYJL9z29FJ154XQtxKQLwwitCRGy4krJ6u8yw2FMzoHprEpFgm33+mFspxSKk/It2G8cfTGMZSeVkYJEHb66HNKHl0A2Fz3hwjpRjh1MZAw0wiZJlnS/LNqoGstQ2PJmTQTW3aJRMoT1GS7q/gSp/3rqySA5EOm0GgUiA3Vi7nGpkBenKDEbQbcIBXRdMOk66BCdiz5XGm/1VLQQLO9oVwY2KBnLaZSISohyGVhbIy7GT2ygoWHHxHn0c5CRVNvGNwesM1gO1NnTFrISLMWSrsDPaAtQ,iv:fa8LFjzqsf2ccfbEe5MOmerb7FzXb4xr24y1GWIMT1Q=,tag:7oQ54DKBb76Pbw1lmEHt+Q==,type:str]",
3 "sops": {
4 "kms": null,
5 "gcp_kms": null,
6 "azure_kv": null,
7 "hc_vault": null,
8 "age": [
9 {
10 "recipient": "age1rmmhetcmllq0ahl5qznlr0eya2zdxwl9h6y5wnl97d2wtyx5t99sm2u866",
11 "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBzcHJLRHh0VkpDNDU3cGZS\nMWFlVWpFemZ2RnpnSllDWU1EWGoyWUxyejNzClBGandzaFI5NXY1bG51Y3VxRk9r\nc29NbXBOaEZDblBuaVowemQydkxBdjgKLS0tICtVb2xkMmh4T0Q0cU4xQnBzZExI\namFRUnRYTWIyQ3RHNUVHWTFrUzhhK1UKqmATNmxlhkxM5PP1U6w7fSYVA8AgIRAt\nJ9WZrTffQfXMdw4RmjWcoVHFH39Fe4SteedxliCCcqjkjgSEB4Rgow==\n-----END AGE ENCRYPTED FILE-----\n"
12 },
13 {
14 "recipient": "age19a7j77w267z04zls7m28a8hj4a0g5af6ltye2d5wypg33c3l89csd4r9zq",
15 "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBjWlhpQWI4c0x0ZXRGYVE2\nR1dHZzZud0ZxQWsrREhJWUowWE1zem5FVFI0CkJBUnIwY1FGS3N2VnpuSkZjRllZ\ncVgyeVg4cTVjRitzL0RKb0ZQb3BsOEkKLS0tIGs3SDBkamVBNDhQUlh5dmVVZXJs\nM3VKdlFKc21GcFY0UUtiaHFvYWI4V0kKKuWYEncxe9NT2ZS3X3+l/gT4BQOrdCg8\nj2jGL+Yzy/356GO3PFTn2HHLam6KWDKaYB5TlK/zSohfUt5giQH2Lw==\n-----END AGE ENCRYPTED FILE-----\n"
16 }
17 ],
18 "lastmodified": "2025-02-19T17:13:51Z",
19 "mac": "ENC[AES256_GCM,data:XsBdMCBjB+YuBMZQrjJ5uZtaYKSqsdWVvm+IEoJflCKPIhPk2rBZ3nY8KngXFbq2fWgsYyTM83kb2trEGIEHUPuERt+mgfCI3bSlylriwgsDWihCjyBecNE+BbdXE0+YcNl8pIwBU4M+3f2StQMH22YamToLJ9i9kfKcBrirDuU=,iv:VTIdBVY3kVBMYWhYUmrP2vZ9rpH90DzF68y1aDf2EAs=,tag:YkL+nw6LNXAceZtx9vgf6A==,type:str]",
20 "pgp": null,
21 "unencrypted_suffix": "_unencrypted",
22 "version": "3.9.4"
23 }
24} \ No newline at end of file
diff --git a/hosts/surtr/dns/keys/kimai.yggdrasil.li_acme b/hosts/surtr/dns/keys/kimai.yggdrasil.li_acme
new file mode 100644
index 00000000..bdfb135a
--- /dev/null
+++ b/hosts/surtr/dns/keys/kimai.yggdrasil.li_acme
@@ -0,0 +1,19 @@
1{
2 "data": "ENC[AES256_GCM,data:sKFt4pH0Xn7Qm6JFMg/2N7Ht7jtMJukfN+U3dQaoYXPbhRJ+heEtDpXV/WP4AlfbfpIOgTPW3mcmQCwKFNhS00vEsQA4728FfXZzDDmZCa3hwg51wDbL7XUOr0OePgzi86lt0Q193K6CkGqEAa1vFIb//ElEfBYIwdATbmcoAsM3mHhz58X7c1qf8LNuB93o/1N2xXXZI3NWOhOjlviTc2DAhffXDwlMJSYUhldnwtDKmLM1mooJzLgm2p9w7gRD7WPqEqZFq9uFDK69P9uX5T9hFHg=,iv:rAE4sYxxLou4tyD4RWTp3LjQP0cya95coy1MvwfEK/U=,tag:u4SSk8SZFlj0ks7d6tDocw==,type:str]",
3 "sops": {
4 "age": [
5 {
6 "recipient": "age1rmmhetcmllq0ahl5qznlr0eya2zdxwl9h6y5wnl97d2wtyx5t99sm2u866",
7 "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB2KzdNUWhEcDB6QmtUTnVh\nNS9Nc2I4UjAzekxhRXo1UmY3SklPejV1TURJCm9NY2lVOERoMDFKTU56Mmh1NHEr\naGV4M1RoVldHV0xyc3Z0MnVqakpjMFUKLS0tIEYxSk9OUm9kMkdtcG5POWRGQVkx\nY1FEaXYwMGo0L0Z0aTVTZDA5aUFDWEUKJ+e/7lR/rNPNVnIy+wkiKiAYMxWp4L7q\nwnSTx451vSnxv9j3JWB43Y7XQC08cisWDj06ULw8FnEbKYOvTYj9mQ==\n-----END AGE ENCRYPTED FILE-----\n"
8 },
9 {
10 "recipient": "age19a7j77w267z04zls7m28a8hj4a0g5af6ltye2d5wypg33c3l89csd4r9zq",
11 "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBwOTU3dUEzaXM5T1VkbDRO\nMm14OG1mUkk2bDRhdnBsMHBkc3kvUzlyNlQwCktFSHJhMnhoQ2J6bC9vUHNLWTRC\nRFpYeHo3N2xjWUhjQnRwQ2Nrc1pRUmsKLS0tIDdPeFBVdkxDd1JWSmcxQ0tLMTBD\ncHU3VExZOUhYUlJvbGNoK3FMK2VIbGMKFk94P9aBY04CPIi983f3Aalgh4fnU+/K\n2mxawSMf9jz8704N5XJfmr2hwNy8hqLIn8bjsEMAPTfE1YBGga4w0g==\n-----END AGE ENCRYPTED FILE-----\n"
12 }
13 ],
14 "lastmodified": "2025-05-24T09:42:23Z",
15 "mac": "ENC[AES256_GCM,data:diCeJGvBmM0Ng722eKoFwDe7pqZrdLPSLn5j9LfdaFI64BAbSbA5bAq4NFXqdJ1vttarD2A5rEafYoXUxP8228x2GhNyWUGW5AWgBjVPUc59gjs4wYKR5HlkVMIadhTwNheEyoEjrxX40GNBgCG7X3ocOtOYKbKECp433gdAPDg=,iv:d+yJMWj2RyFnveo2ZNrpNeV+amXM+H7vdC0A2F7mwjA=,tag:yjibG2iusdprp0ORghYWhw==,type:str]",
16 "unencrypted_suffix": "_unencrypted",
17 "version": "3.10.2"
18 }
19}
diff --git a/hosts/surtr/dns/keys/paperless.yggdrasil.li_acme b/hosts/surtr/dns/keys/paperless.yggdrasil.li_acme
new file mode 100644
index 00000000..bc4640db
--- /dev/null
+++ b/hosts/surtr/dns/keys/paperless.yggdrasil.li_acme
@@ -0,0 +1,24 @@
1{
2 "data": "ENC[AES256_GCM,data:wOl8KLHD0H+btq0A3UreyVF9bOXZsiTwWJkVH8GubyIQDyiDC8vQm+dfv0rz8TwcBWYpC4aMIPPflG2HsdYO4rKGQ/nBmWmxhNXjpnyRo8iKM1BGb5bxNe4eVcUVhI60NuRJDRLmtDp+0rYGT/MVYp0/mHBINsQCXWBPDoaN2PI2GSnRag/x0wcL27xgH6NDd8glcdCN5nCAPDvazA3LialkXXv7/cceA5Q/Ee6HGzPP0w212/UvBm07Z5tXnHiy5cTbAGTUBfIqC8n501jtaQhpMh/yzA1R8KwUrw==,iv:bLzsthCaanNikNS2Es4J1++E5lijEbjyW5hU4zzNBcg=,tag:eWfZ3AtcSAGv8jWXzqlAwQ==,type:str]",
3 "sops": {
4 "kms": null,
5 "gcp_kms": null,
6 "azure_kv": null,
7 "hc_vault": null,
8 "age": [
9 {
10 "recipient": "age1rmmhetcmllq0ahl5qznlr0eya2zdxwl9h6y5wnl97d2wtyx5t99sm2u866",
11 "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBNYWZKOHd2UmxWYVlIamk4\nZ04zQnAwcDdsSDBDWUZnekNva3BzRERyWXljCmNvUU1Fczc2aUN6VGl6NEJ6MGIx\nWHVpeVluWnRnbjNadGxkSmYyNE1rZzQKLS0tIGpkYVZQRDJGS21ZZHdlRk1MMm02\nQ09aanNXSWltNi9QeUNtUVQ2UEZybmMK6/qcNYLMcyKTmtROX+ZsRqDxMXwkXiAV\ndsdsWJ5+zSJuK5SEIh0fqEZ/t4pxnMcr1WieETgLSd+w0sNQS7EKPQ==\n-----END AGE ENCRYPTED FILE-----\n"
12 },
13 {
14 "recipient": "age19a7j77w267z04zls7m28a8hj4a0g5af6ltye2d5wypg33c3l89csd4r9zq",
15 "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSByRjdJOHdjamRHVjlZOTNV\nbTBuam5vNERIWTB6T0JkR2pLUnlQN1BGbUJNClhrZ2hPRWZtT3BERFNwdmNEMmVu\nT0dxcjNkNGIvMVJQWENoUmRhTGd6SXcKLS0tIDV6WDd1bks4K1VuVkgybjdMd0w0\nMHBsT3FmOWU0WnJsM2diQm1sTU1ON2MKtf5HZ0S1cLMx98vDKRKamS7aHIJZ0OnA\nzH4VoeVm+PKsOeqVfY+gMHLdaMEWLKYsz3B8bxIoL5pvnCdT1QAN2A==\n-----END AGE ENCRYPTED FILE-----\n"
16 }
17 ],
18 "lastmodified": "2025-02-13T19:23:42Z",
19 "mac": "ENC[AES256_GCM,data:o7zNTjkohzAouYpJUGqf8DUfYf4/g3GZgc+4cf+PjI0OF8uc1WDCPvliBFe6pf/8QMhV5DFWd2SfszWnpnQhtiIVG/2BEk5sw3P6r/SUbSErakFYHueVQKp+9rdxK6uKcHUYhO46E332AwIxTuvNeHtSBMxx0kAwQPuuD/u3L4A=,iv:aiM0sGyGMk5lfBOpB2bDFCY+UfWwyUNixieww6eOSLs=,tag:MI7xJ7RsyZgQfF1SBVVmcQ==,type:str]",
20 "pgp": null,
21 "unencrypted_suffix": "_unencrypted",
22 "version": "3.9.4"
23 }
24} \ No newline at end of file
diff --git a/hosts/surtr/dns/zones/email.nights.soa b/hosts/surtr/dns/zones/email.nights.soa
index 913a88d4..34209a99 100644
--- a/hosts/surtr/dns/zones/email.nights.soa
+++ b/hosts/surtr/dns/zones/email.nights.soa
@@ -1,7 +1,7 @@
1$ORIGIN nights.email. 1$ORIGIN nights.email.
2$TTL 3600 2$TTL 3600
3@ IN SOA ns.yggdrasil.li. hostmaster.yggdrasil.li ( 3@ IN SOA ns.yggdrasil.li. hostmaster.yggdrasil.li (
4 2023013000 ; serial 4 2025060700 ; serial
5 10800 ; refresh 5 10800 ; refresh
6 3600 ; retry 6 3600 ; retry
7 604800 ; expire 7 604800 ; expire
@@ -27,11 +27,7 @@ $TTL 3600
27 27
28_acme-challenge IN NS ns.yggdrasil.li. 28_acme-challenge IN NS ns.yggdrasil.li.
29 29
30ymir._domainkey IN TXT ( 30ymir._domainkey IN CNAME ymir._domainkey.yggdrasil.li.
31 "v=DKIM1;k=rsa;p=MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAq3cCKlk+VPhyAanLZTM0BCzUT/+fmxHioZcFk0uJk1akBYj7BRofR7eVNcLKpm3rwYMQgE+9vJH9p8SV6tws9EcWc8SMCqqGZlREYM7PmLDiTSK/vjCzkygfgFCb0EBNsY2A/fpP4rTeoxrbcBSvMkq97iY5rwyw4wXZVZXLiDaCj23s8POoxTk1ClqUJZJQ5x2"
32 "qzrC0RfN5kLZ9A7Gq2jB09vNxpXHYqABA0bJv88JiZM7hfkp9IafJZ+yCVMaBcJs4DAxnTjNAuFD9gm+qSFVY8+yeXqL6Qjo5PbruhyZRBW8RgRYT8t5n07XRglMGKKGMwOGLanrltcyXqB+GsDZBD36RAAwjFadnxdpDyRv4SgRP7ff2tKRrORYpmpN+mKdqw5j3J/nP6bXV1oAkyh9XQkPEIDi81WT87EZziTElDzVp6A2qFOxqucAovoRk24"
33 "7vlsns1FApFRsp9mja0UZNObyKD1M6tP9Ep7lS76tFGMk+WDvXRJH5LEsyCpu7sSyl1r/O0M4K+KldRCqLlZd7rf8F5P8T0dn1azk05g7F4p0N/y9GNdzXbPZ9u0eZdI7SEdh8ZoOZp7NVZiBFfbWLSS5ZtyA2kbBa4i7GJ/cuAbEKOmqAkeQPiu96TGIcyjkXjS6mTPI+9UmKZYZC+OM8XdJ02y5KRoonCc19ZS8CAwEAAQ=="
34)
35 31
36_xmpp-client._tcp IN SRV 5 0 5222 ymir.yggdrasil.li. 32_xmpp-client._tcp IN SRV 5 0 5222 ymir.yggdrasil.li.
37_xmpp-server._tcp IN SRV 5 0 5269 ymir.yggdrasil.li. 33_xmpp-server._tcp IN SRV 5 0 5269 ymir.yggdrasil.li.
diff --git a/hosts/surtr/dns/zones/li.141.soa b/hosts/surtr/dns/zones/li.141.soa
index d42b4719..78d137bb 100644
--- a/hosts/surtr/dns/zones/li.141.soa
+++ b/hosts/surtr/dns/zones/li.141.soa
@@ -1,7 +1,7 @@
1$ORIGIN 141.li. 1$ORIGIN 141.li.
2$TTL 3600 2$TTL 3600
3@ IN SOA ns.yggdrasil.li. hostmaster.yggdrasil.li ( 3@ IN SOA ns.yggdrasil.li. hostmaster.yggdrasil.li (
4 2024102100 ; serial 4 2025060701 ; serial
5 10800 ; refresh 5 10800 ; refresh
6 3600 ; retry 6 3600 ; retry
7 604800 ; expire 7 604800 ; expire
@@ -45,11 +45,8 @@ ymir IN AAAA 2a03:4000:6:d004::
45ymir IN MX 0 ymir.yggdrasil.li 45ymir IN MX 0 ymir.yggdrasil.li
46ymir IN TXT "v=spf1 redirect=ymir.yggdrasil.li" 46ymir IN TXT "v=spf1 redirect=ymir.yggdrasil.li"
47 47
48ymir._domainkey IN TXT ( 48ymir._domainkey IN CNAME ymir._domainkey.yggdrasil.li.
49 "v=DKIM1;k=rsa;p=MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAq3cCKlk+VPhyAanLZTM0BCzUT/+fmxHioZcFk0uJk1akBYj7BRofR7eVNcLKpm3rwYMQgE+9vJH9p8SV6tws9EcWc8SMCqqGZlREYM7PmLDiTSK/vjCzkygfgFCb0EBNsY2A/fpP4rTeoxrbcBSvMkq97iY5rwyw4wXZVZXLiDaCj23s8POoxTk1ClqUJZJQ5x2" 49surtr._domainkey IN CNAME surtr._domainkey.yggdrasil.li.
50 "qzrC0RfN5kLZ9A7Gq2jB09vNxpXHYqABA0bJv88JiZM7hfkp9IafJZ+yCVMaBcJs4DAxnTjNAuFD9gm+qSFVY8+yeXqL6Qjo5PbruhyZRBW8RgRYT8t5n07XRglMGKKGMwOGLanrltcyXqB+GsDZBD36RAAwjFadnxdpDyRv4SgRP7ff2tKRrORYpmpN+mKdqw5j3J/nP6bXV1oAkyh9XQkPEIDi81WT87EZziTElDzVp6A2qFOxqucAovoRk24"
51 "7vlsns1FApFRsp9mja0UZNObyKD1M6tP9Ep7lS76tFGMk+WDvXRJH5LEsyCpu7sSyl1r/O0M4K+KldRCqLlZd7rf8F5P8T0dn1azk05g7F4p0N/y9GNdzXbPZ9u0eZdI7SEdh8ZoOZp7NVZiBFfbWLSS5ZtyA2kbBa4i7GJ/cuAbEKOmqAkeQPiu96TGIcyjkXjS6mTPI+9UmKZYZC+OM8XdJ02y5KRoonCc19ZS8CAwEAAQ=="
52)
53 50
54_xmpp-client._tcp IN SRV 5 0 5222 ymir.yggdrasil.li. 51_xmpp-client._tcp IN SRV 5 0 5222 ymir.yggdrasil.li.
55_xmpp-server._tcp IN SRV 5 0 5269 ymir.yggdrasil.li. 52_xmpp-server._tcp IN SRV 5 0 5269 ymir.yggdrasil.li.
@@ -59,5 +56,3 @@ _infinoted._tcp IN SRV 5 0 6523 ymir.yggdrasil.li.
59_submission._tcp IN SRV 5 0 25 ymir.yggdrasil.li. 56_submission._tcp IN SRV 5 0 25 ymir.yggdrasil.li.
60_imap._tcp IN SRV 5 0 143 ymir.yggdrasil.li. 57_imap._tcp IN SRV 5 0 143 ymir.yggdrasil.li.
61_imaps._tcp IN SRV 5 0 993 ymir.yggdrasil.li. 58_imaps._tcp IN SRV 5 0 993 ymir.yggdrasil.li.
62
63_factorio._udp IN SRV 5 0 34197 game01.yggdrasil.li.
diff --git a/hosts/surtr/dns/zones/li.kleen.soa b/hosts/surtr/dns/zones/li.kleen.soa
index a1c7d35a..5dd3e697 100644
--- a/hosts/surtr/dns/zones/li.kleen.soa
+++ b/hosts/surtr/dns/zones/li.kleen.soa
@@ -1,7 +1,7 @@
1$ORIGIN kleen.li. 1$ORIGIN kleen.li.
2$TTL 3600 2$TTL 3600
3@ IN SOA ns.yggdrasil.li. hostmaster.yggdrasil.li ( 3@ IN SOA ns.yggdrasil.li. hostmaster.yggdrasil.li (
4 2023013000 ; serial 4 2025060701 ; serial
5 10800 ; refresh 5 10800 ; refresh
6 3600 ; retry 6 3600 ; retry
7 604800 ; expire 7 604800 ; expire
@@ -27,11 +27,8 @@ $TTL 3600
27 27
28_acme-challenge IN NS ns.yggdrasil.li. 28_acme-challenge IN NS ns.yggdrasil.li.
29 29
30ymir._domainkey IN TXT ( 30ymir._domainkey IN CNAME ymir._domainkey.yggdrasil.li.
31 "v=DKIM1;k=rsa;p=MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAq3cCKlk+VPhyAanLZTM0BCzUT/+fmxHioZcFk0uJk1akBYj7BRofR7eVNcLKpm3rwYMQgE+9vJH9p8SV6tws9EcWc8SMCqqGZlREYM7PmLDiTSK/vjCzkygfgFCb0EBNsY2A/fpP4rTeoxrbcBSvMkq97iY5rwyw4wXZVZXLiDaCj23s8POoxTk1ClqUJZJQ5x2" 31surtr._domainkey IN CNAME surtr._domainkey.yggdrasil.li.
32 "qzrC0RfN5kLZ9A7Gq2jB09vNxpXHYqABA0bJv88JiZM7hfkp9IafJZ+yCVMaBcJs4DAxnTjNAuFD9gm+qSFVY8+yeXqL6Qjo5PbruhyZRBW8RgRYT8t5n07XRglMGKKGMwOGLanrltcyXqB+GsDZBD36RAAwjFadnxdpDyRv4SgRP7ff2tKRrORYpmpN+mKdqw5j3J/nP6bXV1oAkyh9XQkPEIDi81WT87EZziTElDzVp6A2qFOxqucAovoRk24"
33 "7vlsns1FApFRsp9mja0UZNObyKD1M6tP9Ep7lS76tFGMk+WDvXRJH5LEsyCpu7sSyl1r/O0M4K+KldRCqLlZd7rf8F5P8T0dn1azk05g7F4p0N/y9GNdzXbPZ9u0eZdI7SEdh8ZoOZp7NVZiBFfbWLSS5ZtyA2kbBa4i7GJ/cuAbEKOmqAkeQPiu96TGIcyjkXjS6mTPI+9UmKZYZC+OM8XdJ02y5KRoonCc19ZS8CAwEAAQ=="
34)
35 32
36_xmpp-client._tcp IN SRV 5 0 5222 ymir.yggdrasil.li. 33_xmpp-client._tcp IN SRV 5 0 5222 ymir.yggdrasil.li.
37_xmpp-server._tcp IN SRV 5 0 5269 ymir.yggdrasil.li. 34_xmpp-server._tcp IN SRV 5 0 5269 ymir.yggdrasil.li.
diff --git a/hosts/surtr/dns/zones/li.synapse.soa b/hosts/surtr/dns/zones/li.synapse.soa
index 086d4a85..247cf025 100644
--- a/hosts/surtr/dns/zones/li.synapse.soa
+++ b/hosts/surtr/dns/zones/li.synapse.soa
@@ -1,7 +1,7 @@
1$ORIGIN synapse.li. 1$ORIGIN synapse.li.
2$TTL 3600 2$TTL 3600
3@ IN SOA ns.yggdrasil.li. hostmaster.yggdrasil.li ( 3@ IN SOA ns.yggdrasil.li. hostmaster.yggdrasil.li (
4 2023092100 ; serial 4 2025060701 ; serial
5 10800 ; refresh 5 10800 ; refresh
6 3600 ; retry 6 3600 ; retry
7 604800 ; expire 7 604800 ; expire
diff --git a/hosts/surtr/dns/zones/li.xmpp.soa b/hosts/surtr/dns/zones/li.xmpp.soa
deleted file mode 100644
index a9e98fb4..00000000
--- a/hosts/surtr/dns/zones/li.xmpp.soa
+++ /dev/null
@@ -1,43 +0,0 @@
1$ORIGIN xmpp.li.
2$TTL 3600
3@ IN SOA ns.yggdrasil.li. hostmaster.yggdrasil.li (
4 2023013000 ; serial
5 10800 ; refresh
6 3600 ; retry
7 604800 ; expire
8 3600 ; min TTL
9)
10 IN NS ns.yggdrasil.li.
11 IN NS ns.inwx.de.
12 IN NS ns2.inwx.de.
13 IN NS ns3.inwx.eu.
14
15@ IN CAA 128 issue "letsencrypt.org; validationmethods=dns-01"
16@ IN CAA 128 iodef "mailto:caa@yggdrasil.li"
17
18@ IN A 188.68.51.254
19@ IN AAAA 2a03:4000:6:d004::
20@ IN MX 0 ymir.yggdrasil.li.
21@ IN TXT "v=spf1 redirect=yggdrasil.li"
22
23* IN A 188.68.51.254
24* IN AAAA 2a03:4000:6:d004::
25* IN MX 0 ymir.yggdrasil.li.
26* IN TXT "v=spf1 redirect=yggdrasil.li"
27
28_acme-challenge IN NS ns.yggdrasil.li.
29
30ymir._domainkey IN TXT (
31 "v=DKIM1;k=rsa;p=MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAq3cCKlk+VPhyAanLZTM0BCzUT/+fmxHioZcFk0uJk1akBYj7BRofR7eVNcLKpm3rwYMQgE+9vJH9p8SV6tws9EcWc8SMCqqGZlREYM7PmLDiTSK/vjCzkygfgFCb0EBNsY2A/fpP4rTeoxrbcBSvMkq97iY5rwyw4wXZVZXLiDaCj23s8POoxTk1ClqUJZJQ5x2"
32 "qzrC0RfN5kLZ9A7Gq2jB09vNxpXHYqABA0bJv88JiZM7hfkp9IafJZ+yCVMaBcJs4DAxnTjNAuFD9gm+qSFVY8+yeXqL6Qjo5PbruhyZRBW8RgRYT8t5n07XRglMGKKGMwOGLanrltcyXqB+GsDZBD36RAAwjFadnxdpDyRv4SgRP7ff2tKRrORYpmpN+mKdqw5j3J/nP6bXV1oAkyh9XQkPEIDi81WT87EZziTElDzVp6A2qFOxqucAovoRk24"
33 "7vlsns1FApFRsp9mja0UZNObyKD1M6tP9Ep7lS76tFGMk+WDvXRJH5LEsyCpu7sSyl1r/O0M4K+KldRCqLlZd7rf8F5P8T0dn1azk05g7F4p0N/y9GNdzXbPZ9u0eZdI7SEdh8ZoOZp7NVZiBFfbWLSS5ZtyA2kbBa4i7GJ/cuAbEKOmqAkeQPiu96TGIcyjkXjS6mTPI+9UmKZYZC+OM8XdJ02y5KRoonCc19ZS8CAwEAAQ=="
34)
35
36_xmpp-client._tcp IN SRV 5 0 5222 ymir.yggdrasil.li.
37_xmpp-server._tcp IN SRV 5 0 5269 ymir.yggdrasil.li.
38
39_infinoted._tcp IN SRV 5 0 6523 ymir.yggdrasil.li.
40
41_submission._tcp IN SRV 5 0 25 ymir.yggdrasil.li.
42_imap._tcp IN SRV 5 0 143 ymir.yggdrasil.li.
43_imaps._tcp IN SRV 5 0 993 ymir.yggdrasil.li.
diff --git a/hosts/surtr/dns/zones/li.yggdrasil.soa b/hosts/surtr/dns/zones/li.yggdrasil.soa
index 9af6232f..500194ae 100644
--- a/hosts/surtr/dns/zones/li.yggdrasil.soa
+++ b/hosts/surtr/dns/zones/li.yggdrasil.soa
@@ -1,7 +1,7 @@
1$ORIGIN yggdrasil.li. 1$ORIGIN yggdrasil.li.
2$TTL 3600 2$TTL 3600
3@ IN SOA ns.yggdrasil.li. hostmaster.yggdrasil.li ( 3@ IN SOA ns.yggdrasil.li. hostmaster.yggdrasil.li (
4 2025010300 ; serial 4 2025060700 ; serial
5 10800 ; refresh 5 10800 ; refresh
6 3600 ; retry 6 3600 ; retry
7 604800 ; expire 7 604800 ; expire
@@ -77,12 +77,46 @@ _acme-challenge.immich IN NS ns.yggdrasil.li.
77 77
78immich IN HTTPS 1 . alpn="h2,h3" ipv4hint="202.61.241.61" ipv6hint="2a03:4000:52:ada::" 78immich IN HTTPS 1 . alpn="h2,h3" ipv4hint="202.61.241.61" ipv6hint="2a03:4000:52:ada::"
79 79
80paperless IN A 202.61.241.61
81paperless IN AAAA 2a03:4000:52:ada::
82paperless IN MX 0 surtr.yggdrasil.li
83paperless IN TXT "v=spf1 redirect=surtr.yggdrasil.li"
84_acme-challenge.paperless IN NS ns.yggdrasil.li.
85
86paperless IN HTTPS 1 . alpn="h2,h3" ipv4hint="202.61.241.61" ipv6hint="2a03:4000:52:ada::"
87
88hledger IN A 202.61.241.61
89hledger IN AAAA 2a03:4000:52:ada::
90hledger IN MX 0 surtr.yggdrasil.li
91hledger IN TXT "v=spf1 redirect=surtr.yggdrasil.li"
92_acme-challenge.hledger IN NS ns.yggdrasil.li.
93
94hledger IN HTTPS 1 . alpn="h2,h3" ipv4hint="202.61.241.61" ipv6hint="2a03:4000:52:ada::"
95
96audiobookshelf IN A 202.61.241.61
97audiobookshelf IN AAAA 2a03:4000:52:ada::
98audiobookshelf IN MX 0 surtr.yggdrasil.li
99audiobookshelf IN TXT "v=spf1 redirect=surtr.yggdrasil.li"
100_acme-challenge.audiobookshelf IN NS ns.yggdrasil.li.
101
102audiobookshelf IN HTTPS 1 . alpn="h2,h3" ipv4hint="202.61.241.61" ipv6hint="2a03:4000:52:ada::"
103
104kimai IN A 202.61.241.61
105kimai IN AAAA 2a03:4000:52:ada::
106kimai IN MX 0 surtr.yggdrasil.li
107kimai IN TXT "v=spf1 redirect=surtr.yggdrasil.li"
108_acme-challenge.kimai IN NS ns.yggdrasil.li.
109
110kimai IN HTTPS 1 . alpn="h2,h3" ipv4hint="202.61.241.61" ipv6hint="2a03:4000:52:ada::"
111
80vidhar IN AAAA 2a03:4000:52:ada:4:1:: 112vidhar IN AAAA 2a03:4000:52:ada:4:1::
81vidhar IN MX 0 ymir.yggdrasil.li 113vidhar IN MX 0 ymir.yggdrasil.li
82vidhar IN TXT "v=spf1 redirect=yggdrasil.li" 114vidhar IN TXT "v=spf1 redirect=yggdrasil.li"
83 115
84mailout IN A 188.68.51.254 116mailout IN A 188.68.51.254
85mailout IN AAAA 2a03:4000:6:d004:: 117mailout IN AAAA 2a03:4000:6:d004::
118mailout IN A 202.61.241.61
119mailout IN AAAA 2a03:4000:52:ada::
86mailout IN MX 0 ymir.yggdrasil.li 120mailout IN MX 0 ymir.yggdrasil.li
87mailout IN TXT "v=spf1 redirect=yggdrasil.li" 121mailout IN TXT "v=spf1 redirect=yggdrasil.li"
88 122
@@ -104,6 +138,3 @@ _infinoted._tcp IN SRV 5 0 6523 ymir.yggdrasil.li.
104_submission._tcp IN SRV 5 0 25 ymir.yggdrasil.li. 138_submission._tcp IN SRV 5 0 25 ymir.yggdrasil.li.
105_imap._tcp IN SRV 5 0 143 ymir.yggdrasil.li. 139_imap._tcp IN SRV 5 0 143 ymir.yggdrasil.li.
106_imaps._tcp IN SRV 5 0 993 ymir.yggdrasil.li. 140_imaps._tcp IN SRV 5 0 993 ymir.yggdrasil.li.
107
108game01 IN A 94.16.107.151
109game01 IN AAAA 2a03:4000:50:13d:34ee:a2ff:fed0:328f
diff --git a/hosts/surtr/dns/zones/org.dirty-haskell.soa b/hosts/surtr/dns/zones/org.dirty-haskell.soa
deleted file mode 100644
index 27f0d7f9..00000000
--- a/hosts/surtr/dns/zones/org.dirty-haskell.soa
+++ /dev/null
@@ -1,34 +0,0 @@
1$ORIGIN dirty-haskell.org.
2$TTL 3600
3@ IN SOA ns.yggdrasil.li. hostmaster.yggdrasil.li (
4 2023013000 ; serial
5 10800 ; refresh
6 3600 ; retry
7 604800 ; expire
8 3600 ; min TTL
9)
10 IN NS ns.yggdrasil.li.
11 IN NS ns.inwx.de.
12 IN NS ns2.inwx.de.
13 IN NS ns3.inwx.eu.
14
15@ IN CAA 128 issue "letsencrypt.org; validationmethods=dns-01"
16@ IN CAA 128 iodef "mailto:caa@yggdrasil.li"
17
18@ IN A 188.68.51.254
19@ IN AAAA 2a03:4000:6:d004::
20@ IN MX 10 ymir.yggdrasil.li.
21@ IN TXT "v=spf1 redirect=yggdrasil.li"
22
23* IN A 188.68.51.254
24* IN AAAA 2a03:4000:6:d004::
25* IN MX 0 ymir.yggdrasil.li.
26* IN TXT "v=spf1 redirect=yggdrasil.li"
27
28_acme-challenge IN NS ns.yggdrasil.li.
29
30ymir._domainkey IN TXT (
31 "v=DKIM1;k=rsa;p=MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAq3cCKlk+VPhyAanLZTM0BCzUT/+fmxHioZcFk0uJk1akBYj7BRofR7eVNcLKpm3rwYMQgE+9vJH9p8SV6tws9EcWc8SMCqqGZlREYM7PmLDiTSK/vjCzkygfgFCb0EBNsY2A/fpP4rTeoxrbcBSvMkq97iY5rwyw4wXZVZXLiDaCj23s8POoxTk1ClqUJZJQ5x2"
32 "qzrC0RfN5kLZ9A7Gq2jB09vNxpXHYqABA0bJv88JiZM7hfkp9IafJZ+yCVMaBcJs4DAxnTjNAuFD9gm+qSFVY8+yeXqL6Qjo5PbruhyZRBW8RgRYT8t5n07XRglMGKKGMwOGLanrltcyXqB+GsDZBD36RAAwjFadnxdpDyRv4SgRP7ff2tKRrORYpmpN+mKdqw5j3J/nP6bXV1oAkyh9XQkPEIDi81WT87EZziTElDzVp6A2qFOxqucAovoRk24"
33 "7vlsns1FApFRsp9mja0UZNObyKD1M6tP9Ep7lS76tFGMk+WDvXRJH5LEsyCpu7sSyl1r/O0M4K+KldRCqLlZd7rf8F5P8T0dn1azk05g7F4p0N/y9GNdzXbPZ9u0eZdI7SEdh8ZoOZp7NVZiBFfbWLSS5ZtyA2kbBa4i7GJ/cuAbEKOmqAkeQPiu96TGIcyjkXjS6mTPI+9UmKZYZC+OM8XdJ02y5KRoonCc19ZS8CAwEAAQ=="
34)
diff --git a/hosts/surtr/dns/zones/org.praseodym.soa b/hosts/surtr/dns/zones/org.praseodym.soa
index df505b4c..2b97ca19 100644
--- a/hosts/surtr/dns/zones/org.praseodym.soa
+++ b/hosts/surtr/dns/zones/org.praseodym.soa
@@ -1,7 +1,7 @@
1$ORIGIN praseodym.org. 1$ORIGIN praseodym.org.
2$TTL 3600 2$TTL 3600
3@ IN SOA ns.yggdrasil.li. hostmaster.yggdrasil.li ( 3@ IN SOA ns.yggdrasil.li. hostmaster.yggdrasil.li (
4 2023013000 ; serial 4 2025060701 ; serial
5 10800 ; refresh 5 10800 ; refresh
6 3600 ; retry 6 3600 ; retry
7 604800 ; expire 7 604800 ; expire
@@ -32,11 +32,8 @@ surtr IN AAAA 2a03:4000:52:ada::
32surtr IN MX 0 ymir.yggdrasil.li 32surtr IN MX 0 ymir.yggdrasil.li
33surtr IN TXT "v=spf1 redirect=yggdrasil.li" 33surtr IN TXT "v=spf1 redirect=yggdrasil.li"
34 34
35ymir._domainkey IN TXT ( 35ymir._domainkey IN CNAME ymir._domainkey.yggdrasil.li.
36 "v=DKIM1;k=rsa;p=MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAq3cCKlk+VPhyAanLZTM0BCzUT/+fmxHioZcFk0uJk1akBYj7BRofR7eVNcLKpm3rwYMQgE+9vJH9p8SV6tws9EcWc8SMCqqGZlREYM7PmLDiTSK/vjCzkygfgFCb0EBNsY2A/fpP4rTeoxrbcBSvMkq97iY5rwyw4wXZVZXLiDaCj23s8POoxTk1ClqUJZJQ5x2" 36surtr._domainkey IN CNAME surtr._domainkey.yggdrasil.li.
37 "qzrC0RfN5kLZ9A7Gq2jB09vNxpXHYqABA0bJv88JiZM7hfkp9IafJZ+yCVMaBcJs4DAxnTjNAuFD9gm+qSFVY8+yeXqL6Qjo5PbruhyZRBW8RgRYT8t5n07XRglMGKKGMwOGLanrltcyXqB+GsDZBD36RAAwjFadnxdpDyRv4SgRP7ff2tKRrORYpmpN+mKdqw5j3J/nP6bXV1oAkyh9XQkPEIDi81WT87EZziTElDzVp6A2qFOxqucAovoRk24"
38 "7vlsns1FApFRsp9mja0UZNObyKD1M6tP9Ep7lS76tFGMk+WDvXRJH5LEsyCpu7sSyl1r/O0M4K+KldRCqLlZd7rf8F5P8T0dn1azk05g7F4p0N/y9GNdzXbPZ9u0eZdI7SEdh8ZoOZp7NVZiBFfbWLSS5ZtyA2kbBa4i7GJ/cuAbEKOmqAkeQPiu96TGIcyjkXjS6mTPI+9UmKZYZC+OM8XdJ02y5KRoonCc19ZS8CAwEAAQ=="
39)
40 37
41_xmpp-client._tcp IN SRV 5 0 5222 ymir.yggdrasil.li. 38_xmpp-client._tcp IN SRV 5 0 5222 ymir.yggdrasil.li.
42_xmpp-server._tcp IN SRV 5 0 5269 ymir.yggdrasil.li. 39_xmpp-server._tcp IN SRV 5 0 5269 ymir.yggdrasil.li.
diff --git a/hosts/surtr/email/ccert-policy-server/ccert_policy_server/__main__.py b/hosts/surtr/email/ccert-policy-server/ccert_policy_server/__main__.py
index 00182523..7c931559 100644
--- a/hosts/surtr/email/ccert-policy-server/ccert_policy_server/__main__.py
+++ b/hosts/surtr/email/ccert-policy-server/ccert_policy_server/__main__.py
@@ -28,10 +28,12 @@ class PolicyHandler(StreamRequestHandler):
28 28
29 allowed = False 29 allowed = False
30 user = None 30 user = None
31 relay_eligible = False
31 if self.args['sasl_username']: 32 if self.args['sasl_username']:
32 user = self.args['sasl_username'] 33 user = self.args['sasl_username']
33 if self.args['ccert_subject']: 34 if self.args['ccert_subject']:
34 user = self.args['ccert_subject'] 35 user = self.args['ccert_subject']
36 relay_eligible = True
35 37
36 if user: 38 if user:
37 with self.server.db_pool.connection() as conn: 39 with self.server.db_pool.connection() as conn:
@@ -44,10 +46,16 @@ class PolicyHandler(StreamRequestHandler):
44 46
45 with conn.cursor() as cur: 47 with conn.cursor() as cur:
46 cur.row_factory = namedtuple_row 48 cur.row_factory = namedtuple_row
47 cur.execute('SELECT "mailbox"."mailbox" as "user", "local", "extension", "domain" FROM "mailbox" INNER JOIN "mailbox_mapping" ON "mailbox".id = "mailbox_mapping"."mailbox" WHERE "mailbox"."mailbox" = %(user)s AND ("local" = %(local)s OR "local" IS NULL) AND ("extension" = %(extension)s OR "extension" IS NULL) AND "domain" = %(domain)s', params = {'user': user, 'local': local, 'extension': extension if extension is not None else '', 'domain': domain}, prepare=True) 49
48 for record in cur: 50 if relay_eligible:
49 logger.debug('Received result: %s', record) 51 cur.execute('SELECT EXISTS(SELECT true FROM "mailbox" INNER JOIN "relay_access" ON "mailbox".id = "relay_access"."mailbox" WHERE "mailbox"."mailbox" = %(user)s AND ("domain" = %(domain)s OR %(domain)s ilike CONCAT(\'%%_.\', "domain"))) as "exists"', params = {'user': user, 'domain': domain})
50 allowed = True 52 if (row := cur.fetchone()) is not None:
53 allowed = row.exists
54
55 if not allowed:
56 cur.execute('SELECT EXISTS(SELECT true FROM "mailbox" INNER JOIN "mailbox_mapping" ON "mailbox".id = "mailbox_mapping"."mailbox" WHERE "mailbox"."mailbox" = %(user)s AND ("local" = %(local)s OR "local" IS NULL) AND ("extension" = %(extension)s OR "extension" IS NULL) AND "domain" = %(domain)s) as "exists"', params = {'user': user, 'local': local, 'extension': extension if extension is not None else '', 'domain': domain}, prepare=True)
57 if (row := cur.fetchone()) is not None:
58 allowed = row.exists
51 59
52 action = '550 5.7.0 Sender address not authorized for current user' 60 action = '550 5.7.0 Sender address not authorized for current user'
53 if allowed: 61 if allowed:
diff --git a/hosts/surtr/email/default.nix b/hosts/surtr/email/default.nix
index 4196a8bc..c993bb18 100644
--- a/hosts/surtr/email/default.nix
+++ b/hosts/surtr/email/default.nix
@@ -1,4 +1,4 @@
1{ config, pkgs, lib, flakeInputs, ... }: 1{ config, pkgs, lib, flake, flakeInputs, ... }:
2 2
3with lib; 3with lib;
4 4
@@ -15,7 +15,7 @@ let
15 15
16 for file in $out/pipe/bin/*; do 16 for file in $out/pipe/bin/*; do
17 wrapProgram $file \ 17 wrapProgram $file \
18 --set PATH "${pkgs.coreutils}/bin:${pkgs.rspamd}/bin" 18 --set PATH "${makeBinPath (with pkgs; [coreutils rspamd])}"
19 done 19 done
20 ''; 20 '';
21 }; 21 };
@@ -33,12 +33,28 @@ let
33 }); 33 });
34 }); 34 });
35 }; 35 };
36 internal-policy-server =
37 let
38 workspace = flakeInputs.uv2nix.lib.workspace.loadWorkspace { workspaceRoot = ./internal-policy-server; };
39 pythonSet = flake.lib.pythonSet {
40 inherit pkgs;
41 python = pkgs.python312;
42 overlay = workspace.mkPyprojectOverlay {
43 sourcePreference = "wheel";
44 };
45 };
46 virtualEnv = pythonSet.mkVirtualEnv "internal-policy-server-env" workspace.deps.default;
47 in virtualEnv.overrideAttrs (oldAttrs: {
48 meta = (oldAttrs.meta or {}) // {
49 mainProgram = "internal-policy-server";
50 };
51 });
36 52
37 nftables-nologin-script = pkgs.writeScript "nftables-mail-nologin" '' 53 nftables-nologin-script = pkgs.resholve.writeScript "nftables-mail-nologin" {
38 #!${pkgs.zsh}/bin/zsh 54 inputs = with pkgs; [inetutils nftables gnugrep findutils];
39 55 interpreter = lib.getExe pkgs.zsh;
56 } ''
40 set -e 57 set -e
41 export PATH="${lib.makeBinPath (with pkgs; [inetutils nftables])}:$PATH"
42 58
43 typeset -a as_sets mnt_bys route route6 59 typeset -a as_sets mnt_bys route route6
44 as_sets=(${lib.escapeShellArgs config.services.email.nologin.ASSets}) 60 as_sets=(${lib.escapeShellArgs config.services.email.nologin.ASSets})
@@ -51,7 +67,7 @@ let
51 elif [[ "''${line}" =~ "^route6:\s+(.+)$" ]]; then 67 elif [[ "''${line}" =~ "^route6:\s+(.+)$" ]]; then
52 route6+=($match[1]) 68 route6+=($match[1])
53 fi 69 fi
54 done < <(whois -h whois.radb.net "!i''${as_set},1" | egrep -o 'AS[0-9]+' | xargs -- whois -h whois.radb.net -- -i origin) 70 done < <(whois -h whois.radb.net "!i''${as_set},1" | grep -Eo 'AS[0-9]+' | xargs whois -h whois.radb.net -- -i origin)
55 done 71 done
56 for mnt_by in $mnt_bys; do 72 for mnt_by in $mnt_bys; do
57 while IFS=$'\n' read line; do 73 while IFS=$'\n' read line; do
@@ -190,16 +206,19 @@ in {
190 "reject_unauth_destination" 206 "reject_unauth_destination"
191 "reject_unknown_recipient_domain" 207 "reject_unknown_recipient_domain"
192 "reject_unverified_recipient" 208 "reject_unverified_recipient"
209 "check_policy_service unix:/run/postfix-internal-policy.sock"
193 ]; 210 ];
194 unverified_recipient_reject_code = "550"; 211 unverified_recipient_reject_code = "550";
195 unverified_recipient_reject_reason = "Recipient address lookup failed"; 212 unverified_recipient_reject_reason = "Recipient address lookup failed";
196 address_verify_map = "internal:address_verify_map"; 213 address_verify_map = "internal:address_verify_map";
197 address_verify_positive_expire_time = "1h"; 214 address_verify_positive_expire_time = "1h";
198 address_verify_positive_refresh_time = "15m"; 215 address_verify_positive_refresh_time = "15m";
199 address_verify_negative_expire_time = "15s"; 216 address_verify_negative_expire_time = "5m";
200 address_verify_negative_refresh_time = "5s"; 217 address_verify_negative_refresh_time = "1m";
201 address_verify_cache_cleanup_interval = "5s"; 218 address_verify_cache_cleanup_interval = "12h";
219 address_verify_poll_count = "\${stress?15}\${stress:30}";
202 address_verify_poll_delay = "1s"; 220 address_verify_poll_delay = "1s";
221 address_verify_sender_ttl = "30045s";
203 222
204 smtpd_relay_restrictions = [ 223 smtpd_relay_restrictions = [
205 "check_ccert_access ${relay_ccert}" 224 "check_ccert_access ${relay_ccert}"
@@ -213,7 +232,7 @@ in {
213 smtpd_client_event_limit_exceptions = ""; 232 smtpd_client_event_limit_exceptions = "";
214 233
215 milter_default_action = "accept"; 234 milter_default_action = "accept";
216 smtpd_milters = [config.services.opendkim.socket "local:/run/rspamd/rspamd-milter.sock"]; 235 smtpd_milters = [config.services.opendkim.socket "local:/run/rspamd/rspamd-milter.sock" "local:/run/postsrsd/postsrsd-milter.sock"];
217 non_smtpd_milters = [config.services.opendkim.socket "local:/run/rspamd/rspamd-milter.sock"]; 236 non_smtpd_milters = [config.services.opendkim.socket "local:/run/rspamd/rspamd-milter.sock"];
218 237
219 alias_maps = ""; 238 alias_maps = "";
@@ -235,11 +254,6 @@ in {
235 ::/0 silent-discard, dsn 254 ::/0 silent-discard, dsn
236 ''}"; 255 ''}";
237 256
238 sender_canonical_maps = "tcp:localhost:${toString config.services.postsrsd.forwardPort}";
239 sender_canonical_classes = "envelope_sender";
240 recipient_canonical_maps = "tcp:localhost:${toString config.services.postsrsd.reversePort}";
241 recipient_canonical_classes = ["envelope_recipient" "header_recipient"];
242
243 virtual_mailbox_domains = ''pgsql:${pkgs.writeText "virtual_mailbox_domains.cf" '' 257 virtual_mailbox_domains = ''pgsql:${pkgs.writeText "virtual_mailbox_domains.cf" ''
244 hosts = postgresql:///email 258 hosts = postgresql:///email
245 dbname = email 259 dbname = email
@@ -254,11 +268,24 @@ in {
254 virtual_transport = "dvlmtp:unix:/run/dovecot-lmtp"; 268 virtual_transport = "dvlmtp:unix:/run/dovecot-lmtp";
255 smtputf8_enable = false; 269 smtputf8_enable = false;
256 270
257 authorized_submit_users = "inline:{ root= postfwd= }"; 271 authorized_submit_users = "inline:{ root= postfwd= dovecot2= }";
272 authorized_flush_users = "inline:{ root= }";
273 authorized_mailq_users = "inline:{ root= }";
258 274
259 postscreen_access_list = ""; 275 postscreen_access_list = "";
260 postscreen_denylist_action = "drop"; 276 postscreen_denylist_action = "drop";
261 postscreen_greet_action = "enforce"; 277 postscreen_greet_action = "enforce";
278
279 sender_bcc_maps = ''pgsql:${pkgs.writeText "sender_bcc_maps.cf" ''
280 hosts = postgresql:///email
281 dbname = email
282 query = SELECT value FROM sender_bcc_maps WHERE key = '%s'
283 ''}'';
284 recipient_bcc_maps = ''pgsql:${pkgs.writeText "recipient_bcc_maps.cf" ''
285 hosts = postgresql:///email
286 dbname = email
287 query = SELECT value FROM recipient_bcc_maps WHERE key = '%s'
288 ''}'';
262 }; 289 };
263 masterConfig = { 290 masterConfig = {
264 "465" = { 291 "465" = {
@@ -283,7 +310,7 @@ in {
283 hosts = postgresql:///email 310 hosts = postgresql:///email
284 dbname = email 311 dbname = email
285 query = SELECT action FROM virtual_mailbox_access WHERE lookup = '%s' OR (lookup = regexp_replace('%s', '\+[^@]*@', '@') AND NOT EXISTS (SELECT 1 FROM virtual_mailbox_access WHERE lookup = '%s')) 312 query = SELECT action FROM virtual_mailbox_access WHERE lookup = '%s' OR (lookup = regexp_replace('%s', '\+[^@]*@', '@') AND NOT EXISTS (SELECT 1 FROM virtual_mailbox_access WHERE lookup = '%s'))
286 ''},permit_tls_all_clientcerts,reject}'' 313 ''},check_policy_service unix:/run/postfix-internal-policy.sock,permit_tls_all_clientcerts,reject}''
287 "-o" "smtpd_relay_restrictions=permit_tls_all_clientcerts,reject" 314 "-o" "smtpd_relay_restrictions=permit_tls_all_clientcerts,reject"
288 "-o" "{smtpd_data_restrictions = check_policy_service unix:/run/postfwd3/postfwd3.sock}" 315 "-o" "{smtpd_data_restrictions = check_policy_service unix:/run/postfwd3/postfwd3.sock}"
289 "-o" "unverified_sender_reject_code=550" 316 "-o" "unverified_sender_reject_code=550"
@@ -313,7 +340,7 @@ in {
313 hosts = postgresql:///email 340 hosts = postgresql:///email
314 dbname = email 341 dbname = email
315 query = SELECT action FROM virtual_mailbox_access WHERE lookup = '%s' OR (lookup = regexp_replace('%s', '\+[^@]*@', '@') AND NOT EXISTS (SELECT 1 FROM virtual_mailbox_access WHERE lookup = '%s')) 342 query = SELECT action FROM virtual_mailbox_access WHERE lookup = '%s' OR (lookup = regexp_replace('%s', '\+[^@]*@', '@') AND NOT EXISTS (SELECT 1 FROM virtual_mailbox_access WHERE lookup = '%s'))
316 ''},permit_sasl_authenticated,reject}'' 343 ''},check_policy_service unix:/run/postfix-internal-policy.sock,permit_sasl_authenticated,reject}''
317 "-o" "smtpd_relay_restrictions=permit_sasl_authenticated,reject" 344 "-o" "smtpd_relay_restrictions=permit_sasl_authenticated,reject"
318 "-o" "{smtpd_data_restrictions = check_policy_service unix:/run/postfwd3/postfwd3.sock}" 345 "-o" "{smtpd_data_restrictions = check_policy_service unix:/run/postfwd3/postfwd3.sock}"
319 "-o" "unverified_sender_reject_code=550" 346 "-o" "unverified_sender_reject_code=550"
@@ -364,17 +391,19 @@ in {
364 391
365 services.postsrsd = { 392 services.postsrsd = {
366 enable = true; 393 enable = true;
367 domain = "surtr.yggdrasil.li"; 394 domains = [ "surtr.yggdrasil.li" ] ++ concatMap (domain: [".${domain}" domain]) emailDomains;
368 separator = "+"; 395 separator = "+";
369 excludeDomains = [ "surtr.yggdrasil.li" 396 extraConfig = ''
370 ] ++ concatMap (domain: [".${domain}" domain]) emailDomains; 397 socketmap = unix:/run/postsrsd/postsrsd-socketmap.sock
398 milter = unix:/run/postsrsd/postsrsd-milter.sock
399 '';
371 }; 400 };
372 401
373 services.opendkim = { 402 services.opendkim = {
374 enable = true; 403 enable = true;
375 user = "postfix"; group = "postfix"; 404 user = "postfix"; group = "postfix";
376 socket = "local:/run/opendkim/opendkim.sock"; 405 socket = "local:/run/opendkim/opendkim.sock";
377 domains = ''csl:${concatStringsSep "," (["surtr.yggdrasil.li"] ++ emailDomains)}''; 406 domains = ''csl:${concatStringsSep "," (["surtr.yggdrasil.li" "yggdrasil.li" "141.li" "kleen.li" "synapse.li" "praseodym.org"] ++ emailDomains)}'';
378 selector = "surtr"; 407 selector = "surtr";
379 configFile = builtins.toFile "opendkim.conf" '' 408 configFile = builtins.toFile "opendkim.conf" ''
380 Syslog true 409 Syslog true
@@ -484,6 +513,7 @@ in {
484 513
485 users.groups.${config.services.redis.servers.rspamd.user}.members = [ config.services.rspamd.user ]; 514 users.groups.${config.services.redis.servers.rspamd.user}.members = [ config.services.rspamd.user ];
486 515
516 environment.systemPackages = with pkgs; [ dovecot_pigeonhole dovecot_fts_xapian ];
487 services.dovecot2 = { 517 services.dovecot2 = {
488 enable = true; 518 enable = true;
489 enablePAM = false; 519 enablePAM = false;
@@ -491,7 +521,6 @@ in {
491 sslServerKey = "/run/credentials/dovecot2.service/surtr.yggdrasil.li.key.pem"; 521 sslServerKey = "/run/credentials/dovecot2.service/surtr.yggdrasil.li.key.pem";
492 sslCACert = toString ./ca/ca.crt; 522 sslCACert = toString ./ca/ca.crt;
493 mailLocation = "maildir:/var/lib/mail/%u/maildir:UTF-8:INDEX=/var/lib/dovecot/indices/%u"; 523 mailLocation = "maildir:/var/lib/mail/%u/maildir:UTF-8:INDEX=/var/lib/dovecot/indices/%u";
494 modules = with pkgs; [ dovecot_pigeonhole dovecot_fts_xapian ];
495 mailPlugins.globally.enable = [ "fts" "fts_xapian" ]; 524 mailPlugins.globally.enable = [ "fts" "fts_xapian" ];
496 protocols = [ "lmtp" "sieve" ]; 525 protocols = [ "lmtp" "sieve" ];
497 sieve = { 526 sieve = {
@@ -673,7 +702,7 @@ in {
673 plugin { 702 plugin {
674 plugin = fts fts_xapian 703 plugin = fts fts_xapian
675 fts = xapian 704 fts = xapian
676 fts_xapian = partial=2 full=20 attachments=1 verbose=1 705 fts_xapian = partial=3 full=20 attachments=1 verbose=1
677 706
678 fts_autoindex = yes 707 fts_autoindex = yes
679 708
@@ -693,7 +722,7 @@ in {
693 startAt = "*-*-* 22:00:00 Europe/Berlin"; 722 startAt = "*-*-* 22:00:00 Europe/Berlin";
694 serviceConfig = { 723 serviceConfig = {
695 Type = "oneshot"; 724 Type = "oneshot";
696 ExecStart = "${pkgs.dovecot}/bin/doveadm fts optimize -A"; 725 ExecStart = "${getExe' pkgs.dovecot "doveadm"} fts optimize -A";
697 PrivateDevices = true; 726 PrivateDevices = true;
698 PrivateNetwork = true; 727 PrivateNetwork = true;
699 ProtectKernelTunables = true; 728 ProtectKernelTunables = true;
@@ -778,7 +807,7 @@ in {
778 systemd.services.dovecot2 = { 807 systemd.services.dovecot2 = {
779 preStart = '' 808 preStart = ''
780 for f in /etc/dovecot/sieve_flag.d/*.sieve /etc/dovecot/sieve_before.d/*.sieve; do 809 for f in /etc/dovecot/sieve_flag.d/*.sieve /etc/dovecot/sieve_before.d/*.sieve; do
781 ${pkgs.dovecot_pigeonhole}/bin/sievec $f 810 ${getExe' pkgs.dovecot_pigeonhole "sievec"} $f
782 done 811 done
783 ''; 812 '';
784 813
@@ -845,15 +874,16 @@ in {
845 charset utf-8; 874 charset utf-8;
846 source_charset utf-8; 875 source_charset utf-8;
847 ''; 876 '';
848 root = pkgs.runCommand "mta-sts.${domain}" {} '' 877 root = pkgs.writeTextFile {
849 mkdir -p $out/.well-known 878 name = "mta-sts.${domain}";
850 cp ${pkgs.writeText "mta-sts.${domain}.txt" '' 879 destination = "/.well-known/mta-sts.txt";
880 text = ''
851 version: STSv1 881 version: STSv1
852 mode: enforce 882 mode: enforce
853 max_age: 2419200 883 max_age: 2419200
854 mx: mailin.${domain} 884 mx: mailin.${domain}
855 ''} $out/.well-known/mta-sts.txt 885 '';
856 ''; 886 };
857 }; 887 };
858 }) emailDomains); 888 }) emailDomains);
859 }; 889 };
@@ -870,7 +900,7 @@ in {
870 systemd.services.spm = { 900 systemd.services.spm = {
871 serviceConfig = { 901 serviceConfig = {
872 Type = "notify"; 902 Type = "notify";
873 ExecStart = "${pkgs.spm}/bin/spm-server"; 903 ExecStart = getExe' pkgs.spm "spm-server";
874 User = "spm"; 904 User = "spm";
875 Group = "spm"; 905 Group = "spm";
876 906
@@ -928,7 +958,7 @@ in {
928 serviceConfig = { 958 serviceConfig = {
929 Type = "notify"; 959 Type = "notify";
930 960
931 ExecStart = "${ccert-policy-server}/bin/ccert-policy-server"; 961 ExecStart = getExe' ccert-policy-server "ccert-policy-server";
932 962
933 Environment = [ 963 Environment = [
934 "PGDATABASE=email" 964 "PGDATABASE=email"
@@ -961,6 +991,53 @@ in {
961 }; 991 };
962 users.groups."postfix-ccert-sender-policy" = {}; 992 users.groups."postfix-ccert-sender-policy" = {};
963 993
994 systemd.sockets."postfix-internal-policy" = {
995 requiredBy = ["postfix.service"];
996 wants = ["postfix-internal-policy.service"];
997 socketConfig = {
998 ListenStream = "/run/postfix-internal-policy.sock";
999 };
1000 };
1001 systemd.services."postfix-internal-policy" = {
1002 after = [ "postgresql.service" ];
1003 bindsTo = [ "postgresql.service" ];
1004
1005 serviceConfig = {
1006 Type = "notify";
1007
1008 ExecStart = lib.getExe internal-policy-server;
1009
1010 Environment = [
1011 "PGDATABASE=email"
1012 ];
1013
1014 DynamicUser = false;
1015 User = "postfix-internal-policy";
1016 Group = "postfix-internal-policy";
1017 ProtectSystem = "strict";
1018 SystemCallFilter = "@system-service";
1019 NoNewPrivileges = true;
1020 ProtectKernelTunables = true;
1021 ProtectKernelModules = true;
1022 ProtectKernelLogs = true;
1023 ProtectControlGroups = true;
1024 MemoryDenyWriteExecute = true;
1025 RestrictSUIDSGID = true;
1026 KeyringMode = "private";
1027 ProtectClock = true;
1028 RestrictRealtime = true;
1029 PrivateDevices = true;
1030 PrivateTmp = true;
1031 ProtectHostname = true;
1032 ReadWritePaths = ["/run/postgresql"];
1033 };
1034 };
1035 users.users."postfix-internal-policy" = {
1036 isSystemUser = true;
1037 group = "postfix-internal-policy";
1038 };
1039 users.groups."postfix-internal-policy" = {};
1040
964 services.postfwd = { 1041 services.postfwd = {
965 enable = true; 1042 enable = true;
966 cache = false; 1043 cache = false;
diff --git a/hosts/surtr/email/internal-policy-server/.envrc b/hosts/surtr/email/internal-policy-server/.envrc
new file mode 100644
index 00000000..2c909235
--- /dev/null
+++ b/hosts/surtr/email/internal-policy-server/.envrc
@@ -0,0 +1,4 @@
1use flake
2
3[[ -d ".venv" ]] || ( uv venv && uv sync )
4. .venv/bin/activate
diff --git a/hosts/surtr/email/internal-policy-server/.gitignore b/hosts/surtr/email/internal-policy-server/.gitignore
new file mode 100644
index 00000000..4ccfae70
--- /dev/null
+++ b/hosts/surtr/email/internal-policy-server/.gitignore
@@ -0,0 +1,2 @@
1.venv
2**/__pycache__
diff --git a/hosts/surtr/email/internal-policy-server/internal_policy_server/__init__.py b/hosts/surtr/email/internal-policy-server/internal_policy_server/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/hosts/surtr/email/internal-policy-server/internal_policy_server/__init__.py
diff --git a/hosts/surtr/email/internal-policy-server/internal_policy_server/__main__.py b/hosts/surtr/email/internal-policy-server/internal_policy_server/__main__.py
new file mode 100644
index 00000000..04f1a59a
--- /dev/null
+++ b/hosts/surtr/email/internal-policy-server/internal_policy_server/__main__.py
@@ -0,0 +1,106 @@
1from systemd.daemon import listen_fds
2from sdnotify import SystemdNotifier
3from socketserver import StreamRequestHandler, ThreadingMixIn
4from systemd_socketserver import SystemdSocketServer
5import sys
6from threading import Thread
7from psycopg_pool import ConnectionPool
8from psycopg.rows import namedtuple_row
9
10import logging
11
12
13class PolicyHandler(StreamRequestHandler):
14 def handle(self):
15 logger.debug('Handling new connection...')
16
17 self.args = dict()
18
19 line = None
20 while line := self.rfile.readline().removesuffix(b'\n'):
21 if b'=' not in line:
22 break
23
24 key, val = line.split(sep=b'=', maxsplit=1)
25 self.args[key.decode()] = val.decode()
26
27 logger.info('Connection parameters: %s', self.args)
28
29 allowed = False
30 user = None
31 if self.args['sasl_username']:
32 user = self.args['sasl_username']
33 if self.args['ccert_subject']:
34 user = self.args['ccert_subject']
35
36 with self.server.db_pool.connection() as conn:
37 local, domain = self.args['recipient'].split(sep='@', maxsplit=1)
38 extension = None
39 if '+' in local:
40 local, extension = local.split(sep='+', maxsplit=1)
41
42 logger.debug('Parsed recipient address: %s', {'local': local, 'extension': extension, 'domain': domain})
43
44 with conn.cursor() as cur:
45 cur.row_factory = namedtuple_row
46 cur.execute('SELECT id, internal FROM "mailbox_mapping" WHERE ("local" = %(local)s OR "local" IS NULL) AND ("extension" = %(extension)s OR "extension" IS NULL) AND "domain" = %(domain)s', params = {'local': local, 'extension': extension if extension is not None else '', 'domain': domain}, prepare = True)
47 if (row := cur.fetchone()) is not None:
48 if not row.internal:
49 logger.debug('Recipient mailbox is not internal')
50 allowed = True
51 elif user:
52 cur.execute('SELECT EXISTS(SELECT true FROM "mailbox_mapping_access" INNER JOIN "mailbox" ON "mailbox".id = "mailbox_mapping_access"."mailbox" WHERE mailbox_mapping = %(mailbox_mapping)s AND "mailbox"."mailbox" = %(user)s) as "exists"', params = { 'mailbox_mapping': row.id, 'user': user }, prepare = True)
53 if (row := cur.fetchone()) is not None:
54 allowed = row.exists
55 else:
56 logger.debug('Recipient is not local')
57 allowed = True
58
59 action = '550 5.7.0 Recipient mailbox mapping not authorized for current user'
60 if allowed:
61 action = 'DUNNO'
62
63 logger.info('Reached verdict: %s', {'allowed': allowed, 'action': action})
64 self.wfile.write(f'action={action}\n\n'.encode())
65
66class ThreadedSystemdSocketServer(ThreadingMixIn, SystemdSocketServer):
67 def __init__(self, fd, RequestHandlerClass):
68 super().__init__(fd, RequestHandlerClass)
69
70 self.db_pool = ConnectionPool(min_size=1)
71 self.db_pool.wait()
72
73def main():
74 global logger
75 logger = logging.getLogger(__name__)
76 console_handler = logging.StreamHandler()
77 console_handler.setFormatter( logging.Formatter('[%(levelname)s](%(name)s): %(message)s') )
78 if sys.stderr.isatty():
79 console_handler.setFormatter( logging.Formatter('%(asctime)s [%(levelname)s](%(name)s): %(message)s') )
80 logger.addHandler(console_handler)
81 logger.setLevel(logging.DEBUG)
82
83 # log uncaught exceptions
84 def log_exceptions(type, value, tb):
85 global logger
86
87 logger.error(value)
88 sys.__excepthook__(type, value, tb) # calls default excepthook
89
90 sys.excepthook = log_exceptions
91
92 fds = listen_fds()
93 servers = [ThreadedSystemdSocketServer(fd, PolicyHandler) for fd in fds]
94
95 if servers:
96 for server in servers:
97 Thread(name=f'Server for fd{server.fileno()}', target=server.serve_forever).start()
98 else:
99 return 2
100
101 SystemdNotifier().notify('READY=1')
102
103 return 0
104
105if __name__ == '__main__':
106 sys.exit(main())
diff --git a/hosts/surtr/email/internal-policy-server/pyproject.toml b/hosts/surtr/email/internal-policy-server/pyproject.toml
new file mode 100644
index 00000000..c697cd01
--- /dev/null
+++ b/hosts/surtr/email/internal-policy-server/pyproject.toml
@@ -0,0 +1,18 @@
1[project]
2name = "internal-policy-server"
3version = "0.1.0"
4requires-python = ">=3.12"
5dependencies = [
6 "psycopg>=3.2.9",
7 "psycopg-binary>=3.2.9",
8 "psycopg-pool>=3.2.6",
9 "sdnotify>=0.3.2",
10 "systemd-socketserver>=1.0",
11]
12
13[project.scripts]
14internal-policy-server = "internal_policy_server.__main__:main"
15
16[build-system]
17requires = ["hatchling"]
18build-backend = "hatchling.build"
diff --git a/hosts/surtr/email/internal-policy-server/uv.lock b/hosts/surtr/email/internal-policy-server/uv.lock
new file mode 100644
index 00000000..f7a4e729
--- /dev/null
+++ b/hosts/surtr/email/internal-policy-server/uv.lock
@@ -0,0 +1,119 @@
1version = 1
2revision = 2
3requires-python = ">=3.12"
4
5[[package]]
6name = "internal-policy-server"
7version = "0.1.0"
8source = { editable = "." }
9dependencies = [
10 { name = "psycopg" },
11 { name = "psycopg-binary" },
12 { name = "psycopg-pool" },
13 { name = "sdnotify" },
14 { name = "systemd-socketserver" },
15]
16
17[package.metadata]
18requires-dist = [
19 { name = "psycopg", specifier = ">=3.2.9" },
20 { name = "psycopg-binary", specifier = ">=3.2.9" },
21 { name = "psycopg-pool", specifier = ">=3.2.6" },
22 { name = "sdnotify", specifier = ">=0.3.2" },
23 { name = "systemd-socketserver", specifier = ">=1.0" },
24]
25
26[[package]]
27name = "psycopg"
28version = "3.2.9"
29source = { registry = "https://pypi.org/simple" }
30dependencies = [
31 { name = "typing-extensions", marker = "python_full_version < '3.13'" },
32 { name = "tzdata", marker = "sys_platform == 'win32'" },
33]
34sdist = { url = "https://files.pythonhosted.org/packages/27/4a/93a6ab570a8d1a4ad171a1f4256e205ce48d828781312c0bbaff36380ecb/psycopg-3.2.9.tar.gz", hash = "sha256:2fbb46fcd17bc81f993f28c47f1ebea38d66ae97cc2dbc3cad73b37cefbff700", size = 158122, upload-time = "2025-05-13T16:11:15.533Z" }
35wheels = [
36 { url = "https://files.pythonhosted.org/packages/44/b0/a73c195a56eb6b92e937a5ca58521a5c3346fb233345adc80fd3e2f542e2/psycopg-3.2.9-py3-none-any.whl", hash = "sha256:01a8dadccdaac2123c916208c96e06631641c0566b22005493f09663c7a8d3b6", size = 202705, upload-time = "2025-05-13T16:06:26.584Z" },
37]
38
39[[package]]
40name = "psycopg-binary"
41version = "3.2.9"
42source = { registry = "https://pypi.org/simple" }
43wheels = [
44 { url = "https://files.pythonhosted.org/packages/29/6f/ec9957e37a606cd7564412e03f41f1b3c3637a5be018d0849914cb06e674/psycopg_binary-3.2.9-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:be7d650a434921a6b1ebe3fff324dbc2364393eb29d7672e638ce3e21076974e", size = 4022205, upload-time = "2025-05-13T16:07:48.195Z" },
45 { url = "https://files.pythonhosted.org/packages/6b/ba/497b8bea72b20a862ac95a94386967b745a472d9ddc88bc3f32d5d5f0d43/psycopg_binary-3.2.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6a76b4722a529390683c0304501f238b365a46b1e5fb6b7249dbc0ad6fea51a0", size = 4083795, upload-time = "2025-05-13T16:07:50.917Z" },
46 { url = "https://files.pythonhosted.org/packages/42/07/af9503e8e8bdad3911fd88e10e6a29240f9feaa99f57d6fac4a18b16f5a0/psycopg_binary-3.2.9-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:96a551e4683f1c307cfc3d9a05fec62c00a7264f320c9962a67a543e3ce0d8ff", size = 4655043, upload-time = "2025-05-13T16:07:54.857Z" },
47 { url = "https://files.pythonhosted.org/packages/28/ed/aff8c9850df1648cc6a5cc7a381f11ee78d98a6b807edd4a5ae276ad60ad/psycopg_binary-3.2.9-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:61d0a6ceed8f08c75a395bc28cb648a81cf8dee75ba4650093ad1a24a51c8724", size = 4477972, upload-time = "2025-05-13T16:07:57.925Z" },
48 { url = "https://files.pythonhosted.org/packages/5c/bd/8e9d1b77ec1a632818fe2f457c3a65af83c68710c4c162d6866947d08cc5/psycopg_binary-3.2.9-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad280bbd409bf598683dda82232f5215cfc5f2b1bf0854e409b4d0c44a113b1d", size = 4737516, upload-time = "2025-05-13T16:08:01.616Z" },
49 { url = "https://files.pythonhosted.org/packages/46/ec/222238f774cd5a0881f3f3b18fb86daceae89cc410f91ef6a9fb4556f236/psycopg_binary-3.2.9-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76eddaf7fef1d0994e3d536ad48aa75034663d3a07f6f7e3e601105ae73aeff6", size = 4436160, upload-time = "2025-05-13T16:08:04.278Z" },
50 { url = "https://files.pythonhosted.org/packages/37/78/af5af2a1b296eeca54ea7592cd19284739a844974c9747e516707e7b3b39/psycopg_binary-3.2.9-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:52e239cd66c4158e412318fbe028cd94b0ef21b0707f56dcb4bdc250ee58fd40", size = 3753518, upload-time = "2025-05-13T16:08:07.567Z" },
51 { url = "https://files.pythonhosted.org/packages/ec/ac/8a3ed39ea069402e9e6e6a2f79d81a71879708b31cc3454283314994b1ae/psycopg_binary-3.2.9-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:08bf9d5eabba160dd4f6ad247cf12f229cc19d2458511cab2eb9647f42fa6795", size = 3313598, upload-time = "2025-05-13T16:08:09.999Z" },
52 { url = "https://files.pythonhosted.org/packages/da/43/26549af068347c808fbfe5f07d2fa8cef747cfff7c695136172991d2378b/psycopg_binary-3.2.9-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:1b2cf018168cad87580e67bdde38ff5e51511112f1ce6ce9a8336871f465c19a", size = 3407289, upload-time = "2025-05-13T16:08:12.66Z" },
53 { url = "https://files.pythonhosted.org/packages/67/55/ea8d227c77df8e8aec880ded398316735add8fda5eb4ff5cc96fac11e964/psycopg_binary-3.2.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:14f64d1ac6942ff089fc7e926440f7a5ced062e2ed0949d7d2d680dc5c00e2d4", size = 3472493, upload-time = "2025-05-13T16:08:15.672Z" },
54 { url = "https://files.pythonhosted.org/packages/3c/02/6ff2a5bc53c3cd653d281666728e29121149179c73fddefb1e437024c192/psycopg_binary-3.2.9-cp312-cp312-win_amd64.whl", hash = "sha256:7a838852e5afb6b4126f93eb409516a8c02a49b788f4df8b6469a40c2157fa21", size = 2927400, upload-time = "2025-05-13T16:08:18.652Z" },
55 { url = "https://files.pythonhosted.org/packages/28/0b/f61ff4e9f23396aca674ed4d5c9a5b7323738021d5d72d36d8b865b3deaf/psycopg_binary-3.2.9-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:98bbe35b5ad24a782c7bf267596638d78aa0e87abc7837bdac5b2a2ab954179e", size = 4017127, upload-time = "2025-05-13T16:08:21.391Z" },
56 { url = "https://files.pythonhosted.org/packages/bc/00/7e181fb1179fbfc24493738b61efd0453d4b70a0c4b12728e2b82db355fd/psycopg_binary-3.2.9-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:72691a1615ebb42da8b636c5ca9f2b71f266be9e172f66209a361c175b7842c5", size = 4080322, upload-time = "2025-05-13T16:08:24.049Z" },
57 { url = "https://files.pythonhosted.org/packages/58/fd/94fc267c1d1392c4211e54ccb943be96ea4032e761573cf1047951887494/psycopg_binary-3.2.9-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25ab464bfba8c401f5536d5aa95f0ca1dd8257b5202eede04019b4415f491351", size = 4655097, upload-time = "2025-05-13T16:08:27.376Z" },
58 { url = "https://files.pythonhosted.org/packages/41/17/31b3acf43de0b2ba83eac5878ff0dea5a608ca2a5c5dd48067999503a9de/psycopg_binary-3.2.9-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0e8aeefebe752f46e3c4b769e53f1d4ad71208fe1150975ef7662c22cca80fab", size = 4482114, upload-time = "2025-05-13T16:08:30.781Z" },
59 { url = "https://files.pythonhosted.org/packages/85/78/b4d75e5fd5a85e17f2beb977abbba3389d11a4536b116205846b0e1cf744/psycopg_binary-3.2.9-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b7e4e4dd177a8665c9ce86bc9caae2ab3aa9360b7ce7ec01827ea1baea9ff748", size = 4737693, upload-time = "2025-05-13T16:08:34.625Z" },
60 { url = "https://files.pythonhosted.org/packages/3b/95/7325a8550e3388b00b5e54f4ced5e7346b531eb4573bf054c3dbbfdc14fe/psycopg_binary-3.2.9-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7fc2915949e5c1ea27a851f7a472a7da7d0a40d679f0a31e42f1022f3c562e87", size = 4437423, upload-time = "2025-05-13T16:08:37.444Z" },
61 { url = "https://files.pythonhosted.org/packages/1a/db/cef77d08e59910d483df4ee6da8af51c03bb597f500f1fe818f0f3b925d3/psycopg_binary-3.2.9-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a1fa38a4687b14f517f049477178093c39c2a10fdcced21116f47c017516498f", size = 3758667, upload-time = "2025-05-13T16:08:40.116Z" },
62 { url = "https://files.pythonhosted.org/packages/95/3e/252fcbffb47189aa84d723b54682e1bb6d05c8875fa50ce1ada914ae6e28/psycopg_binary-3.2.9-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:5be8292d07a3ab828dc95b5ee6b69ca0a5b2e579a577b39671f4f5b47116dfd2", size = 3320576, upload-time = "2025-05-13T16:08:43.243Z" },
63 { url = "https://files.pythonhosted.org/packages/1c/cd/9b5583936515d085a1bec32b45289ceb53b80d9ce1cea0fef4c782dc41a7/psycopg_binary-3.2.9-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:778588ca9897b6c6bab39b0d3034efff4c5438f5e3bd52fda3914175498202f9", size = 3411439, upload-time = "2025-05-13T16:08:47.321Z" },
64 { url = "https://files.pythonhosted.org/packages/45/6b/6f1164ea1634c87956cdb6db759e0b8c5827f989ee3cdff0f5c70e8331f2/psycopg_binary-3.2.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f0d5b3af045a187aedbd7ed5fc513bd933a97aaff78e61c3745b330792c4345b", size = 3477477, upload-time = "2025-05-13T16:08:51.166Z" },
65 { url = "https://files.pythonhosted.org/packages/7b/1d/bf54cfec79377929da600c16114f0da77a5f1670f45e0c3af9fcd36879bc/psycopg_binary-3.2.9-cp313-cp313-win_amd64.whl", hash = "sha256:2290bc146a1b6a9730350f695e8b670e1d1feb8446597bed0bbe7c3c30e0abcb", size = 2928009, upload-time = "2025-05-13T16:08:53.67Z" },
66]
67
68[[package]]
69name = "psycopg-pool"
70version = "3.2.6"
71source = { registry = "https://pypi.org/simple" }
72dependencies = [
73 { name = "typing-extensions" },
74]
75sdist = { url = "https://files.pythonhosted.org/packages/cf/13/1e7850bb2c69a63267c3dbf37387d3f71a00fd0e2fa55c5db14d64ba1af4/psycopg_pool-3.2.6.tar.gz", hash = "sha256:0f92a7817719517212fbfe2fd58b8c35c1850cdd2a80d36b581ba2085d9148e5", size = 29770, upload-time = "2025-02-26T12:03:47.129Z" }
76wheels = [
77 { url = "https://files.pythonhosted.org/packages/47/fd/4feb52a55c1a4bd748f2acaed1903ab54a723c47f6d0242780f4d97104d4/psycopg_pool-3.2.6-py3-none-any.whl", hash = "sha256:5887318a9f6af906d041a0b1dc1c60f8f0dda8340c2572b74e10907b51ed5da7", size = 38252, upload-time = "2025-02-26T12:03:45.073Z" },
78]
79
80[[package]]
81name = "sdnotify"
82version = "0.3.2"
83source = { registry = "https://pypi.org/simple" }
84sdist = { url = "https://files.pythonhosted.org/packages/ce/d8/9fdc36b2a912bf78106de4b3f0de3891ff8f369e7a6f80be842b8b0b6bd5/sdnotify-0.3.2.tar.gz", hash = "sha256:73977fc746b36cc41184dd43c3fe81323e7b8b06c2bb0826c4f59a20c56bb9f1", size = 2459, upload-time = "2017-08-02T20:03:44.395Z" }
85
86[[package]]
87name = "systemd-python"
88version = "235"
89source = { registry = "https://pypi.org/simple" }
90sdist = { url = "https://files.pythonhosted.org/packages/10/9e/ab4458e00367223bda2dd7ccf0849a72235ee3e29b36dce732685d9b7ad9/systemd-python-235.tar.gz", hash = "sha256:4e57f39797fd5d9e2d22b8806a252d7c0106c936039d1e71c8c6b8008e695c0a", size = 61677, upload-time = "2023-02-11T13:42:16.588Z" }
91
92[[package]]
93name = "systemd-socketserver"
94version = "1.0"
95source = { registry = "https://pypi.org/simple" }
96dependencies = [
97 { name = "systemd-python" },
98]
99wheels = [
100 { url = "https://files.pythonhosted.org/packages/d8/4f/b28b7f08880120a26669b080ca74487c8c67e8b54dcb0467a8f0c9f38ed6/systemd_socketserver-1.0-py3-none-any.whl", hash = "sha256:987a8bfbf28d959e7c2966c742ad7bad482f05e121077defcf95bb38267db9a8", size = 3248, upload-time = "2020-04-26T05:26:40.661Z" },
101]
102
103[[package]]
104name = "typing-extensions"
105version = "4.13.2"
106source = { registry = "https://pypi.org/simple" }
107sdist = { url = "https://files.pythonhosted.org/packages/f6/37/23083fcd6e35492953e8d2aaaa68b860eb422b34627b13f2ce3eb6106061/typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef", size = 106967, upload-time = "2025-04-10T14:19:05.416Z" }
108wheels = [
109 { url = "https://files.pythonhosted.org/packages/8b/54/b1ae86c0973cc6f0210b53d508ca3641fb6d0c56823f288d108bc7ab3cc8/typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c", size = 45806, upload-time = "2025-04-10T14:19:03.967Z" },
110]
111
112[[package]]
113name = "tzdata"
114version = "2025.2"
115source = { registry = "https://pypi.org/simple" }
116sdist = { url = "https://files.pythonhosted.org/packages/95/32/1a225d6164441be760d75c2c42e2780dc0873fe382da3e98a2e1e48361e5/tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9", size = 196380, upload-time = "2025-03-23T13:54:43.652Z" }
117wheels = [
118 { url = "https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", size = 347839, upload-time = "2025-03-23T13:54:41.845Z" },
119]
diff --git a/hosts/surtr/hledger.nix b/hosts/surtr/hledger.nix
new file mode 100644
index 00000000..e44933c3
--- /dev/null
+++ b/hosts/surtr/hledger.nix
@@ -0,0 +1,66 @@
1{ config, ... }:
2
3{
4 config = {
5 security.acme.rfc2136Domains = {
6 "hledger.yggdrasil.li" = {
7 restartUnits = ["nginx.service"];
8 };
9 };
10
11 services.nginx = {
12 upstreams."hledger" = {
13 servers = {
14 "[2a03:4000:52:ada:4:1::]:5000" = {};
15 };
16 extraConfig = ''
17 keepalive 8;
18 '';
19 };
20 virtualHosts = {
21 "hledger.yggdrasil.li" = {
22 kTLS = true;
23 http3 = true;
24 forceSSL = true;
25 sslCertificate = "/run/credentials/nginx.service/hledger.yggdrasil.li.pem";
26 sslCertificateKey = "/run/credentials/nginx.service/hledger.yggdrasil.li.key.pem";
27 sslTrustedCertificate = "/run/credentials/nginx.service/hledger.yggdrasil.li.chain.pem";
28 extraConfig = ''
29 charset utf-8;
30 '';
31
32 locations = {
33 "/".extraConfig = ''
34 proxy_pass http://hledger;
35
36 proxy_http_version 1.1;
37 proxy_set_header Upgrade $http_upgrade;
38 proxy_set_header Connection "upgrade";
39
40 proxy_redirect off;
41 proxy_set_header Host $host;
42 proxy_set_header X-Real-IP $remote_addr;
43 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
44 proxy_set_header X-Forwarded-Host $server_name;
45 proxy_set_header X-Forwarded-Proto $scheme;
46
47 client_max_body_size 0;
48 proxy_request_buffering off;
49 proxy_buffering off;
50 '';
51 };
52 };
53 };
54 };
55
56 systemd.services.nginx = {
57 serviceConfig = {
58 LoadCredential = [
59 "hledger.yggdrasil.li.key.pem:${config.security.acme.certs."hledger.yggdrasil.li".directory}/key.pem"
60 "hledger.yggdrasil.li.pem:${config.security.acme.certs."hledger.yggdrasil.li".directory}/fullchain.pem"
61 "hledger.yggdrasil.li.chain.pem:${config.security.acme.certs."hledger.yggdrasil.li".directory}/chain.pem"
62 ];
63 };
64 };
65 };
66}
diff --git a/hosts/surtr/kimai.nix b/hosts/surtr/kimai.nix
new file mode 100644
index 00000000..454b3d80
--- /dev/null
+++ b/hosts/surtr/kimai.nix
@@ -0,0 +1,68 @@
1{ config, ... }:
2
3{
4 config = {
5 security.acme.rfc2136Domains = {
6 "kimai.yggdrasil.li" = {
7 restartUnits = ["nginx.service"];
8 };
9 };
10
11 services.nginx = {
12 upstreams."kimai" = {
13 servers = {
14 "[2a03:4000:52:ada:6::2]:80" = {};
15 };
16 extraConfig = ''
17 keepalive 8;
18 '';
19 };
20 virtualHosts = {
21 "kimai.yggdrasil.li" = {
22 kTLS = true;
23 http3 = true;
24 forceSSL = true;
25 sslCertificate = "/run/credentials/nginx.service/kimai.yggdrasil.li.pem";
26 sslCertificateKey = "/run/credentials/nginx.service/kimai.yggdrasil.li.key.pem";
27 sslTrustedCertificate = "/run/credentials/nginx.service/kimai.yggdrasil.li.chain.pem";
28 extraConfig = ''
29 charset utf-8;
30 '';
31
32 locations = {
33 "/".extraConfig = ''
34 proxy_pass http://kimai;
35
36 proxy_http_version 1.1;
37 proxy_set_header Upgrade $http_upgrade;
38 proxy_set_header Connection "upgrade";
39
40 proxy_redirect off;
41 proxy_set_header Host $host;
42 proxy_set_header X-Real-IP $remote_addr;
43 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
44 proxy_set_header X-Forwarded-Host $server_name;
45 proxy_set_header X-Forwarded-Proto $scheme;
46
47 client_max_body_size 0;
48 proxy_request_buffering off;
49 proxy_buffering off;
50
51 proxy_read_timeout 300;
52 '';
53 };
54 };
55 };
56 };
57
58 systemd.services.nginx = {
59 serviceConfig = {
60 LoadCredential = [
61 "kimai.yggdrasil.li.key.pem:${config.security.acme.certs."kimai.yggdrasil.li".directory}/key.pem"
62 "kimai.yggdrasil.li.pem:${config.security.acme.certs."kimai.yggdrasil.li".directory}/fullchain.pem"
63 "kimai.yggdrasil.li.chain.pem:${config.security.acme.certs."kimai.yggdrasil.li".directory}/chain.pem"
64 ];
65 };
66 };
67 };
68}
diff --git a/hosts/surtr/paperless.nix b/hosts/surtr/paperless.nix
new file mode 100644
index 00000000..7bc4397c
--- /dev/null
+++ b/hosts/surtr/paperless.nix
@@ -0,0 +1,66 @@
1{ config, ... }:
2
3{
4 config = {
5 security.acme.rfc2136Domains = {
6 "paperless.yggdrasil.li" = {
7 restartUnits = ["nginx.service"];
8 };
9 };
10
11 services.nginx = {
12 upstreams."paperless" = {
13 servers = {
14 "[2a03:4000:52:ada:4:1::]:28981" = {};
15 };
16 extraConfig = ''
17 keepalive 8;
18 '';
19 };
20 virtualHosts = {
21 "paperless.yggdrasil.li" = {
22 kTLS = true;
23 http3 = true;
24 forceSSL = true;
25 sslCertificate = "/run/credentials/nginx.service/paperless.yggdrasil.li.pem";
26 sslCertificateKey = "/run/credentials/nginx.service/paperless.yggdrasil.li.key.pem";
27 sslTrustedCertificate = "/run/credentials/nginx.service/paperless.yggdrasil.li.chain.pem";
28 extraConfig = ''
29 charset utf-8;
30 '';
31
32 locations = {
33 "/".extraConfig = ''
34 proxy_pass http://paperless;
35
36 proxy_http_version 1.1;
37 proxy_set_header Upgrade $http_upgrade;
38 proxy_set_header Connection "upgrade";
39
40 proxy_redirect off;
41 proxy_set_header Host $host;
42 proxy_set_header X-Real-IP $remote_addr;
43 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
44 proxy_set_header X-Forwarded-Host $server_name;
45 proxy_set_header X-Forwarded-Proto $scheme;
46
47 client_max_body_size 0;
48 proxy_request_buffering off;
49 proxy_buffering off;
50 '';
51 };
52 };
53 };
54 };
55
56 systemd.services.nginx = {
57 serviceConfig = {
58 LoadCredential = [
59 "paperless.yggdrasil.li.key.pem:${config.security.acme.certs."paperless.yggdrasil.li".directory}/key.pem"
60 "paperless.yggdrasil.li.pem:${config.security.acme.certs."paperless.yggdrasil.li".directory}/fullchain.pem"
61 "paperless.yggdrasil.li.chain.pem:${config.security.acme.certs."paperless.yggdrasil.li".directory}/chain.pem"
62 ];
63 };
64 };
65 };
66}
diff --git a/hosts/surtr/postgresql/default.nix b/hosts/surtr/postgresql/default.nix
index 583e4443..3786ea7c 100644
--- a/hosts/surtr/postgresql/default.nix
+++ b/hosts/surtr/postgresql/default.nix
@@ -89,6 +89,10 @@ in {
89 "d /var/spool/pgbackrest 0750 postgres postgres - -" 89 "d /var/spool/pgbackrest 0750 postgres postgres - -"
90 ]; 90 ];
91 91
92 systemd.services.postgresql.serviceConfig = {
93 ReadWritePaths = [ "/var/spool/pgbackrest" "/var/lib/pgbackrest/archive/surtr" ];
94 };
95
92 systemd.services.migrate-postgresql = { 96 systemd.services.migrate-postgresql = {
93 after = [ "postgresql.service" ]; 97 after = [ "postgresql.service" ];
94 bindsTo = [ "postgresql.service" ]; 98 bindsTo = [ "postgresql.service" ];
@@ -276,6 +280,64 @@ in {
276 CREATE VIEW imap_user ("user", "password", quota_rule) AS SELECT mailbox.mailbox AS "user", "password", quota_rule FROM mailbox_quota_rule INNER JOIN mailbox ON mailbox_quota_rule.mailbox = mailbox.mailbox; 280 CREATE VIEW imap_user ("user", "password", quota_rule) AS SELECT mailbox.mailbox AS "user", "password", quota_rule FROM mailbox_quota_rule INNER JOIN mailbox ON mailbox_quota_rule.mailbox = mailbox.mailbox;
277 281
278 COMMIT; 282 COMMIT;
283
284 BEGIN;
285 SELECT _v.register_patch('013-internal', ARRAY['000-base'], null);
286
287 ALTER TABLE mailbox_mapping ADD COLUMN internal bool NOT NULL DEFAULT false;
288 CREATE TABLE mailbox_mapping_access (
289 id uuid PRIMARY KEY NOT NULL DEFAULT gen_random_uuid(),
290 mailbox_mapping uuid REFERENCES mailbox_mapping(id),
291 mailbox uuid REFERENCES mailbox(id)
292 );
293 CREATE USER "postfix-internal-policy";
294 GRANT CONNECT ON DATABASE "email" TO "postfix-internal-policy";
295 ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO "postfix-internal-policy";
296 GRANT SELECT ON ALL TABLES IN SCHEMA public TO "postfix-internal-policy";
297
298 COMMIT;
299
300 BEGIN;
301 SELECT _v.register_patch('014-relay', ARRAY['000-base'], null);
302
303 CREATE TABLE relay_access (
304 id uuid PRIMARY KEY NOT NULL DEFAULT gen_random_uuid(),
305 mailbox uuid REFERENCES mailbox(id),
306 domain citext NOT NULL CONSTRAINT domain_non_empty CHECK (domain <> ''')
307 );
308
309 COMMIT;
310
311 BEGIN;
312 SELECT _v.register_patch('015-relay-unique', ARRAY['000-base', '014-relay'], null);
313
314 CREATE UNIQUE INDEX relay_unique ON relay_access (mailbox, domain);
315
316 COMMIT;
317
318 BEGIN;
319 SELECT _v.register_patch('015-sender_bcc', null, null);
320
321 CREATE TABLE sender_bcc_maps (
322 id uuid PRIMARY KEY NOT NULL DEFAULT gen_random_uuid(),
323 key text NOT NULL CONSTRAINT key_not_empty CHECK (key <> '''),
324 value text NOT NULL CONSTRAINT value_not_empty CHECK (value <> '''),
325 CONSTRAINT key_unique UNIQUE (key)
326 );
327
328 COMMIT;
329
330 BEGIN;
331 SELECT _v.register_patch('016-recipient_bcc', null, null);
332
333 CREATE TABLE recipient_bcc_maps (
334 id uuid PRIMARY KEY NOT NULL DEFAULT gen_random_uuid(),
335 key text NOT NULL CONSTRAINT key_not_empty CHECK (key <> '''),
336 value text NOT NULL CONSTRAINT value_not_empty CHECK (value <> '''),
337 CONSTRAINT recipient_bcc_maps_key_unique UNIQUE (key)
338 );
339
340 COMMIT;
279 ''} 341 ''}
280 342
281 psql etebase postgres -eXf ${pkgs.writeText "etebase.sql" '' 343 psql etebase postgres -eXf ${pkgs.writeText "etebase.sql" ''
diff --git a/hosts/surtr/tls/tsig_key.gup b/hosts/surtr/tls/tsig_key.gup
index 825479e5..46a3789e 100644
--- a/hosts/surtr/tls/tsig_key.gup
+++ b/hosts/surtr/tls/tsig_key.gup
@@ -3,4 +3,4 @@
3keyFile=../dns/keys/${2:t}_acme 3keyFile=../dns/keys/${2:t}_acme
4gup -u $keyFile 4gup -u $keyFile
5sops -d --input-type=binary --output-type=binary ${keyFile} | yq -r '.key[0].secret' > $1 5sops -d --input-type=binary --output-type=binary ${keyFile} | yq -r '.key[0].secret' > $1
6sops -p '7ED22F4AA7BB55728B643DC5471B7D88E4EF66F8,30D3453B8CD02FE2A3E7C78C0FB536FB87AE8F51' --input-type=binary -e -i $1 6sops --input-type=binary -e -i $1
diff --git a/hosts/surtr/tls/tsig_keys/audiobookshelf.yggdrasil.li b/hosts/surtr/tls/tsig_keys/audiobookshelf.yggdrasil.li
new file mode 100644
index 00000000..8dd610dd
--- /dev/null
+++ b/hosts/surtr/tls/tsig_keys/audiobookshelf.yggdrasil.li
@@ -0,0 +1,19 @@
1{
2 "data": "ENC[AES256_GCM,data:r9jhdTlbDnCMq1QLJutn76uz1Ml8MFs7fXYRSiVYh1gafcXXsUZBq5+qqoQI,iv:un/luttuKpCiMf53fa2SRY0ffttGiYwT8DuHCKEnnEI=,tag:SkNULZSulQmP99aB/Ec+Fw==,type:str]",
3 "sops": {
4 "age": [
5 {
6 "recipient": "age1rmmhetcmllq0ahl5qznlr0eya2zdxwl9h6y5wnl97d2wtyx5t99sm2u866",
7 "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBkZGJzaEsrSU4raHlTVDVB\nczRnWVlSTTRuNXU0T3F1RTkxKytXeVJRdGpFCk9WMzNBR1NaTTMzN3BGQ2JmTjVt\nRU4rSWxCYjJPYVRzLzR0OVRYQm45TUkKLS0tIDNyMnpPN2VKUFFadTkveXRYeWps\nYUNaTjRJLzdWUnREaUVIWkpFV0FTZ2MKJS0K49SdkLW4p67FlgboHy/OVvCiUA7g\nuv5b+yotkQmh5xJwr7CUvwRewqJh56mg1yhWmE8wzpgLZMIjRXcQCQ==\n-----END AGE ENCRYPTED FILE-----\n"
8 },
9 {
10 "recipient": "age19a7j77w267z04zls7m28a8hj4a0g5af6ltye2d5wypg33c3l89csd4r9zq",
11 "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBQQ1h3M1lXTXVNd0d6cmtU\nU2JtUzFFblJudmEycnJONkkwME9wWm5jWVFzCnRYVEFWaVNvSW9GZ05TRWF4L2ho\nanltVytEU3ZOdHk1VHY5aGJDUkdDdmcKLS0tIEtzOFVkbmpjbWN5d0c1VEpxc1Rr\nSzJwclYxeC9TVWNaK2gwUmJSY0x1ZVUKTNivp5iS+1tzVMjMn17/ncvHcELhjQ/B\n0OVz4VpKM2wv6CjEcIMxmchqT8p8GFYVRrKUdqO2GEKOoe8ANtidWA==\n-----END AGE ENCRYPTED FILE-----\n"
12 }
13 ],
14 "lastmodified": "2025-05-09T17:07:16Z",
15 "mac": "ENC[AES256_GCM,data:SwS+8UQnPgHORobKLu+u2pNaMdKIvR+etUed8btbbne/IX/Wpxt0qyPYXNNGGRkN3KAxTHWjRRdrKU1bkuTU3ER1c94T935ExDESKJLVjzaEF5VSWCqLyUNCMsY2ANw84UES2swK4YI4zF1CP7rD8tKFFld78IWZoeQ7XNGDMRA=,iv:neLvamISgQ5+aqW1iRj9xJoXq1weNNyy7KCFG2+WRQE=,tag:66SDO61WnKU6DVElo9CImg==,type:str]",
16 "unencrypted_suffix": "_unencrypted",
17 "version": "3.10.2"
18 }
19}
diff --git a/hosts/surtr/tls/tsig_keys/hledger.yggdrasil.li b/hosts/surtr/tls/tsig_keys/hledger.yggdrasil.li
new file mode 100644
index 00000000..ab6cdd68
--- /dev/null
+++ b/hosts/surtr/tls/tsig_keys/hledger.yggdrasil.li
@@ -0,0 +1,24 @@
1{
2 "data": "ENC[AES256_GCM,data:Yd70QIj9DE6a5IN+Mf2M5p95vkRMHRg9BXaM686W7BRtthOw9m54/5FK6JWr,iv:cIOIKinkqFFPgTZdewWVY0h6kM5hGfVzuA4iYNhwK5c=,tag:Ds/oI+TOERbIdcGbI4WoEg==,type:str]",
3 "sops": {
4 "kms": null,
5 "gcp_kms": null,
6 "azure_kv": null,
7 "hc_vault": null,
8 "age": [
9 {
10 "recipient": "age1rmmhetcmllq0ahl5qznlr0eya2zdxwl9h6y5wnl97d2wtyx5t99sm2u866",
11 "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA1NnJ4SmsvSlh3ZlliSm9C\nNG45clh3NEZWZ05jaHhVK0xqTVRFL2wxN1ZrCk5hL2p2ZjhtcDBjTEZscXFTNkY5\nemVZSUUwV2V5cFBTdWo4RWxsM2xROVEKLS0tIDBFSFlkUVJ0ajJEUENlelVFKzVk\ndnJhMURMU2o3WVBKZGNVRXBiNytqUFEKHivcSTYy5D770C0h7RsmLBmkIG9+MDoV\ngJHvfkGzXPKwmDTMKdHbIk+ctI+u0/1jMn/K2Q9OFnOIYxP3gHiFag==\n-----END AGE ENCRYPTED FILE-----\n"
12 },
13 {
14 "recipient": "age19a7j77w267z04zls7m28a8hj4a0g5af6ltye2d5wypg33c3l89csd4r9zq",
15 "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSArYUNCQ1U2MXpQdmM4SHR0\nTnJWZlFGTUQ1NjVqZ3Z2MGt5NFpBVFp0b0YwCkdseitkbzI1RkZyL3V5Z1pNMG9R\nVnEzRUxrTDQrS3BiMXB1QUFPeUcxZUkKLS0tIGJFODkwNFY3c0tBLzFBNjFiYjJk\nZW82bTdia2F1NHpNSG1IYmhWb1ZCTHMK9ovFx3+x5PrV4y6+RH5XA5DK2wRPXlAt\ncxxpRZIlmnvhZXIeCYE9yhHFmz3uAn0Oib1RUDblca9FlnF9tyYD6g==\n-----END AGE ENCRYPTED FILE-----\n"
16 }
17 ],
18 "lastmodified": "2025-02-19T17:13:51Z",
19 "mac": "ENC[AES256_GCM,data:ReRuK9qdZV8AbMzA9Yur0AZW+1RF3aRnfBvsKJkQtXsFdkmJQ4QkRGtL27RmjFdvQ3kXBIyhib7hYA60AJ0amduYrSScY0dtz8AurjyE4f2BGQ9/QeKRBfKXHxLvj4/xWNvS4+PVdGKkKbqIs8isz9n77WQQ3lTHop2K/TjaTuQ=,iv:gUhDK9oeUHdpQ2Fp8mFDIgPFo2JjHE0jjooL7FmvmrE=,tag:2lkwSl3j3oqamdLbM9wbow==,type:str]",
20 "pgp": null,
21 "unencrypted_suffix": "_unencrypted",
22 "version": "3.9.4"
23 }
24} \ No newline at end of file
diff --git a/hosts/surtr/tls/tsig_keys/kimai.yggdrasil.li b/hosts/surtr/tls/tsig_keys/kimai.yggdrasil.li
new file mode 100644
index 00000000..b9199975
--- /dev/null
+++ b/hosts/surtr/tls/tsig_keys/kimai.yggdrasil.li
@@ -0,0 +1,19 @@
1{
2 "data": "ENC[AES256_GCM,data:ATcU3Ix7o5d/49rD5H8je1ozTjoghrloMh5DIZ5WE3oYauUAknpGfr9xq92V,iv:vy9YK5Ot7CCjMtgAGVeAUQuaSw4F5kmmZ0GJYV9kCdQ=,tag:F/MXTUM2AI1fGXa9Ewn8yQ==,type:str]",
3 "sops": {
4 "age": [
5 {
6 "recipient": "age1rmmhetcmllq0ahl5qznlr0eya2zdxwl9h6y5wnl97d2wtyx5t99sm2u866",
7 "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBDMEF0cUdydERYVzJCa3pW\nTlo0NUFON0d5RGJFVnVTNVg3cjNEUERQMEdFClEvQW5odlNEd2F1VTFmMWQrL2RB\ncllFZVpIVVJrNTJsSGF4UEdZMnVmQzAKLS0tIFUrQkkzRVZiOFNiTnFCT1pEYVRM\nQm8wV1JkQ3RrR1dkL0FsNkhsY2kxa1kKGnAo/6oibgXexUU31THdLu6X+pRtrkjD\nZnXGPZ2xaESDVUVEYQPVpNrjt9brZGJBI1BasrkEwHAXMbJC236yYQ==\n-----END AGE ENCRYPTED FILE-----\n"
8 },
9 {
10 "recipient": "age19a7j77w267z04zls7m28a8hj4a0g5af6ltye2d5wypg33c3l89csd4r9zq",
11 "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB3MGs1Z2ZqK2pqWHdVYTJH\naTlncHdPa3Zld0JhQW5Ccmc1SStWSnlDR0JrCmpML2d4TGdldUdoZCtaWVpPZVl0\nVm4waWVBS1orRS90ZS96N0Y2M29LY0UKLS0tIEI1Z2VVbVVxRUpOZEN4NnBRRklC\nQXloelZCb04xbmduTlVuL005TlRGMHMKfLB6zA3sj3HgDBC7VGfGVB6I1zJpt0PV\nkCV2yADgvAA2pT9HPg9IWAEpTPysOBiuE2jPNtFvylZYwTDHoumFnQ==\n-----END AGE ENCRYPTED FILE-----\n"
12 }
13 ],
14 "lastmodified": "2025-05-24T09:42:23Z",
15 "mac": "ENC[AES256_GCM,data:0pk1LpWPmX9td/TwJFxwWp5pTDyW78UtHXMDah+V9Tmgi8hH7ONdysgjwpDwS/c4zGnMA3qtobEL286U3//CTXt2qVsiUGLsnngzs2E6yBg8oGMYlGrch4M355Fl5ZxYsc8QLA6qWcuZ4H3QW8PnoqdJixcHoYLoxG01dzh4Bc0=,iv:zchk4enI1D80BkJLji5RLm7OTk3GeF8nYHuwqBxCXIM=,tag:bgkknPMqkSidi6bDFfv6UQ==,type:str]",
16 "unencrypted_suffix": "_unencrypted",
17 "version": "3.10.2"
18 }
19}
diff --git a/hosts/surtr/tls/tsig_keys/paperless.yggdrasil.li b/hosts/surtr/tls/tsig_keys/paperless.yggdrasil.li
new file mode 100644
index 00000000..b1029931
--- /dev/null
+++ b/hosts/surtr/tls/tsig_keys/paperless.yggdrasil.li
@@ -0,0 +1,24 @@
1{
2 "data": "ENC[AES256_GCM,data:D9l0pklD2KDZ4/TXHtXg00MmCnjCVVBG0AK9j5OxxBCyYseCTckp2P/iPOng,iv:DjvuKWPr/jldfk0eZ5+jWHN0RurdruR4Md7AMAPzRQg=,tag:h0c4m3hpATzzb6a7DVmi9w==,type:str]",
3 "sops": {
4 "kms": null,
5 "gcp_kms": null,
6 "azure_kv": null,
7 "hc_vault": null,
8 "age": [
9 {
10 "recipient": "age1rmmhetcmllq0ahl5qznlr0eya2zdxwl9h6y5wnl97d2wtyx5t99sm2u866",
11 "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA3S05aS0ZEcVU2T3BIOG13\nSDJLTUp4OG9ZYVluK2gxbUJDSFRaQ0xnS0YwClFkKzByanJGOWFwbTlISndyU0Rx\nN1FJb3FVaUZOVDBsWEdHTVNGaGNtMVkKLS0tIDI1RmxaMlhVd1FPL1dNdlRGK0Nq\nMFpJZTNnWncrbkV5YS82ZnhGRld0UG8KIuf7bC7GVxaGeR7gwC7kGu/wtBppjq4H\nyDT05CYJf9/EE3K5aJpIOlxyqowRs2SINvIVkyd5ggYkkxCctmGXjQ==\n-----END AGE ENCRYPTED FILE-----\n"
12 },
13 {
14 "recipient": "age19a7j77w267z04zls7m28a8hj4a0g5af6ltye2d5wypg33c3l89csd4r9zq",
15 "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBUV3ZqTVJkNWdHeTlKK3Vl\nVTdrdzE2Z3YzVmZxelNFMDNMTUJneHNtNzFRCmQxTU84ekV4bFVLajU0ajB6ZzZK\neEpuQTcyS1o4MW9xTU5nMXVUR0gxTmcKLS0tIGVzZC8xYTc1VkU2RzA2NFQ1K2xz\nLzhPVjBUcytWNGRsdnFob3A4aWljelkKFMlmigcEVzelcEiv6WGya1dsIOJYr7YT\naBHgMttV7zzYHLqvIVJSCz+uw2FqDyqN46twmzFC0HSHeiKbvRrHVw==\n-----END AGE ENCRYPTED FILE-----\n"
16 }
17 ],
18 "lastmodified": "2025-02-13T19:23:42Z",
19 "mac": "ENC[AES256_GCM,data:0Fcgq0pOZtBBSiK8pUr/jadXMdtbZYFhUbSe+7DQpB8Fo2r8cEoT+Cpcy7tu+l9eXUiDk/tXTBJyMXaW4XWwS/Fe6Zcb95UYaYR1Y6OM9JVPYmwd6QSeC13MwzhYaCDlBiWWq69Zn8grEg7npWo/LS9LK7IEbN7EI8o7QYDI6cw=,iv:C+8ZmVTNWySQ+/6j+YirSwZzoMqXRlgstk47Efxmqps=,tag:Be/6Ve6M/Dcm/6QrbF+JTw==,type:str]",
20 "pgp": null,
21 "unencrypted_suffix": "_unencrypted",
22 "version": "3.9.4"
23 }
24} \ No newline at end of file
diff --git a/hosts/surtr/vpn/default.nix b/hosts/surtr/vpn/default.nix
index 1bdcf74e..92223144 100644
--- a/hosts/surtr/vpn/default.nix
+++ b/hosts/surtr/vpn/default.nix
@@ -1,4 +1,4 @@
1{ pkgs, config, lib, ... }: 1{ flake, pkgs, config, lib, ... }:
2 2
3with lib; 3with lib;
4 4
@@ -22,7 +22,11 @@ in {
22 "--load-credential=surtr.priv:/run/credentials/container@vpn.service/surtr.priv" 22 "--load-credential=surtr.priv:/run/credentials/container@vpn.service/surtr.priv"
23 "--network-ipvlan=ens3:upstream" 23 "--network-ipvlan=ens3:upstream"
24 ]; 24 ];
25 config = { 25 config = let hostConfig = config; in { config, pkgs, ... }: {
26 system.stateVersion = lib.mkIf hostConfig.containers."vpn".ephemeral config.system.nixos.release;
27 system.configurationRevision = mkIf (flake ? rev) flake.rev;
28 nixpkgs.pkgs = hostConfig.nixpkgs.pkgs;
29
26 boot.kernel.sysctl = { 30 boot.kernel.sysctl = {
27 "net.core.rmem_max" = 4194304; 31 "net.core.rmem_max" = 4194304;
28 "net.core.wmem_max" = 4194304; 32 "net.core.wmem_max" = 4194304;
diff --git a/hosts/surtr/vpn/geri.pub b/hosts/surtr/vpn/geri.pub
index ed5de2b2..2cd9b24e 100644
--- a/hosts/surtr/vpn/geri.pub
+++ b/hosts/surtr/vpn/geri.pub
@@ -1 +1 @@
sYuQSNZHzfegv8HRz71jnZm2nFLGeRnaGwVonhKUj2k= hhER05bvstOTGfiAG3IJsFkBNWCUZHokBXwaiC5d534=
diff --git a/hosts/surtr/zfs.nix b/hosts/surtr/zfs.nix
index 17c5cd32..3795956d 100644
--- a/hosts/surtr/zfs.nix
+++ b/hosts/surtr/zfs.nix
@@ -49,7 +49,7 @@
49 49
50 boot.postBootCommands = '' 50 boot.postBootCommands = ''
51 echo "=== STARTING ZPOOL IMPORT ===" 51 echo "=== STARTING ZPOOL IMPORT ==="
52 ${pkgs.zfs}/bin/zpool import -a -N -d /dev 52 ${pkgs.zfs}/bin/zpool import -a -f -N -d /dev
53 ${pkgs.zfs}/bin/zpool status 53 ${pkgs.zfs}/bin/zpool status
54 ${pkgs.zfs}/bin/zfs mount -a 54 ${pkgs.zfs}/bin/zfs mount -a
55 echo "=== ZPOOL IMPORT COMPLETE ===" 55 echo "=== ZPOOL IMPORT COMPLETE ==="
diff --git a/hosts/vidhar/audiobookshelf/abs-podcast-autoplaylist-gkleen.toml b/hosts/vidhar/audiobookshelf/abs-podcast-autoplaylist-gkleen.toml
new file mode 100644
index 00000000..a5319e38
--- /dev/null
+++ b/hosts/vidhar/audiobookshelf/abs-podcast-autoplaylist-gkleen.toml
@@ -0,0 +1,19 @@
1{
2 "data": "ENC[AES256_GCM,data:60OmHwuLC7RJVNNn8lsCFjIFrtDlmmT3yAm3DYn/K2b8OJB/lzKBhMUCyPpoI2lfMm6y47/DMwXI3ExH3QwfgGRf4i/Tcv7p6FCkjFgDc0RhAM7cXNSnh1gKTff8QYtPoNIzmycFCThNr7iZsPsf2/1npVaVHTnt9nTc+cmDLc+lELlvjSE00JOXch/if7KPwFww9K83XlrFmoRvwybfXR0unJqxK2XLvj+dQuKD4Bhyb88iSgu4dX1yw2uBSZBD16S4Io0DaZ+as5Yw4Kon7WMj3Jd5kz8ZxK+0NCy1CVJHOfJIwgYl0SVPp4DpbAPtJO4R/ciXyDQ/XGpoLtHjxnKXaJlJoSiA7FhuSEk+jB/peLHrYV1obdIRE5Dstly01S5cydKlfQ+A0TSjxFSWBYMEiD89sD09Br3iSJX5FejOoS8d2IQJ5faVzgQl4T5aBKsxCNNwmYrEe8m9HN7o2eer8nTKMln5IxZi3ZWhnjgJfrJ4QTXFndxCb78jo8HroN3+7VhoM136UZkqH1OMrIgAH/XSlW08G8m9MRamKsAWklq9aVflcEsPWTHmYW7rjAapQYf+jyK6BbfHcYmyKM82TFZ5iNB60Pth6EJgb2V8PZiChGvDzQvFYYOO3p9a/J8bVqsnPZBXXYcIBt42ZuRPvyyUTfM+75V1eYE9ZGFML+QlofwNCAg+/Rnl+RRy4z+8xQxd8Dn06geDpHsr4yND72FRUTKLbjxF5xfbzBRcZEXjGkyFdEAF7rB78I8xIqii+n6Yt8uEURmd4geI9KWXRQnwofTz9pklaAnRbER8zy/BJIiIYy8zecUHJn9v/DPnsnksfL6RRmG4tHaRBDbpAag0kVkCrpO/flK6dZOl/wvoVVVqT2O69a9/RpHLSV2f//ZS6L9s6vaYe4pXL0M6QymgA22sNHaws6XggJlTxVOFGRejMGYrKqVWtC+2UNbnel+/J0N1qj4luWfQaf9+1j+fq7vyLSzXYFCiyOLAznpqOhzKu6VWy2IbR0UnCoL5ZbhIba9e2MXM7Czy9Yee4xc=,iv:M0GbtFFl1XUeq+y9H+MiD+9z/ASB9hsd06KhpPzSwEo=,tag:vTLIIf+CeZN6DU25CSP8tw==,type:str]",
3 "sops": {
4 "age": [
5 {
6 "recipient": "age1rmmhetcmllq0ahl5qznlr0eya2zdxwl9h6y5wnl97d2wtyx5t99sm2u866",
7 "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA0aE1XNUNCM1Q5V0d3R2JG\nbjJZTmdvQ21JbmtyR0ZmODFMdVBGejRoam1vCjMzMGdTb3BReDVCa2JJU0JrSHFP\ndTdicU5TRjIrTWpteDMzeGtDT0xaelkKLS0tIFhaSlFrbzFDUjRZV0lGR0cydVdZ\nY2xma0VSVXlTM1JucFJUSys4dlRvdEUK9gQNQEdKDDf1ikWzd6uTlE50WsfO/EB0\nGH2Ono6oNWbKWTyl/wRO8NzXx0nudwqq66s0oBLIdTMQOpIBBNI0XQ==\n-----END AGE ENCRYPTED FILE-----\n"
8 },
9 {
10 "recipient": "age1qffdqvy9arld9zd5a5cylt0n98xhcns5shxhrhwjq5g4qa844ejselaa4l",
11 "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBiRWFqSHNlY1IvMkkwaEto\ncHZHa2p1Y25SakFkS2JYMlRFcFhnZGY1dVRFCkxSWmxvcHZMampQKzdKRHI0ZVMx\nUTFtR0pHbzFaQ0xQUFA2ZERDSWpwS0UKLS0tIFBaSGczY3VWdy9TKzRDZWZ2SElY\nbVQ4dDNhQllmVmViWGs5c3V4TmNscjQKeugevQJFAN/8JrzeAm4hm2JsQGb26BCb\n3dKYnN1kJU7oVHr1aVfXwMpELNYt9poX6WTY2h9lsdHuRlqoFXAA5Q==\n-----END AGE ENCRYPTED FILE-----\n"
12 }
13 ],
14 "lastmodified": "2025-05-10T10:25:15Z",
15 "mac": "ENC[AES256_GCM,data:dhj7e+vF3uiR6I22PR5tdNdM8EyrWmGGTIqjj8H7IdNIsZBHzjeHlBDFOwN7z/JMO0BVwIi4DmhApg2BSPGsQZGDQZ28UTCC8TDtd1zmfGtSP8R8AFHADYdLK/desMtHg6BZTnLv5tpba34WWdflMNOQpwgWPZsIk/DkLaoXdvk=,iv:qkoAZngTz2sfWdxDs+h8Mb2IrkF8gqnQoR5iRoeKjbY=,tag:zXrkBJmPM4ItJxMnX8IDxQ==,type:str]",
16 "unencrypted_suffix": "_unencrypted",
17 "version": "3.10.2"
18 }
19}
diff --git a/hosts/vidhar/audiobookshelf/default.nix b/hosts/vidhar/audiobookshelf/default.nix
new file mode 100644
index 00000000..136bcaff
--- /dev/null
+++ b/hosts/vidhar/audiobookshelf/default.nix
@@ -0,0 +1,21 @@
1{ config, pkgs, lib, ... }:
2
3{
4 config = {
5 services.audiobookshelf = {
6 enable = true;
7 host = "2a03:4000:52:ada:4:1::";
8 port = 28982;
9 };
10
11 users.groups.audiobookshelf.members = [ "gkleen" ];
12
13 services.abs-podcast-autoplaylist = {
14 gkleen = {};
15 };
16 sops.secrets.${config.services.abs-podcast-autoplaylist.gkleen.configSecret} = {
17 format = "binary";
18 sopsFile = ./abs-podcast-autoplaylist-gkleen.toml;
19 };
20 };
21}
diff --git a/hosts/vidhar/default.nix b/hosts/vidhar/default.nix
index b0797d8a..7da17e6f 100644
--- a/hosts/vidhar/default.nix
+++ b/hosts/vidhar/default.nix
@@ -4,7 +4,7 @@ with lib;
4 4
5{ 5{
6 imports = with flake.nixosModules.systemProfiles; [ 6 imports = with flake.nixosModules.systemProfiles; [
7 ./zfs.nix ./network ./samba.nix ./dns ./prometheus ./borg ./pgbackrest ./postgresql.nix ./immich.nix 7 ./zfs.nix ./network ./samba.nix ./dns ./prometheus ./borg ./pgbackrest ./postgresql.nix ./immich.nix ./paperless ./hledger ./audiobookshelf ./kimai
8 tmpfs-root zfs 8 tmpfs-root zfs
9 initrd-all-crypto-modules default-locale openssh rebuild-machines 9 initrd-all-crypto-modules default-locale openssh rebuild-machines
10 build-server 10 build-server
diff --git a/hosts/vidhar/hledger/default.nix b/hosts/vidhar/hledger/default.nix
new file mode 100644
index 00000000..ae080f66
--- /dev/null
+++ b/hosts/vidhar/hledger/default.nix
@@ -0,0 +1,83 @@
1{ config, lib, pkgs, ... }:
2{
3 config = {
4 services.hledger-web = {
5 enable = true;
6 allow = "view";
7 stateDir = "/var/lib/hledger";
8 journalFiles = lib.mkForce ["web.journal"];
9 baseUrl = "https://hledger.yggdrasil.li";
10 extraOptions = [
11 "--socket=/run/hledger-web/http.sock"
12 ];
13 };
14 users = {
15 users.hledger.uid = 982;
16 groups.hledger.gid = 979;
17 };
18 systemd.services.hledger-web = {
19 serviceConfig = {
20 UMask = "0002";
21 ReadOnlyPaths = [ config.services.hledger-web.stateDir ];
22 RuntimeDirectory = [ "hledger-web" ];
23 PrivateDevices = true;
24 StateDirectory = "hledger";
25 CapabilityBoundingSet = "";
26 AmbientCapabilities = "";
27 ProtectSystem = "strict";
28 ProtectKernelTunables = true;
29 ProtectKernelModules = true;
30 ProtectControlGroups = true;
31 ProtectClock = true;
32 ProtectHostname = true;
33 ProtectHome = "tmpfs";
34 ProtectKernelLogs = true;
35 ProtectProc = "invisible";
36 ProcSubset = "pid";
37 PrivateNetwork = false;
38 RestrictAddressFamilies = "AF_INET AF_INET6 AF_UNIX";
39 SystemCallArchitectures = "native";
40 SystemCallFilter = [
41 "@system-service @resources"
42 "~@obsolete @privileged"
43 ];
44 RestrictSUIDSGID = true;
45 RemoveIPC = true;
46 NoNewPrivileges = true;
47 RestrictRealtime = true;
48 RestrictNamespaces = true;
49 LockPersonality = true;
50 PrivateUsers = true;
51 TemporaryFileSystem = [ "/var/lib/hledger/.cache:mode=0750,uid=${toString (config.users.users.hledger.uid)},gid=${toString (config.users.groups.hledger.gid)}" ];
52 };
53 };
54 services.nginx = {
55 upstreams.hledger = {
56 servers = { "unix:/run/hledger-web/http.sock" = {}; };
57 };
58 virtualHosts."hledger.yggdrasil.li" = {
59 listen = [
60 { addr = "[2a03:4000:52:ada:4:1::]"; port = 5000; }
61 ];
62 extraConfig = ''
63 set_real_ip_from 2a03:4000:52:ada:4::;
64 auth_basic "hledger";
65 auth_basic_user_file "/run/credentials/nginx.service/hledger_users";
66 '';
67 locations."/" = {
68 proxyPass = "http://hledger/";
69 proxyWebsockets = true;
70 };
71 };
72 };
73 systemd.services.nginx.serviceConfig = {
74 SupplementaryGroups = [ "hledger" ];
75 LoadCredential = [ "hledger_users:${config.sops.secrets."hledger_users".path}" ];
76 };
77 sops.secrets."hledger_users" = {
78 format = "binary";
79 sopsFile = ./htpasswd;
80 reloadUnits = [ "nginx.service" ];
81 };
82 };
83}
diff --git a/hosts/vidhar/hledger/htpasswd b/hosts/vidhar/hledger/htpasswd
new file mode 100644
index 00000000..016cb525
--- /dev/null
+++ b/hosts/vidhar/hledger/htpasswd
@@ -0,0 +1,24 @@
1{
2 "data": "ENC[AES256_GCM,data:9MNDIAc7ePYk3xQDorX2pU8ybJkJb33RKiJxc2DYauXFNQYxtGwCYhZwod7p7fPh3KqZxBNMRoZXr+/RnV+trsqjAcOOjnXTWLbX6nubq/xm+q0BxEjOPn7FvJF9XOblBeupldo+byGh2CMH9qQv5Fov,iv:3Tym+Mfr48OJet3qDFZPg0XjYr4sNQdNdiu0vUxmzbY=,tag:E0sxRY/jeMVlqH6uAYvD/Q==,type:str]",
3 "sops": {
4 "kms": null,
5 "gcp_kms": null,
6 "azure_kv": null,
7 "hc_vault": null,
8 "age": [
9 {
10 "recipient": "age1rmmhetcmllq0ahl5qznlr0eya2zdxwl9h6y5wnl97d2wtyx5t99sm2u866",
11 "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA3eFBsOEM2ZUNVT2V3LytC\nTUJvUDdKc0VzMyt2cDFKYU03djBjZVFpeVY4CjByMXhPVXRJVjhKQWZvQ2xuOTE3\ncXdJV1lZaHR3cVl0Z0hQaG00M2dGbjQKLS0tIEIzenVxb3cwM3pXTUl1YUZlSlk2\nbDc3VmE5NkEyZ2tRd01OUGZibmhtUlEKxdesIdvzm8s0SmXU5R+tSbmS5Dj24jrb\nEiMERYy1g8GyHR3d2/mU5iOIdsBegSZReUVzomaMT9L7/TmubgOP3g==\n-----END AGE ENCRYPTED FILE-----\n"
12 },
13 {
14 "recipient": "age1qffdqvy9arld9zd5a5cylt0n98xhcns5shxhrhwjq5g4qa844ejselaa4l",
15 "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBPa2RDZzR6cEFYTFA1QkND\nbndVeHVrMVJ0MWZvRmw5VXRhOHlRYllIRWxRCjU4dks4R25LS1RZMHFnbmpQRVZz\nNXhubkJvZFc2amRwMDVtQlE0NnBKNzQKLS0tIHRyeDUxTEFPMEMzWUVkZURzODdm\nSHdqbUpvNmFTS1QveFRpRHdnWHpHb28KnvdUkMkKGiBVHQD7Yv7n6WZjihCGJAR2\nMKl2WAn4g4jzgcXPwwIAIjUrMGSIdGpwCTUDcDnlKWAbRYO2B6P17A==\n-----END AGE ENCRYPTED FILE-----\n"
16 }
17 ],
18 "lastmodified": "2025-02-19T17:11:17Z",
19 "mac": "ENC[AES256_GCM,data:yBIEqHhr4igoMlRcgg2SigKfejqeuNmuleYolsLJo+QOaW4BHITJTvLxRV1JHPpcMVQkF//zx4ZfUUrb8tTN0znGu3Jnpd0JVagbfCVyEuT6d1SB/GzyUVvoQ2GlcA9us+5gjI4oEJTQCfVqnLDBWsw+jXdr3nEIWo6Mvbqo3lI=,iv:I6Swk4wyd+96+tJKRY/FHlS7ZShMDROcbl+l+ZLRxhM=,tag:P1uQvB4NLdkPEKRMI6lLxw==,type:str]",
20 "pgp": null,
21 "unencrypted_suffix": "_unencrypted",
22 "version": "3.9.4"
23 }
24} \ No newline at end of file
diff --git a/hosts/vidhar/kimai/default.nix b/hosts/vidhar/kimai/default.nix
new file mode 100644
index 00000000..0258697b
--- /dev/null
+++ b/hosts/vidhar/kimai/default.nix
@@ -0,0 +1,89 @@
1{ flake, config, ... }:
2
3{
4 config = {
5 boot.enableContainers = true;
6 boot.kernel.sysctl = {
7 "net.netfilter.nf_log_all_netns" = true;
8 };
9
10 containers."kimai" = {
11 autoStart = true;
12 ephemeral = true;
13 bindMounts = {
14 "/var/lib/kimai" = {
15 hostPath = "/var/lib/kimai/state";
16 isReadOnly = false;
17 };
18 "/var/lib/mysql" = {
19 hostPath = "/var/lib/kimai/mysql";
20 isReadOnly = false;
21 };
22 };
23 privateNetwork = true;
24 # forwardPorts = [
25 # { containerPort = 80;
26 # hostPort = 28983;
27 # }
28 # ];
29 hostAddress = "192.168.52.113";
30 localAddress = "192.168.52.114";
31 hostAddress6 = "2a03:4000:52:ada:6::1";
32 localAddress6 = "2a03:4000:52:ada:6::2";
33 config = let hostConfig = config; in { config, pkgs, lib, ... }: {
34 system.stateVersion = lib.mkIf hostConfig.containers."kimai".ephemeral config.system.nixos.release;
35 system.configurationRevision = lib.mkIf (flake ? rev) flake.rev;
36 nixpkgs.pkgs = hostConfig.nixpkgs.pkgs;
37
38 services.kimai.sites."kimai.yggdrasil.li" = {
39 database.socket = "/run/mysqld/mysqld.sock";
40 };
41
42 networking = {
43 useDHCP = false;
44 useNetworkd = true;
45 useHostResolvConf = false;
46 firewall.enable = false;
47 nftables = {
48 enable = true;
49 rulesetFile = ./ruleset.nft;
50 };
51 };
52
53 services.resolved.fallbackDns = [
54 "9.9.9.10#dns10.quad9.net"
55 "149.112.112.10#dns10.quad9.net"
56 "2620:fe::10#dns10.quad9.net"
57 "2620:fe::fe:10#dns10.quad9.net"
58 ];
59
60 systemd.network = {
61 networks.upstream = {
62 name = "eth0";
63 matchConfig = {
64 Name = "eth0";
65 };
66 linkConfig = {
67 RequiredForOnline = true;
68 };
69 networkConfig = {
70 Address = [ "192.168.52.114/32" "2a03:4000:52:ada:6::2/128" ];
71 LLMNR = false;
72 MulticastDNS = false;
73 };
74 routes = [
75 { Destination = "192.168.52.113/32"; }
76 { Destination = "2a03:4000:52:ada:6::1/128"; }
77 { Destination = "0.0.0.0/0";
78 Gateway = "192.168.52.113";
79 }
80 { Destination = "::/0";
81 Gateway = "2a03:4000:52:ada:6::1";
82 }
83 ];
84 };
85 };
86 };
87 };
88 };
89}
diff --git a/hosts/vidhar/kimai/ruleset.nft b/hosts/vidhar/kimai/ruleset.nft
new file mode 100644
index 00000000..ad4db6d5
--- /dev/null
+++ b/hosts/vidhar/kimai/ruleset.nft
@@ -0,0 +1,149 @@
1define icmp_protos = {ipv6-icmp, icmp, igmp}
2
3table arp filter {
4 limit lim_arp {
5 rate over 50 mbytes/second burst 50 mbytes
6 }
7
8 counter arp-rx {}
9 counter arp-tx {}
10
11 counter arp-ratelimit-rx {}
12 counter arp-ratelimit-tx {}
13
14 chain input {
15 type filter hook input priority filter
16 policy accept
17
18 limit name lim_arp counter name arp-ratelimit-rx drop
19
20 counter name arp-rx
21 }
22
23 chain output {
24 type filter hook output priority filter
25 policy accept
26
27 limit name lim_arp counter name arp-ratelimit-tx drop
28
29 counter name arp-tx
30 }
31}
32
33table inet filter {
34 limit lim_reject {
35 rate over 1000/second burst 1000 packets
36 }
37
38 limit lim_icmp {
39 rate over 50 mbytes/second burst 50 mbytes
40 }
41
42 counter invalid-fw {}
43 counter fw-lo {}
44
45 counter reject-ratelimit-fw {}
46 counter reject-fw {}
47 counter reject-tcp-fw {}
48 counter reject-icmp-fw {}
49
50 counter drop-fw {}
51
52 counter invalid-rx {}
53
54 counter rx-lo {}
55 counter invalid-local4-rx {}
56 counter invalid-local6-rx {}
57
58 counter icmp-ratelimit-rx {}
59 counter icmp-rx {}
60
61 counter kimai-rx {}
62
63 counter established-rx {}
64
65 counter reject-ratelimit-rx {}
66 counter reject-rx {}
67 counter reject-tcp-rx {}
68 counter reject-icmp-rx {}
69
70 counter drop-rx {}
71
72 counter tx-lo {}
73
74 counter icmp-ratelimit-tx {}
75 counter icmp-tx {}
76
77 counter kimai-tx {}
78
79 counter tx {}
80
81 chain forward {
82 type filter hook forward priority filter
83 policy drop
84
85
86 ct state invalid log level debug prefix "kimai: drop invalid forward: " counter name invalid-fw drop
87
88
89 iifname lo counter name fw-lo accept
90
91
92 limit name lim_reject log level debug prefix "kimai: drop forward: " counter name reject-ratelimit-fw drop
93 log level debug prefix "kimai: reject forward: " counter name reject-fw
94 meta l4proto tcp ct state new counter name reject-tcp-fw reject with tcp reset
95 ct state new counter name reject-icmp-fw reject
96
97
98 counter name drop-fw
99 }
100
101 chain input {
102 type filter hook input priority filter
103 policy drop
104
105
106 ct state invalid log level debug prefix "kimai: drop invalid input: " counter name invalid-rx drop
107
108
109 iifname lo counter name rx-lo accept
110 iif != lo ip daddr 127.0.0.1/8 counter name invalid-local4-rx reject
111 iif != lo ip6 daddr ::1/128 counter name invalid-local6-rx reject
112
113
114 meta l4proto $icmp_protos limit name lim_icmp counter name icmp-ratelimit-rx drop
115 meta l4proto $icmp_protos counter name icmp-rx accept
116
117
118 tcp dport 80 counter name kimai-rx accept
119
120
121 ct state { established, related } counter name established-rx accept
122
123
124 limit name lim_reject log level debug prefix "kimai: drop input: " counter name reject-ratelimit-rx drop
125 log level debug prefix "kimai: reject input: " counter name reject-rx
126 meta l4proto tcp ct state new counter name reject-tcp-rx reject with tcp reset
127 ct state new counter name reject-icmp-rx reject
128
129
130 counter name drop-rx
131 }
132
133 chain output {
134 type filter hook output priority filter
135 policy accept
136
137
138 oifname lo counter name tx-lo accept
139
140 meta l4proto $icmp_protos limit name lim_icmp counter name icmp-ratelimit-tx drop
141 meta l4proto $icmp_protos counter name icmp-tx accept
142
143
144 tcp sport 80 counter name kimai-tx
145
146
147 counter name tx
148 }
149}
diff --git a/hosts/vidhar/network/default.nix b/hosts/vidhar/network/default.nix
index 0643f0bb..92d755f3 100644
--- a/hosts/vidhar/network/default.nix
+++ b/hosts/vidhar/network/default.nix
@@ -103,7 +103,14 @@ with lib;
103 /srv/nfs/nix-store 10.141.0.0/24(ro,async,root_squash) 2a03:4000:52:ada:1::/80(ro,async,root_squash) 103 /srv/nfs/nix-store 10.141.0.0/24(ro,async,root_squash) 2a03:4000:52:ada:1::/80(ro,async,root_squash)
104 ''; 104 '';
105 }; 105 };
106 settings.nfsd.vers3 = false; 106 settings.nfsd = {
107 rdma = true;
108 vers3 = false;
109 vers4 = true;
110 "vers4.0" = false;
111 "vers4.1" = false;
112 "vers4.2" = true;
113 };
107 }; 114 };
108 115
109 fileSystems = { 116 fileSystems = {
diff --git a/hosts/vidhar/network/dhcp/default.nix b/hosts/vidhar/network/dhcp/default.nix
index 07a83351..11460393 100644
--- a/hosts/vidhar/network/dhcp/default.nix
+++ b/hosts/vidhar/network/dhcp/default.nix
@@ -1,8 +1,33 @@
1{ flake, config, pkgs, lib, ... }: 1{ flake, config, pkgs, lib, sources, ... }:
2 2
3with lib; 3with lib;
4 4
5{ 5let
6 nfsrootBaseUrl = "http://nfsroot.vidhar.yggdrasil";
7 tftpIp = "10.141.0.1";
8 nfsIp = tftpIp;
9 ipxe = pkgs.ipxe.override {
10 additionalTargets = {
11 "bin-i386-efi/ipxe.efi" = "i386-ipxe.efi";
12 };
13 additionalOptions = [
14 "NSLOOKUP_CMD"
15 "PING_CMD"
16 "CONSOLE_CMD"
17 ];
18 embedScript = pkgs.writeText "yggdrasil.ipxe" ''
19 #!ipxe
20
21 cpair --background 9 1
22 cpair --background 9 3
23 cpair --background 9 6
24
25 set user-class iPXE-yggdrasil
26
27 autoboot
28 '';
29 };
30in {
6 config = { 31 config = {
7 services.kea = { 32 services.kea = {
8 dhcp4 = { 33 dhcp4 = {
@@ -23,41 +48,67 @@ with lib;
23 }; 48 };
24 49
25 client-classes = [ 50 client-classes = [
26 { name = "eostre-ipxe"; 51 { name = "ipxe-eostre";
27 test = "hexstring(pkt4.mac, ':') == '00:d8:61:79:c5:40' and option[77].hex == 'iPXE'"; 52 test = "hexstring(pkt4.mac, ':') == '00:d8:61:79:c5:40' and option[77].hex == 'iPXE-yggdrasil'";
28 next-server = "10.141.0.1"; 53 next-server = tftpIp;
29 boot-file-name = "http://nfsroot.vidhar.yggdrasil/eostre/netboot.ipxe"; 54 boot-file-name = "${nfsrootBaseUrl}/eostre.menu.ipxe";
55 only-if-required = true;
56 }
57 { name = "ipxe-yggdrasil";
58 test = "option[77].hex == 'iPXE-yggdrasil'";
59 next-server = tftpIp;
60 boot-file-name = "${nfsrootBaseUrl}/installer-x86_64-linux.menu.ipxe";
61 only-if-required = true;
62 }
63
64 { name = "uefi-http";
65 test = "option[client-system].hex == 0x0010";
66 option-data = [
67 { name = "vendor-class-identifier"; data = "HTTPClient"; }
68 ];
69 boot-file-name = "${nfsrootBaseUrl}/ipxe.efi";
70 only-if-required = true;
71 }
72
73 { name = "ipxe-uefi-64";
74 test = "option[77].hex == 'iPXE' and (substring(option[60].hex,0,20) == 'PXEClient:Arch:00007' or substring(option[60].hex,0,20) == 'PXEClient:Arch:00008' or substring(option[60].hex,0,20) == 'PXEClient:Arch:00009')";
75 boot-file-name = "${nfsrootBaseUrl}/ipxe.efi";
76 only-if-required = true;
77 }
78 { name = "ipxe-uefi-32";
79 test = "option[77].hex == 'iPXE' and (substring(option[60].hex,0,20) == 'PXEClient:Arch:00002' or substring(option[60].hex,0,20) == 'PXEClient:Arch:00006')";
80 boot-file-name = "${nfsrootBaseUrl}/i386-ipxe.efi";
30 only-if-required = true; 81 only-if-required = true;
31 } 82 }
32 { name = "ipxe"; 83 { name = "ipxe-legacy";
33 test = "option[77].hex == 'iPXE'"; 84 test = "option[77].hex == 'iPXE' and substring(option[60].hex,0,20) == 'PXEClient:Arch:00000'";
34 next-server = "10.141.0.1"; 85 boot-file-name = "${nfsrootBaseUrl}/ipxe.lkrn";
35 boot-file-name = "http://nfsroot.vidhar.yggdrasil/installer-x86_64-linux/netboot.ipxe";
36 only-if-required = true; 86 only-if-required = true;
37 } 87 }
88
38 { name = "uefi-64"; 89 { name = "uefi-64";
39 test = "substring(option[60].hex,0,20) == 'PXEClient:Arch:00007' or substring(option[60].hex,0,20) == 'PXEClient:Arch:00008' or substring(option[60].hex,0,20) == 'PXEClient:Arch:00009'"; 90 test = "substring(option[60].hex,0,20) == 'PXEClient:Arch:00007' or substring(option[60].hex,0,20) == 'PXEClient:Arch:00008' or substring(option[60].hex,0,20) == 'PXEClient:Arch:00009'";
40 only-if-required = true;
41 option-data = [ 91 option-data = [
42 { name = "tftp-server-name"; data = "10.141.0.1"; } 92 { name = "tftp-server-name"; data = tftpIp; }
43 ]; 93 ];
44 boot-file-name = "ipxe.efi"; 94 boot-file-name = "ipxe.efi";
95 only-if-required = true;
45 } 96 }
46 { name = "uefi-32"; 97 { name = "uefi-32";
47 test = "substring(option[60].hex,0,20) == 'PXEClient:Arch:00002' or substring(option[60].hex,0,20) == 'PXEClient:Arch:00006'"; 98 test = "substring(option[60].hex,0,20) == 'PXEClient:Arch:00002' or substring(option[60].hex,0,20) == 'PXEClient:Arch:00006'";
48 only-if-required = true;
49 option-data = [ 99 option-data = [
50 { name = "tftp-server-name"; data = "10.141.0.1"; } 100 { name = "tftp-server-name"; data = tftpIp; }
51 ]; 101 ];
52 boot-file-name = "i386-ipxe.efi"; 102 boot-file-name = "i386-ipxe.efi";
103 only-if-required = true;
53 } 104 }
54 { name = "legacy"; 105 { name = "legacy";
55 test = "substring(option[60].hex,0,20) == 'PXEClient:Arch:00000'"; 106 test = "substring(option[60].hex,0,20) == 'PXEClient:Arch:00000'";
56 only-if-required = true;
57 option-data = [ 107 option-data = [
58 { name = "tftp-server-name"; data = "10.141.0.1"; } 108 { name = "tftp-server-name"; data = tftpIp; }
59 ]; 109 ];
60 boot-file-name = "undionly.kpxe"; 110 boot-file-name = "ipxe.lkrn";
111 only-if-required = true;
61 } 112 }
62 ]; 113 ];
63 114
@@ -252,34 +303,78 @@ with lib;
252 name = "nfsroot.vidhar.yggdrasil"; 303 name = "nfsroot.vidhar.yggdrasil";
253 paths = 304 paths =
254 (map (system: 305 (map (system:
255 let 306 pkgs.symlinkJoin {
256 installerBuild = (flake.nixosConfigurations.${"installer-${system}-nfsroot"}.extendModules { 307 name = "installer-${system}";
257 modules = [ 308 paths = [
258 ({ ... }: { 309 (builtins.addErrorContext "while evaluating installer-${system}-nfsroot" (let
259 config.nfsroot.storeDevice = "10.141.0.1:nix-store"; 310 installerBuild' = (flake.nixosConfigurations.${"installer-${system}-nfsroot"}.extendModules {
260 config.nfsroot.registrationUrl = "http://nfsroot.vidhar.yggdrasil/installer-${system}/registration"; 311 modules = [
261 }) 312 ({ ... }: {
262 ]; 313 config.nfsroot.storeDevice = "${nfsIp}:nix-store";
263 }).config.system.build; 314 config.nfsroot.registrationUrl = "${nfsrootBaseUrl}/installer-${system}/registration";
264 in builtins.toPath (pkgs.runCommandLocal "install-${system}" {} '' 315 config.system.nixos.label = "installer-${system}";
265 mkdir -p $out/installer-${system} 316 })
266 install -m 0444 -t $out/installer-${system} \ 317 ];
267 ${installerBuild.initialRamdisk}/initrd \ 318 });
268 ${installerBuild.kernel}/bzImage \ 319 installerBuild = installerBuild'.config.system.build;
269 ${installerBuild.netbootIpxeScript}/netboot.ipxe \ 320 in builtins.toPath (pkgs.runCommandLocal "installer-${system}" {} ''
270 ${pkgs.closureInfo { rootPaths = installerBuild.storeContents; }}/registration 321 mkdir -p $out/installer-${system}
271 '') 322 install -m 0444 -t $out/installer-${system} \
272 ) ["x86_64-linux"] 323 ${installerBuild.initialRamdisk}/initrd \
324 ${installerBuild.kernel}/bzImage \
325 ${installerBuild.netbootIpxeScript}/netboot.ipxe \
326 ${pkgs.closureInfo { rootPaths = installerBuild.storeContents; }}/registration
327 install -m 0444 ${pkgs.writeText "installer-${system}.menu.ipxe" ''
328 #!ipxe
329
330 :start
331 menu iPXE boot menu for installer-${system}
332 item installer ${with installerBuild'; "${config.system.nixos.distroName} ${config.system.nixos.codeName} ${config.system.nixos.label} (Linux ${config.boot.kernelPackages.kernel.modDirVersion})"}
333 item memtest memtest86plus
334 item netboot netboot.xyz
335 item shell iPXE shell
336 choose --timeout 0 --default installer selected || goto shell
337 goto ''${selected}
338
339 :shell
340 shell
341 goto start
342
343 :installer
344 chain installer-${system}/netboot.ipxe
345 goto start
346
347 :netboot
348 iseq ''${platform} efi && chain --autofree netboot.xyz.efi || chain --autofree netboot.xyz.lkrn
349 goto start
350
351 :memtest
352 iseq ''${platform} efi && chain --autofree memtest.efi || chain --autofree memtest.bin
353 goto start
354 ''} $out/installer-${system}.menu.ipxe
355 '')))
356 ];
357 }) ["x86_64-linux"]
273 ) ++ [ 358 ) ++ [
274 (let 359 (pkgs.runCommandLocal "utils" {} ''
275 eostreBuild = (flake.nixosConfigurations.eostre.extendModules { 360 mkdir $out
361 install -m 0444 -t $out \
362 ${ipxe}/{ipxe.efi,i386-ipxe.efi,ipxe.lkrn} \
363 ${pkgs.memtest86plus}/{memtest.efi,memtest.bin}
364 install -m 0444 ${sources.netbootxyz-efi.src} $out/netboot.xyz.efi
365 install -m 0444 ${sources.netbootxyz-lkrn.src} $out/netboot.xyz.lkrn
366 '')
367 (builtins.addErrorContext "while evaluating eostre" (let
368 eostreBuild' = (flake.nixosConfigurations.eostre.extendModules {
276 modules = [ 369 modules = [
277 ({ ... }: { 370 ({ ... }: {
278 config.nfsroot.storeDevice = "10.141.0.1:nix-store"; 371 config.nfsroot.storeDevice = "${nfsIp}:nix-store";
279 config.nfsroot.registrationUrl = "http://nfsroot.vidhar.yggdrasil/eostre/registration"; 372 config.nfsroot.registrationUrl = "${nfsrootBaseUrl}/eostre/registration";
373 config.system.nixos.label = "eostre";
280 }) 374 })
281 ]; 375 ];
282 }).config.system.build; 376 });
377 eostreBuild = eostreBuild'.config.system.build;
283 in builtins.toPath (pkgs.runCommandLocal "eostre" {} '' 378 in builtins.toPath (pkgs.runCommandLocal "eostre" {} ''
284 mkdir -p $out/eostre 379 mkdir -p $out/eostre
285 install -m 0444 -t $out/eostre \ 380 install -m 0444 -t $out/eostre \
@@ -287,7 +382,39 @@ with lib;
287 ${eostreBuild.kernel}/bzImage \ 382 ${eostreBuild.kernel}/bzImage \
288 ${eostreBuild.netbootIpxeScript}/netboot.ipxe \ 383 ${eostreBuild.netbootIpxeScript}/netboot.ipxe \
289 ${pkgs.closureInfo { rootPaths = eostreBuild.storeContents; }}/registration 384 ${pkgs.closureInfo { rootPaths = eostreBuild.storeContents; }}/registration
290 '')) 385 install -m 0444 ${pkgs.writeText "eostre.menu.ipxe" ''
386 #!ipxe
387
388 set menu-timeout 5000
389
390 :start
391 menu iPXE boot menu for eostre
392 item eostre ${with eostreBuild'; "${config.system.nixos.distroName} ${config.system.nixos.codeName} ${config.system.nixos.label} (Linux ${config.boot.kernelPackages.kernel.modDirVersion})"}
393 item memtest memtest86plus
394 item netboot netboot.xyz
395 item shell iPXE shell
396 choose --timeout ''${menu-timeout} --default eostre selected || goto shell
397 set menu-timeout 0
398 goto ''${selected}
399
400 :shell
401 set menu-timeout 0
402 shell
403 goto start
404
405 :eostre
406 chain eostre/netboot.ipxe
407 goto start
408
409 :netboot
410 iseq ''${platform} efi && chain --autofree netboot.xyz.efi || chain --autofree netboot.xyz.lkrn
411 goto start
412
413 :memtest
414 iseq ''${platform} efi && chain --autofree memtest.efi || chain --autofree memtest.bin
415 goto start
416 ''} $out/eostre.menu.ipxe
417 '')))
291 ]; 418 ];
292 }; 419 };
293 }; 420 };
@@ -298,20 +425,12 @@ with lib;
298 after = [ "network.target" ]; 425 after = [ "network.target" ];
299 wantedBy = [ "multi-user.target" ]; 426 wantedBy = [ "multi-user.target" ];
300 serviceConfig.ExecStart = let 427 serviceConfig.ExecStart = let
301 ipxe = pkgs.ipxe.override {
302 additionalTargets = {
303 "bin-i386-efi/ipxe.efi" = "i386-ipxe.efi";
304 };
305 additionalOptions = [
306 "NSLOOKUP_CMD"
307 ];
308 };
309 tftpRoot = pkgs.runCommandLocal "netboot" {} '' 428 tftpRoot = pkgs.runCommandLocal "netboot" {} ''
310 mkdir -p $out 429 mkdir -p $out
311 install -m 0444 -t $out \ 430 install -m 0444 -t $out \
312 ${ipxe}/ipxe.efi ${ipxe}/i386-ipxe.efi ${ipxe}/undionly.kpxe 431 ${ipxe}/{ipxe.efi,i386-ipxe.efi,ipxe.lkrn}
313 ''; 432 '';
314 in "${pkgs.atftp}/sbin/atftpd --daemon --no-fork --bind-address=10.141.0.1 ${tftpRoot}"; 433 in "${pkgs.atftp}/sbin/atftpd --daemon --no-fork --bind-address=${tftpIp} ${tftpRoot}";
315 }; 434 };
316 }; 435 };
317} 436}
diff --git a/hosts/vidhar/network/ruleset.nft b/hosts/vidhar/network/ruleset.nft
index 10fd4c51..7897fb3d 100644
--- a/hosts/vidhar/network/ruleset.nft
+++ b/hosts/vidhar/network/ruleset.nft
@@ -60,6 +60,7 @@ table inet filter {
60 counter fw-lo {} 60 counter fw-lo {}
61 counter fw-lan {} 61 counter fw-lan {}
62 counter fw-gpon {} 62 counter fw-gpon {}
63 counter fw-kimai {}
63 64
64 counter fw-cups {} 65 counter fw-cups {}
65 66
@@ -92,6 +93,10 @@ table inet filter {
92 counter tftp-rx {} 93 counter tftp-rx {}
93 counter pgbackrest-rx {} 94 counter pgbackrest-rx {}
94 counter immich-rx {} 95 counter immich-rx {}
96 counter paperless-rx {}
97 counter hledger-rx {}
98 counter audiobookshelf-rx {}
99 counter kimai-rx {}
95 100
96 counter established-rx {} 101 counter established-rx {}
97 102
@@ -121,6 +126,10 @@ table inet filter {
121 counter tftp-tx {} 126 counter tftp-tx {}
122 counter pgbackrest-tx {} 127 counter pgbackrest-tx {}
123 counter immich-tx {} 128 counter immich-tx {}
129 counter paperless-tx {}
130 counter hledger-tx {}
131 counter audiobookshelf-tx {}
132 counter kimai-tx {}
124 133
125 counter tx {} 134 counter tx {}
126 135
@@ -144,8 +153,13 @@ table inet filter {
144 153
145 oifname { lan, gpon, bifrost } meta l4proto $icmp_protos jump forward_icmp_accept 154 oifname { lan, gpon, bifrost } meta l4proto $icmp_protos jump forward_icmp_accept
146 iifname lan oifname { gpon, bifrost } counter name fw-lan accept 155 iifname lan oifname { gpon, bifrost } counter name fw-lan accept
156 iifname ve-kimai oifname gpon counter name fw-kimai accept
147 157
148 iifname gpon oifname lan ct state { established, related } counter name fw-gpon accept 158 iifname gpon oifname lan ct state { established, related } counter name fw-gpon accept
159 iifname gpon oifname ve-kimai ct state { established, related } counter name fw-kimai accept
160
161 iifname bifrost oifname ve-kimai tcp dport 80 ip6 saddr $bifrost_surtr ip6 daddr 2a03:4000:52:ada:6::2 counter name kimai-rx accept
162 iifname ve-kimai oifname bifrost tcp sport 80 ip6 saddr 2a03:4000:52:ada:6::2 ip6 daddr $bifrost_surtr counter name kimai-tx accept
149 163
150 164
151 limit name lim_reject log level debug prefix "drop forward: " counter name reject-ratelimit-fw drop 165 limit name lim_reject log level debug prefix "drop forward: " counter name reject-ratelimit-fw drop
@@ -197,6 +211,9 @@ table inet filter {
197 tcp dport 8432 counter name pgbackrest-rx accept 211 tcp dport 8432 counter name pgbackrest-rx accept
198 212
199 iifname bifrost tcp dport 2283 ip6 saddr $bifrost_surtr counter name immich-rx accept 213 iifname bifrost tcp dport 2283 ip6 saddr $bifrost_surtr counter name immich-rx accept
214 iifname bifrost tcp dport 28981 ip6 saddr $bifrost_surtr counter name paperless-rx accept
215 iifname bifrost tcp dport 5000 ip6 saddr $bifrost_surtr counter name hledger-rx accept
216 iifname bifrost tcp dport 28982 ip6 saddr $bifrost_surtr counter name audiobookshelf-rx accept
200 217
201 ct state { established, related } counter name established-rx accept 218 ct state { established, related } counter name established-rx accept
202 219
@@ -246,6 +263,9 @@ table inet filter {
246 tcp sport 8432 counter name pgbackrest-tx accept 263 tcp sport 8432 counter name pgbackrest-tx accept
247 264
248 iifname bifrost tcp sport 2283 ip6 daddr $bifrost_surtr counter name immich-tx accept 265 iifname bifrost tcp sport 2283 ip6 daddr $bifrost_surtr counter name immich-tx accept
266 iifname bifrost tcp sport 28981 ip6 daddr $bifrost_surtr counter name paperless-tx accept
267 iifname bifrost tcp sport 5000 ip6 daddr $bifrost_surtr counter name hledger-tx accept
268 iifname bifrost tcp sport 28982 ip6 daddr $bifrost_surtr counter name audiobookshelf-tx accept
249 269
250 270
251 counter name tx 271 counter name tx
@@ -254,7 +274,7 @@ table inet filter {
254 274
255table inet nat { 275table inet nat {
256 counter gpon-nat {} 276 counter gpon-nat {}
257 # counter container-nat {} 277 counter kimai-nat {}
258 278
259 chain postrouting { 279 chain postrouting {
260 type nat hook postrouting priority srcnat 280 type nat hook postrouting priority srcnat
@@ -262,7 +282,7 @@ table inet nat {
262 282
263 283
264 meta nfproto ipv4 oifname gpon counter name gpon-nat masquerade 284 meta nfproto ipv4 oifname gpon counter name gpon-nat masquerade
265 # iifname ve-* oifname gpon counter name container-nat masquerade 285 iifname ve-kimai oifname gpon counter name kimai-nat masquerade
266 } 286 }
267} 287}
268 288
diff --git a/hosts/vidhar/paperless/default.nix b/hosts/vidhar/paperless/default.nix
new file mode 100644
index 00000000..dd02da38
--- /dev/null
+++ b/hosts/vidhar/paperless/default.nix
@@ -0,0 +1,25 @@
1{ config, ... }:
2
3{
4 config = {
5 services.paperless = {
6 enable = true;
7 address = "2a03:4000:52:ada:4:1::";
8 passwordFile = config.sops.secrets."paperless-rootpw".path;
9 settings = {
10 PAPERLESS_OCR_LANGUAGE = "deu+eng";
11 PAPERLESS_URL = "https://paperless.yggdrasil.li";
12 PAPERLESS_FILENAME_FORMAT = "{{ created_year }}/{{ document_type }}/{{ correspondent }}/{{ created }}_{{ doc_pk }}_{{ title }}";
13 PAPERLESS_FILENAME_FORMAT_REMOVE_NONE = "true";
14 PAPERLESS_TASK_WORKERS = "3";
15 PAPERLESS_THREADS_PER_WORKER = "4";
16 };
17 database.createLocally = true;
18 };
19
20 sops.secrets."paperless-rootpw" = {
21 format = "binary";
22 sopsFile = ./rootpw;
23 };
24 };
25}
diff --git a/hosts/vidhar/paperless/rootpw b/hosts/vidhar/paperless/rootpw
new file mode 100644
index 00000000..11f48fcb
--- /dev/null
+++ b/hosts/vidhar/paperless/rootpw
@@ -0,0 +1,24 @@
1{
2 "data": "ENC[AES256_GCM,data:Bsns3bLs7aA++eTf2Vh4g2iAXhmrMRTF,iv:zQ6hgXEvgHAloN6UMW54f2nYCvEhHPXQSBVSihHFiC0=,tag:uiGTEs07dpx12PcAjmbr9Q==,type:str]",
3 "sops": {
4 "kms": null,
5 "gcp_kms": null,
6 "azure_kv": null,
7 "hc_vault": null,
8 "age": [
9 {
10 "recipient": "age1rmmhetcmllq0ahl5qznlr0eya2zdxwl9h6y5wnl97d2wtyx5t99sm2u866",
11 "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBlVUJjdEdIZGd6UDJBRXlL\nODFyWDhHOU9oTEVCVlFiUXVXNm9XZmVuampVCkJ0YkFXTlZXVnRldmtlVkJaR3R2\nMFhpaHB5M3pLeDFkUkkzMUFydGNnOFEKLS0tIEJtNWc0V2JaaWYvQlp6TGxVdVZO\neVpzQzB5Um82TUZOeHBHeE50MGlqNWsKj1P54Fc+c5n35+Og9DwBWkvW947hgFsp\ni/G2QcaLHHJMTexTCZYsr1naSVa/cMBAbrZmtjz0HV4Q1kCJtvlrIg==\n-----END AGE ENCRYPTED FILE-----\n"
12 },
13 {
14 "recipient": "age1qffdqvy9arld9zd5a5cylt0n98xhcns5shxhrhwjq5g4qa844ejselaa4l",
15 "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA1UG1QSWtXcFZoQVRBOC9D\nT2VnTW9pcTRCMForcHdZVld0c1NmNFZpWUNBCkRkMERKUVliYXRqb25saWxyb2JN\nbC9YL2ZQbytRM0ZjNmlQOTlTZTQrV2sKLS0tIFZyUWtRcXNqZUZxMGN5d0tHUng2\nVXNSdFEwMmtIVEdVRVlWeVU1YmJVSkUKRJa42k551QtiC6S0tmMv7eVN7GRqpXWz\nvzNh+BM9TOJNaTMmVesr4vXNDLOSFS3PxYv95xuOBzVg3zOHuai72g==\n-----END AGE ENCRYPTED FILE-----\n"
16 }
17 ],
18 "lastmodified": "2025-02-13T19:20:33Z",
19 "mac": "ENC[AES256_GCM,data:mG6AC3L8MMeZ0Ajr7zV1mzPcHviQw2adtGjSbrbPRw1xqN7siu6svoybv8xkahP2Grq/xKAiyfXFOFo7Uyc3ub5fSovAEolNazqybZYsyam5vHpeC23dXcEkZUJSPJ9/CSB5uI9nX3NPC64QUjCxHZ7qfH5gcXT9D12H8LSqKlQ=,iv:4Skdj8l9jlTX9Unc2xE2hCKVawHBnHR8L4kZA6H8xNw=,tag:zJsJ3S//faAn7AGwLefNoA==,type:str]",
20 "pgp": null,
21 "unencrypted_suffix": "_unencrypted",
22 "version": "3.9.4"
23 }
24} \ No newline at end of file
diff --git a/hosts/vidhar/prometheus/default.nix b/hosts/vidhar/prometheus/default.nix
index d368ad52..094f9f7a 100644
--- a/hosts/vidhar/prometheus/default.nix
+++ b/hosts/vidhar/prometheus/default.nix
@@ -26,7 +26,8 @@ in {
26 enable = true; 26 enable = true;
27 27
28 extraFlags = [ 28 extraFlags = [
29 "--enable-feature=remote-write-receiver" 29 "--web.enable-remote-write-receiver"
30 "--storage.tsdb.retention.size=35GB"
30 ]; 31 ];
31 32
32 exporters = { 33 exporters = {