summaryrefslogtreecommitdiff
path: root/hosts
diff options
context:
space:
mode:
Diffstat (limited to 'hosts')
-rw-r--r--hosts/eostre/default.nix21
-rw-r--r--hosts/sif/default.nix50
-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/mail/default.nix70
-rw-r--r--hosts/surtr/bifrost/default.nix4
-rw-r--r--hosts/surtr/default.nix3
-rw-r--r--hosts/surtr/dns/default.nix2
-rw-r--r--hosts/surtr/dns/keys/kimai.yggdrasil.li_acme19
-rw-r--r--hosts/surtr/dns/zones/email.nights.soa8
-rw-r--r--hosts/surtr/dns/zones/li.141.soa9
-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.yggdrasil.soa12
-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.nix195
-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/kimai.nix68
-rw-r--r--hosts/surtr/postgresql/default.nix58
-rw-r--r--hosts/surtr/tls/tsig_keys/kimai.yggdrasil.li19
-rw-r--r--hosts/vidhar/default.nix4
-rw-r--r--hosts/vidhar/kimai/default.nix89
-rw-r--r--hosts/vidhar/kimai/ruleset.nft149
-rw-r--r--hosts/vidhar/network/ruleset.nft12
-rw-r--r--hosts/vidhar/prometheus/default.nix3
33 files changed, 997 insertions, 223 deletions
diff --git a/hosts/eostre/default.nix b/hosts/eostre/default.nix
index fd4b15f2..d4113024 100644
--- a/hosts/eostre/default.nix
+++ b/hosts/eostre/default.nix
@@ -37,14 +37,10 @@ with lib;
37 powerManagement.enable = true; 37 powerManagement.enable = true;
38 }; 38 };
39 39
40 opengl.enable = true; 40 graphics.enable = true;
41 }; 41 };
42 42
43 environment.etc."machine-id".text = "f457b21333f1491e916521151ff5d468";
44
45 networking = { 43 networking = {
46 hostId = "f457b213";
47
48 domain = "lan.yggdrasil"; 44 domain = "lan.yggdrasil";
49 search = [ "lan.yggdrasil" "yggdrasil" ]; 45 search = [ "lan.yggdrasil" "yggdrasil" ];
50 46
@@ -83,19 +79,14 @@ with lib;
83 ]; 79 ];
84 }; 80 };
85 81
86 82 services.displayManager.sddm = {
87 services.xserver = {
88 enable = true; 83 enable = true;
89 displayManager.sddm = { 84 wayland.enable = true;
90 enable = true; 85 settings = {
91 settings = { 86 Users.HideUsers = "gkleen";
92 Users.HideUsers = "gkleen";
93 };
94 }; 87 };
95 desktopManager.plasma5.enable = true;
96
97 videoDrivers = [ "nvidia" ];
98 }; 88 };
89 services.desktopManager.plasma6.enable = true;
99 90
100 91
101 services.openssh = { 92 services.openssh = {
diff --git a/hosts/sif/default.nix b/hosts/sif/default.nix
index f4de24e8..b0d2fd78 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
@@ -98,6 +98,8 @@ in {
98 server ptbtime2.ptb.de prefer iburst nts 98 server ptbtime2.ptb.de prefer iburst nts
99 server ptbtime3.ptb.de prefer iburst nts 99 server ptbtime3.ptb.de prefer iburst nts
100 server ptbtime4.ptb.de prefer iburst nts 100 server ptbtime4.ptb.de prefer iburst nts
101 pool ntppool1.time.nl prefer iburst nts
102 pool ntppool2.time.nl prefer iburst nts
101 103
102 authselectmode require 104 authselectmode require
103 minsources 3 105 minsources 3
@@ -130,6 +132,12 @@ in {
130 useNetworkd = true; 132 useNetworkd = true;
131 }; 133 };
132 134
135 environment.etc."NetworkManager/dnsmasq.d/dnssec.conf" = {
136 text = ''
137 conf-file=${pkgs.dnsmasq}/share/dnsmasq/trust-anchors.conf
138 dnssec
139 '';
140 };
133 environment.etc."NetworkManager/dnsmasq.d/libvirt_dnsmasq.conf" = { 141 environment.etc."NetworkManager/dnsmasq.d/libvirt_dnsmasq.conf" = {
134 text = '' 142 text = ''
135 except-interface=virbr0 143 except-interface=virbr0
@@ -372,19 +380,6 @@ in {
372 ]; 380 ];
373 381
374 services = { 382 services = {
375 uucp = {
376 enable = true;
377 nodeName = "sif";
378 remoteNodes = {
379 "ymir" = {
380 publicKeys = ["ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIG6KNtsCOl5fsZ4rV7udTulGMphJweLBoKapzerWNoLY root@ymir"];
381 hostnames = ["ymir.yggdrasil.li" "ymir.niflheim.yggdrasil"];
382 };
383 };
384
385 defaultCommands = lib.mkForce [];
386 };
387
388 avahi.enable = true; 383 avahi.enable = true;
389 384
390 fwupd.enable = true; 385 fwupd.enable = true;
@@ -403,8 +398,8 @@ in {
403 398
404 logind = { 399 logind = {
405 lidSwitch = "suspend"; 400 lidSwitch = "suspend";
406 lidSwitchDocked = "lock"; 401 lidSwitchDocked = "ignore";
407 lidSwitchExternalPower = "lock"; 402 lidSwitchExternalPower = "ignore";
408 }; 403 };
409 404
410 atd = { 405 atd = {
@@ -610,25 +605,6 @@ in {
610 605
611 environment.etc."X11/xorg.conf.d/50-wacom.conf".source = lib.mkForce ./wacom.conf; 606 environment.etc."X11/xorg.conf.d/50-wacom.conf".source = lib.mkForce ./wacom.conf;
612 607
613 systemd.services."ac-plugged" = {
614 description = "Inhibit handling of lid-switch and sleep";
615
616 path = with pkgs; [ systemd coreutils ];
617
618 script = ''
619 exec systemd-inhibit --what=handle-lid-switch --why="AC is connected" --mode=block sleep infinity
620 '';
621
622 serviceConfig = {
623 Type = "simple";
624 };
625 };
626
627 services.udev.extraRules = with pkgs; lib.mkAfter ''
628 SUBSYSTEM=="power_supply", ENV{POWER_SUPPLY_ONLINE}=="0", RUN+="${systemd}/bin/systemctl --no-block stop ac-plugged.service"
629 SUBSYSTEM=="power_supply", ENV{POWER_SUPPLY_ONLINE}=="1", RUN+="${systemd}/bin/systemctl --no-block start ac-plugged.service"
630 '';
631
632 systemd.services."nix-daemon".serviceConfig = { 608 systemd.services."nix-daemon".serviceConfig = {
633 MemoryAccounting = true; 609 MemoryAccounting = true;
634 MemoryHigh = "50%"; 610 MemoryHigh = "50%";
@@ -688,7 +664,7 @@ in {
688 directories = [ 664 directories = [
689 "/nix" 665 "/nix"
690 "/root" 666 "/root"
691 "/home" 667 "/home"
692 "/var/log" 668 "/var/log"
693 "/var/lib/sops-nix" 669 "/var/lib/sops-nix"
694 "/var/lib/nixos" 670 "/var/lib/nixos"
@@ -699,8 +675,6 @@ in {
699 "/var/lib/upower" 675 "/var/lib/upower"
700 "/var/lib/postfix" 676 "/var/lib/postfix"
701 "/etc/NetworkManager/system-connections" 677 "/etc/NetworkManager/system-connections"
702 { directory = "/var/uucp"; user = "uucp"; group = "uucp"; mode = "0700"; }
703 { directory = "/var/spool/uucp"; user = "uucp"; group = "uucp"; mode = "0750"; }
704 ]; 678 ];
705 files = [ 679 files = [
706 ]; 680 ];
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/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/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 d420040a..63beece3 100644
--- a/hosts/surtr/default.nix
+++ b/hosts/surtr/default.nix
@@ -7,7 +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 10 ./paperless.nix ./hledger.nix ./audiobookshelf.nix ./kimai.nix
11 ]; 11 ];
12 12
13 config = { 13 config = {
@@ -22,7 +22,6 @@ with lib;
22 device = "/dev/vda"; 22 device = "/dev/vda";
23 }; 23 };
24 24
25
26 tmp.useTmpfs = true; 25 tmp.useTmpfs = true;
27 26
28 zfs.devNodes = "/dev"; # /dev/vda2 does not show up in /dev/disk/by-id 27 zfs.devNodes = "/dev"; # /dev/vda2 does not show up in /dev/disk/by-id
diff --git a/hosts/surtr/dns/default.nix b/hosts/surtr/dns/default.nix
index 7aa3fb00..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" "paperless.yggdrasil.li" "hledger.yggdrasil.li" "audiobookshelf.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"]; };
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/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 ab117f09..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 2025020900 ; 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.
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.yggdrasil.soa b/hosts/surtr/dns/zones/li.yggdrasil.soa
index 7273827b..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 2025050900 ; serial 4 2025060700 ; serial
5 10800 ; refresh 5 10800 ; refresh
6 3600 ; retry 6 3600 ; retry
7 604800 ; expire 7 604800 ; expire
@@ -101,12 +101,22 @@ _acme-challenge.audiobookshelf IN NS ns.yggdrasil.li.
101 101
102audiobookshelf IN HTTPS 1 . alpn="h2,h3" ipv4hint="202.61.241.61" ipv6hint="2a03:4000:52:ada::" 102audiobookshelf IN HTTPS 1 . alpn="h2,h3" ipv4hint="202.61.241.61" ipv6hint="2a03:4000:52:ada::"
103 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
104vidhar IN AAAA 2a03:4000:52:ada:4:1:: 112vidhar IN AAAA 2a03:4000:52:ada:4:1::
105vidhar IN MX 0 ymir.yggdrasil.li 113vidhar IN MX 0 ymir.yggdrasil.li
106vidhar IN TXT "v=spf1 redirect=yggdrasil.li" 114vidhar IN TXT "v=spf1 redirect=yggdrasil.li"
107 115
108mailout IN A 188.68.51.254 116mailout IN A 188.68.51.254
109mailout 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::
110mailout IN MX 0 ymir.yggdrasil.li 120mailout IN MX 0 ymir.yggdrasil.li
111mailout IN TXT "v=spf1 redirect=yggdrasil.li" 121mailout IN TXT "v=spf1 redirect=yggdrasil.li"
112 122
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 4666d1d6..fa7ddac6 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
@@ -113,14 +129,14 @@ in {
113 setSendmail = true; 129 setSendmail = true;
114 postmasterAlias = ""; rootAlias = ""; extraAliases = ""; 130 postmasterAlias = ""; rootAlias = ""; extraAliases = "";
115 destination = []; 131 destination = [];
116 sslCert = "/run/credentials/postfix.service/surtr.yggdrasil.li.pem";
117 sslKey = "/run/credentials/postfix.service/surtr.yggdrasil.li.key.pem";
118 networks = []; 132 networks = [];
119 config = let 133 config = {
120 relay_ccert = "texthash:${pkgs.writeText "relay_ccert" ""}";
121 in {
122 smtpd_tls_security_level = "may"; 134 smtpd_tls_security_level = "may";
123 135
136 smtpd_tls_chain_files = [
137 "/run/credentials/postfix.service/surtr.yggdrasil.li.full.pem"
138 ];
139
124 #the dh params 140 #the dh params
125 smtpd_tls_dh1024_param_file = toString config.security.dhparams.params."postfix-1024".path; 141 smtpd_tls_dh1024_param_file = toString config.security.dhparams.params."postfix-1024".path;
126 smtpd_tls_dh512_param_file = toString config.security.dhparams.params."postfix-512".path; 142 smtpd_tls_dh512_param_file = toString config.security.dhparams.params."postfix-512".path;
@@ -155,12 +171,7 @@ in {
155 171
156 smtp_tls_connection_reuse = true; 172 smtp_tls_connection_reuse = true;
157 173
158 tls_server_sni_maps = ''texthash:${pkgs.writeText "sni" ( 174 tls_server_sni_maps = "inline:{${concatMapStringsSep ", " (domain: "{ ${domain} = /run/credentials/postfix.service/${removePrefix "." domain}.full.pem }") (concatMap (domain: [domain "mailin.${domain}" "mailsub.${domain}" ".${domain}"]) emailDomains)}}";
159 concatMapStringsSep "\n\n" (domain:
160 concatMapStringsSep "\n" (subdomain: "${subdomain} /run/credentials/postfix.service/${removePrefix "." subdomain}.full.pem")
161 [domain "mailin.${domain}" "mailsub.${domain}" ".${domain}"]
162 ) emailDomains
163 )}'';
164 175
165 smtp_tls_policy_maps = "socketmap:unix:${config.services.postfix-mta-sts-resolver.settings.path}:postfix"; 176 smtp_tls_policy_maps = "socketmap:unix:${config.services.postfix-mta-sts-resolver.settings.path}:postfix";
166 177
@@ -184,12 +195,12 @@ in {
184 dbname = email 195 dbname = email
185 query = SELECT action FROM virtual_mailbox_access WHERE lookup = '%s' 196 query = SELECT action FROM virtual_mailbox_access WHERE lookup = '%s'
186 ''}" 197 ''}"
187 "check_ccert_access ${relay_ccert}"
188 "reject_non_fqdn_helo_hostname" 198 "reject_non_fqdn_helo_hostname"
189 "reject_invalid_helo_hostname" 199 "reject_invalid_helo_hostname"
190 "reject_unauth_destination" 200 "reject_unauth_destination"
191 "reject_unknown_recipient_domain" 201 "reject_unknown_recipient_domain"
192 "reject_unverified_recipient" 202 "reject_unverified_recipient"
203 "check_policy_service unix:/run/postfix-internal-policy.sock"
193 ]; 204 ];
194 unverified_recipient_reject_code = "550"; 205 unverified_recipient_reject_code = "550";
195 unverified_recipient_reject_reason = "Recipient address lookup failed"; 206 unverified_recipient_reject_reason = "Recipient address lookup failed";
@@ -204,7 +215,6 @@ in {
204 address_verify_sender_ttl = "30045s"; 215 address_verify_sender_ttl = "30045s";
205 216
206 smtpd_relay_restrictions = [ 217 smtpd_relay_restrictions = [
207 "check_ccert_access ${relay_ccert}"
208 "reject_unauth_destination" 218 "reject_unauth_destination"
209 ]; 219 ];
210 220
@@ -251,11 +261,24 @@ in {
251 virtual_transport = "dvlmtp:unix:/run/dovecot-lmtp"; 261 virtual_transport = "dvlmtp:unix:/run/dovecot-lmtp";
252 smtputf8_enable = false; 262 smtputf8_enable = false;
253 263
254 authorized_submit_users = "inline:{ root= postfwd= }"; 264 authorized_submit_users = "inline:{ root= postfwd= ${config.services.dovecot2.user}= }";
265 authorized_flush_users = "inline:{ root= }";
266 authorized_mailq_users = "inline:{ root= }";
255 267
256 postscreen_access_list = ""; 268 postscreen_access_list = "";
257 postscreen_denylist_action = "drop"; 269 postscreen_denylist_action = "drop";
258 postscreen_greet_action = "enforce"; 270 postscreen_greet_action = "enforce";
271
272 sender_bcc_maps = ''pgsql:${pkgs.writeText "sender_bcc_maps.cf" ''
273 hosts = postgresql:///email
274 dbname = email
275 query = SELECT value FROM sender_bcc_maps WHERE key = '%s'
276 ''}'';
277 recipient_bcc_maps = ''pgsql:${pkgs.writeText "recipient_bcc_maps.cf" ''
278 hosts = postgresql:///email
279 dbname = email
280 query = SELECT value FROM recipient_bcc_maps WHERE key = '%s'
281 ''}'';
259 }; 282 };
260 masterConfig = { 283 masterConfig = {
261 "465" = { 284 "465" = {
@@ -280,7 +303,7 @@ in {
280 hosts = postgresql:///email 303 hosts = postgresql:///email
281 dbname = email 304 dbname = email
282 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')) 305 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'))
283 ''},permit_tls_all_clientcerts,reject}'' 306 ''},check_policy_service unix:/run/postfix-internal-policy.sock,permit_tls_all_clientcerts,reject}''
284 "-o" "smtpd_relay_restrictions=permit_tls_all_clientcerts,reject" 307 "-o" "smtpd_relay_restrictions=permit_tls_all_clientcerts,reject"
285 "-o" "{smtpd_data_restrictions = check_policy_service unix:/run/postfwd3/postfwd3.sock}" 308 "-o" "{smtpd_data_restrictions = check_policy_service unix:/run/postfwd3/postfwd3.sock}"
286 "-o" "unverified_sender_reject_code=550" 309 "-o" "unverified_sender_reject_code=550"
@@ -310,7 +333,7 @@ in {
310 hosts = postgresql:///email 333 hosts = postgresql:///email
311 dbname = email 334 dbname = email
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')) 335 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'))
313 ''},permit_sasl_authenticated,reject}'' 336 ''},check_policy_service unix:/run/postfix-internal-policy.sock,permit_sasl_authenticated,reject}''
314 "-o" "smtpd_relay_restrictions=permit_sasl_authenticated,reject" 337 "-o" "smtpd_relay_restrictions=permit_sasl_authenticated,reject"
315 "-o" "{smtpd_data_restrictions = check_policy_service unix:/run/postfwd3/postfwd3.sock}" 338 "-o" "{smtpd_data_restrictions = check_policy_service unix:/run/postfwd3/postfwd3.sock}"
316 "-o" "unverified_sender_reject_code=550" 339 "-o" "unverified_sender_reject_code=550"
@@ -325,7 +348,10 @@ in {
325 maxproc = 0; 348 maxproc = 0;
326 args = [ 349 args = [
327 "-o" "header_checks=pcre:${pkgs.writeText "header_checks_submission" '' 350 "-o" "header_checks=pcre:${pkgs.writeText "header_checks_submission" ''
351 if /^Received: /
352 !/by surtr\.yggdrasil\.li/ STRIP
328 /^Received: from [^ ]+ \([^ ]+ [^ ]+\)\s+(.*)$/ REPLACE Received: $1 353 /^Received: from [^ ]+ \([^ ]+ [^ ]+\)\s+(.*)$/ REPLACE Received: $1
354 endif
329 ''}" 355 ''}"
330 ]; 356 ];
331 }; 357 };
@@ -364,6 +390,7 @@ in {
364 domains = [ "surtr.yggdrasil.li" ] ++ concatMap (domain: [".${domain}" domain]) emailDomains; 390 domains = [ "surtr.yggdrasil.li" ] ++ concatMap (domain: [".${domain}" domain]) emailDomains;
365 separator = "+"; 391 separator = "+";
366 extraConfig = '' 392 extraConfig = ''
393 socketmap = unix:/run/postsrsd/postsrsd-socketmap.sock
367 milter = unix:/run/postsrsd/postsrsd-milter.sock 394 milter = unix:/run/postsrsd/postsrsd-milter.sock
368 ''; 395 '';
369 }; 396 };
@@ -372,7 +399,7 @@ in {
372 enable = true; 399 enable = true;
373 user = "postfix"; group = "postfix"; 400 user = "postfix"; group = "postfix";
374 socket = "local:/run/opendkim/opendkim.sock"; 401 socket = "local:/run/opendkim/opendkim.sock";
375 domains = ''csl:${concatStringsSep "," (["surtr.yggdrasil.li"] ++ emailDomains)}''; 402 domains = ''csl:${concatStringsSep "," (["surtr.yggdrasil.li" "yggdrasil.li" "141.li" "kleen.li" "synapse.li" "praseodym.org"] ++ emailDomains)}'';
376 selector = "surtr"; 403 selector = "surtr";
377 configFile = builtins.toFile "opendkim.conf" '' 404 configFile = builtins.toFile "opendkim.conf" ''
378 Syslog true 405 Syslog true
@@ -476,7 +503,7 @@ in {
476 }; 503 };
477 }; 504 };
478 505
479 users.groups.${config.services.rspamd.group}.members = [ config.services.postfix.user "dovecot2" ]; 506 users.groups.${config.services.rspamd.group}.members = [ config.services.postfix.user config.services.dovecot2.user ];
480 507
481 services.redis.servers.rspamd.enable = true; 508 services.redis.servers.rspamd.enable = true;
482 509
@@ -486,8 +513,8 @@ in {
486 services.dovecot2 = { 513 services.dovecot2 = {
487 enable = true; 514 enable = true;
488 enablePAM = false; 515 enablePAM = false;
489 sslServerCert = "/run/credentials/dovecot2.service/surtr.yggdrasil.li.pem"; 516 sslServerCert = "/run/credentials/dovecot.service/surtr.yggdrasil.li.pem";
490 sslServerKey = "/run/credentials/dovecot2.service/surtr.yggdrasil.li.key.pem"; 517 sslServerKey = "/run/credentials/dovecot.service/surtr.yggdrasil.li.key.pem";
491 sslCACert = toString ./ca/ca.crt; 518 sslCACert = toString ./ca/ca.crt;
492 mailLocation = "maildir:/var/lib/mail/%u/maildir:UTF-8:INDEX=/var/lib/dovecot/indices/%u"; 519 mailLocation = "maildir:/var/lib/mail/%u/maildir:UTF-8:INDEX=/var/lib/dovecot/indices/%u";
493 mailPlugins.globally.enable = [ "fts" "fts_xapian" ]; 520 mailPlugins.globally.enable = [ "fts" "fts_xapian" ];
@@ -500,8 +527,8 @@ in {
500 dovecotSqlConf = pkgs.writeText "dovecot-sql.conf" '' 527 dovecotSqlConf = pkgs.writeText "dovecot-sql.conf" ''
501 driver = pgsql 528 driver = pgsql
502 connect = dbname=email 529 connect = dbname=email
503 password_query = SELECT (CASE WHEN '%k' = 'valid' AND '%m' = 'EXTERNAL' THEN NULL ELSE "password" END) as password, (CASE WHEN '%k' = 'valid' AND '%m' = 'EXTERNAL' THEN true WHEN password IS NULL THEN true ELSE NULL END) as nopassword, "user", quota_rule, 'dovecot2' as uid, 'dovecot2' as gid FROM imap_user WHERE "user" = '%n' 530 password_query = SELECT (CASE WHEN '%k' = 'valid' AND '%m' = 'EXTERNAL' THEN NULL ELSE "password" END) as password, (CASE WHEN '%k' = 'valid' AND '%m' = 'EXTERNAL' THEN true WHEN password IS NULL THEN true ELSE NULL END) as nopassword, "user", quota_rule, '${config.services.dovecot2.user}' as uid, '${config.services.dovecot2.group}' as gid FROM imap_user WHERE "user" = '%n'
504 user_query = SELECT "user", quota_rule, 'dovecot2' as uid, 'dovecot2' as gid FROM imap_user WHERE "user" = '%n' 531 user_query = SELECT "user", quota_rule, '${config.services.dovecot2.user}' as uid, 'dovecot2' as gid FROM imap_user WHERE "user" = '%n'
505 iterate_query = SELECT "user" FROM imap_user 532 iterate_query = SELECT "user" FROM imap_user
506 ''; 533 '';
507 in '' 534 in ''
@@ -509,16 +536,16 @@ in {
509 536
510 mail_plugins = $mail_plugins quota 537 mail_plugins = $mail_plugins quota
511 538
512 first_valid_uid = ${toString config.users.users.dovecot2.uid} 539 first_valid_uid = ${toString config.users.users.${config.services.dovecot2.user}.uid}
513 last_valid_uid = ${toString config.users.users.dovecot2.uid} 540 last_valid_uid = ${toString config.users.users.${config.services.dovecot2.user}.uid}
514 first_valid_gid = ${toString config.users.groups.dovecot2.gid} 541 first_valid_gid = ${toString config.users.groups.${config.services.dovecot2.group}.gid}
515 last_valid_gid = ${toString config.users.groups.dovecot2.gid} 542 last_valid_gid = ${toString config.users.groups.${config.services.dovecot2.group}.gid}
516 543
517 ${concatMapStringsSep "\n\n" (domain: 544 ${concatMapStringsSep "\n\n" (domain:
518 concatMapStringsSep "\n" (subdomain: '' 545 concatMapStringsSep "\n" (subdomain: ''
519 local_name ${subdomain} { 546 local_name ${subdomain} {
520 ssl_cert = </run/credentials/dovecot2.service/${subdomain}.pem 547 ssl_cert = </run/credentials/dovecot.service/${subdomain}.pem
521 ssl_key = </run/credentials/dovecot2.service/${subdomain}.key.pem 548 ssl_key = </run/credentials/dovecot.service/${subdomain}.key.pem
522 } 549 }
523 '') ["imap.${domain}" domain] 550 '') ["imap.${domain}" domain]
524 ) emailDomains} 551 ) emailDomains}
@@ -539,10 +566,10 @@ in {
539 auth_debug = yes 566 auth_debug = yes
540 567
541 service auth { 568 service auth {
542 user = dovecot2 569 user = ${config.services.dovecot2.user}
543 } 570 }
544 service auth-worker { 571 service auth-worker {
545 user = dovecot2 572 user = ${config.services.dovecot2.user}
546 } 573 }
547 574
548 userdb { 575 userdb {
@@ -563,7 +590,7 @@ in {
563 args = ${pkgs.writeText "dovecot-sql.conf" '' 590 args = ${pkgs.writeText "dovecot-sql.conf" ''
564 driver = pgsql 591 driver = pgsql
565 connect = dbname=email 592 connect = dbname=email
566 user_query = SELECT DISTINCT ON (extension IS NULL, local IS NULL) "user", quota_rule, 'dovecot2' as uid, 'dovecot2' as gid FROM lmtp_mapping WHERE CASE WHEN extension IS NOT NULL AND local IS NOT NULL THEN ('%n' :: citext) = local || '+' || extension AND domain = ('%d' :: citext) WHEN local IS NOT NULL THEN (local = ('%n' :: citext) OR ('%n' :: citext) ILIKE local || '+%%') AND domain = ('%d' :: citext) WHEN extension IS NOT NULL THEN ('%n' :: citext) ILIKE '%%+' || extension AND domain = ('%d' :: citext) ELSE domain = ('%d' :: citext) END ORDER BY (extension IS NULL) ASC, (local IS NULL) ASC 593 user_query = SELECT DISTINCT ON (extension IS NULL, local IS NULL) "user", quota_rule, '${config.services.dovecot2.user}' as uid, '${config.services.dovecot2.group}' as gid FROM lmtp_mapping WHERE CASE WHEN extension IS NOT NULL AND local IS NOT NULL THEN ('%n' :: citext) = local || '+' || extension AND domain = ('%d' :: citext) WHEN local IS NOT NULL THEN (local = ('%n' :: citext) OR ('%n' :: citext) ILIKE local || '+%%') AND domain = ('%d' :: citext) WHEN extension IS NOT NULL THEN ('%n' :: citext) ILIKE '%%+' || extension AND domain = ('%d' :: citext) ELSE domain = ('%d' :: citext) END ORDER BY (extension IS NULL) ASC, (local IS NULL) ASC
567 ''} 594 ''}
568 595
569 skip = never 596 skip = never
@@ -671,7 +698,7 @@ in {
671 plugin { 698 plugin {
672 plugin = fts fts_xapian 699 plugin = fts fts_xapian
673 fts = xapian 700 fts = xapian
674 fts_xapian = partial=2 full=20 attachments=1 verbose=1 701 fts_xapian = partial=3 full=20 attachments=1 verbose=1
675 702
676 fts_autoindex = yes 703 fts_autoindex = yes
677 704
@@ -686,12 +713,12 @@ in {
686 713
687 systemd.services.dovecot-fts-xapian-optimize = { 714 systemd.services.dovecot-fts-xapian-optimize = {
688 description = "Optimize dovecot indices for fts_xapian"; 715 description = "Optimize dovecot indices for fts_xapian";
689 requisite = [ "dovecot2.service" ]; 716 requisite = [ "dovecot.service" ];
690 after = [ "dovecot2.service" ]; 717 after = [ "dovecot.service" ];
691 startAt = "*-*-* 22:00:00 Europe/Berlin"; 718 startAt = "*-*-* 22:00:00 Europe/Berlin";
692 serviceConfig = { 719 serviceConfig = {
693 Type = "oneshot"; 720 Type = "oneshot";
694 ExecStart = "${pkgs.dovecot}/bin/doveadm fts optimize -A"; 721 ExecStart = "${getExe' pkgs.dovecot "doveadm"} fts optimize -A";
695 PrivateDevices = true; 722 PrivateDevices = true;
696 PrivateNetwork = true; 723 PrivateNetwork = true;
697 ProtectKernelTunables = true; 724 ProtectKernelTunables = true;
@@ -752,31 +779,29 @@ in {
752 779
753 security.acme.rfc2136Domains = { 780 security.acme.rfc2136Domains = {
754 "surtr.yggdrasil.li" = { 781 "surtr.yggdrasil.li" = {
755 restartUnits = [ "postfix.service" "dovecot2.service" ]; 782 restartUnits = [ "postfix.service" "dovecot.service" ];
756 }; 783 };
757 } // listToAttrs (map (domain: nameValuePair "spm.${domain}" { restartUnits = ["nginx.service"]; }) spmDomains) 784 } // listToAttrs (map (domain: nameValuePair "spm.${domain}" { restartUnits = ["nginx.service"]; }) spmDomains)
758 // listToAttrs (concatMap (domain: [ 785 // listToAttrs (concatMap (domain: [
759 (nameValuePair domain { restartUnits = ["postfix.service" "dovecot2.service"]; }) 786 (nameValuePair domain { restartUnits = ["postfix.service" "dovecot.service"]; })
760 (nameValuePair "mailin.${domain}" { restartUnits = ["postfix.service"]; }) 787 (nameValuePair "mailin.${domain}" { restartUnits = ["postfix.service"]; })
761 (nameValuePair "mailsub.${domain}" { restartUnits = ["postfix.service"]; }) 788 (nameValuePair "mailsub.${domain}" { restartUnits = ["postfix.service"]; })
762 (nameValuePair "imap.${domain}" { restartUnits = ["dovecot2.service"]; }) 789 (nameValuePair "imap.${domain}" { restartUnits = ["dovecot.service"]; })
763 (nameValuePair "mta-sts.${domain}" { restartUnits = ["nginx.service"]; }) 790 (nameValuePair "mta-sts.${domain}" { restartUnits = ["nginx.service"]; })
764 ]) emailDomains); 791 ]) emailDomains);
765 792
766 systemd.services.postfix = { 793 systemd.services.postfix = {
767 serviceConfig.LoadCredential = [ 794 serviceConfig.LoadCredential = let
768 "surtr.yggdrasil.li.key.pem:${config.security.acme.certs."surtr.yggdrasil.li".directory}/key.pem" 795 tlsCredential = domain: "${domain}.full.pem:${config.security.acme.certs.${domain}.directory}/full.pem";
769 "surtr.yggdrasil.li.pem:${config.security.acme.certs."surtr.yggdrasil.li".directory}/fullchain.pem" 796 in [
770 ] ++ concatMap (domain: 797 (tlsCredential "surtr.yggdrasil.li")
771 map (subdomain: "${subdomain}.full.pem:${config.security.acme.certs.${subdomain}.directory}/full.pem") 798 ] ++ concatMap (domain: map tlsCredential [domain "mailin.${domain}" "mailsub.${domain}"]) emailDomains;
772 [domain "mailin.${domain}" "mailsub.${domain}"]
773 ) emailDomains;
774 }; 799 };
775 800
776 systemd.services.dovecot2 = { 801 systemd.services.dovecot = {
777 preStart = '' 802 preStart = ''
778 for f in /etc/dovecot/sieve_flag.d/*.sieve /etc/dovecot/sieve_before.d/*.sieve; do 803 for f in /etc/dovecot/sieve_flag.d/*.sieve /etc/dovecot/sieve_before.d/*.sieve; do
779 ${pkgs.dovecot_pigeonhole}/bin/sievec $f 804 ${getExe' pkgs.dovecot_pigeonhole "sievec"} $f
780 done 805 done
781 ''; 806 '';
782 807
@@ -843,15 +868,16 @@ in {
843 charset utf-8; 868 charset utf-8;
844 source_charset utf-8; 869 source_charset utf-8;
845 ''; 870 '';
846 root = pkgs.runCommand "mta-sts.${domain}" {} '' 871 root = pkgs.writeTextFile {
847 mkdir -p $out/.well-known 872 name = "mta-sts.${domain}";
848 cp ${pkgs.writeText "mta-sts.${domain}.txt" '' 873 destination = "/.well-known/mta-sts.txt";
874 text = ''
849 version: STSv1 875 version: STSv1
850 mode: enforce 876 mode: enforce
851 max_age: 2419200 877 max_age: 2419200
852 mx: mailin.${domain} 878 mx: mailin.${domain}
853 ''} $out/.well-known/mta-sts.txt 879 '';
854 ''; 880 };
855 }; 881 };
856 }) emailDomains); 882 }) emailDomains);
857 }; 883 };
@@ -868,7 +894,7 @@ in {
868 systemd.services.spm = { 894 systemd.services.spm = {
869 serviceConfig = { 895 serviceConfig = {
870 Type = "notify"; 896 Type = "notify";
871 ExecStart = "${pkgs.spm}/bin/spm-server"; 897 ExecStart = getExe' pkgs.spm "spm-server";
872 User = "spm"; 898 User = "spm";
873 Group = "spm"; 899 Group = "spm";
874 900
@@ -926,7 +952,7 @@ in {
926 serviceConfig = { 952 serviceConfig = {
927 Type = "notify"; 953 Type = "notify";
928 954
929 ExecStart = "${ccert-policy-server}/bin/ccert-policy-server"; 955 ExecStart = getExe' ccert-policy-server "ccert-policy-server";
930 956
931 Environment = [ 957 Environment = [
932 "PGDATABASE=email" 958 "PGDATABASE=email"
@@ -959,6 +985,53 @@ in {
959 }; 985 };
960 users.groups."postfix-ccert-sender-policy" = {}; 986 users.groups."postfix-ccert-sender-policy" = {};
961 987
988 systemd.sockets."postfix-internal-policy" = {
989 requiredBy = ["postfix.service"];
990 wants = ["postfix-internal-policy.service"];
991 socketConfig = {
992 ListenStream = "/run/postfix-internal-policy.sock";
993 };
994 };
995 systemd.services."postfix-internal-policy" = {
996 after = [ "postgresql.service" ];
997 bindsTo = [ "postgresql.service" ];
998
999 serviceConfig = {
1000 Type = "notify";
1001
1002 ExecStart = lib.getExe internal-policy-server;
1003
1004 Environment = [
1005 "PGDATABASE=email"
1006 ];
1007
1008 DynamicUser = false;
1009 User = "postfix-internal-policy";
1010 Group = "postfix-internal-policy";
1011 ProtectSystem = "strict";
1012 SystemCallFilter = "@system-service";
1013 NoNewPrivileges = true;
1014 ProtectKernelTunables = true;
1015 ProtectKernelModules = true;
1016 ProtectKernelLogs = true;
1017 ProtectControlGroups = true;
1018 MemoryDenyWriteExecute = true;
1019 RestrictSUIDSGID = true;
1020 KeyringMode = "private";
1021 ProtectClock = true;
1022 RestrictRealtime = true;
1023 PrivateDevices = true;
1024 PrivateTmp = true;
1025 ProtectHostname = true;
1026 ReadWritePaths = ["/run/postgresql"];
1027 };
1028 };
1029 users.users."postfix-internal-policy" = {
1030 isSystemUser = true;
1031 group = "postfix-internal-policy";
1032 };
1033 users.groups."postfix-internal-policy" = {};
1034
962 services.postfwd = { 1035 services.postfwd = {
963 enable = true; 1036 enable = true;
964 cache = false; 1037 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/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/postgresql/default.nix b/hosts/surtr/postgresql/default.nix
index 059f4088..3786ea7c 100644
--- a/hosts/surtr/postgresql/default.nix
+++ b/hosts/surtr/postgresql/default.nix
@@ -280,6 +280,64 @@ in {
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; 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;
281 281
282 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;
283 ''} 341 ''}
284 342
285 psql etebase postgres -eXf ${pkgs.writeText "etebase.sql" '' 343 psql etebase postgres -eXf ${pkgs.writeText "etebase.sql" ''
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/vidhar/default.nix b/hosts/vidhar/default.nix
index c9470ee9..547572c6 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 ./paperless ./hledger ./audiobookshelf 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
@@ -136,7 +136,7 @@ with lib;
136 wantedBy = ["basic.target"]; 136 wantedBy = ["basic.target"];
137 serviceConfig = { 137 serviceConfig = {
138 ExecStart = pkgs.writeShellScript "limit-pstate-start" '' 138 ExecStart = pkgs.writeShellScript "limit-pstate-start" ''
139 echo 50 > /sys/devices/system/cpu/intel_pstate/max_perf_pct 139 echo 40 > /sys/devices/system/cpu/intel_pstate/max_perf_pct
140 ''; 140 '';
141 RemainAfterExit = true; 141 RemainAfterExit = true;
142 ExecStop = pkgs.writeShellScript "limit-pstate-stop" '' 142 ExecStop = pkgs.writeShellScript "limit-pstate-stop" ''
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/ruleset.nft b/hosts/vidhar/network/ruleset.nft
index 6b0ac9fc..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
@@ -95,6 +96,7 @@ table inet filter {
95 counter paperless-rx {} 96 counter paperless-rx {}
96 counter hledger-rx {} 97 counter hledger-rx {}
97 counter audiobookshelf-rx {} 98 counter audiobookshelf-rx {}
99 counter kimai-rx {}
98 100
99 counter established-rx {} 101 counter established-rx {}
100 102
@@ -127,6 +129,7 @@ table inet filter {
127 counter paperless-tx {} 129 counter paperless-tx {}
128 counter hledger-tx {} 130 counter hledger-tx {}
129 counter audiobookshelf-tx {} 131 counter audiobookshelf-tx {}
132 counter kimai-tx {}
130 133
131 counter tx {} 134 counter tx {}
132 135
@@ -150,8 +153,13 @@ table inet filter {
150 153
151 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
152 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
153 157
154 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
155 163
156 164
157 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
@@ -266,7 +274,7 @@ table inet filter {
266 274
267table inet nat { 275table inet nat {
268 counter gpon-nat {} 276 counter gpon-nat {}
269 # counter container-nat {} 277 counter kimai-nat {}
270 278
271 chain postrouting { 279 chain postrouting {
272 type nat hook postrouting priority srcnat 280 type nat hook postrouting priority srcnat
@@ -274,7 +282,7 @@ table inet nat {
274 282
275 283
276 meta nfproto ipv4 oifname gpon counter name gpon-nat masquerade 284 meta nfproto ipv4 oifname gpon counter name gpon-nat masquerade
277 # iifname ve-* oifname gpon counter name container-nat masquerade 285 iifname ve-kimai oifname gpon counter name kimai-nat masquerade
278 } 286 }
279} 287}
280 288
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 = {