ctucx.git: nixfiles

ctucx' nixfiles

commit 7226c379cce4e199b809b3abe238490d57663ee8
parent f73bde980edd51a5068a1cc0bea0d133d7996469
Author: Leah (ctucx) <git@ctu.cx>
Date: Sat, 26 Nov 2022 21:32:54 +0100

machines/osterei/mail: move to machine `trabbi`
15 files changed, 556 insertions(+), 528 deletions(-)
M
machines/osterei/configuration.nix
|
4
----
D
machines/osterei/maddy.nix
|
355
-------------------------------------------------------------------------------
D
machines/osterei/mail.nix
|
129
-------------------------------------------------------------------------------
M
machines/trabbi/configuration.nix
|
1
+
A
machines/trabbi/maddy.nix
|
359
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A
machines/trabbi/mail.nix
|
158
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
D
secrets/osterei/mail/password-leah-ctu.cx.age
|
11
-----------
D
secrets/osterei/mail/password-mail-zug.network.age
|
11
-----------
D
secrets/osterei/restic/maddy.age
|
0
D
secrets/osterei/restic/mail.age
|
11
-----------
M
secrets/secrets.nix
|
12
+++++-------
A
secrets/trabbi/mail/password-hi-f2k1.de.age
|
12
++++++++++++
A
secrets/trabbi/mail/password-leah-ctu.cx.age
|
11
+++++++++++
A
secrets/trabbi/mail/password-mail-zug.network.age
|
10
++++++++++
A
secrets/trabbi/restic/mail.age
|
0
diff --git a/machines/osterei/configuration.nix b/machines/osterei/configuration.nix
@@ -13,10 +13,6 @@
 
     # monitoring
     ../../configurations/linux/services/prometheus-node-exporter.nix
-
-    # communication
-    ./mail.nix
-
   ];
 
   services.pcscd.enable                             = lib.mkForce false;
diff --git a/machines/osterei/maddy.nix b/machines/osterei/maddy.nix
@@ -1,355 +0,0 @@
-{ inputs, config, lib, pkgs, ... }:
-
-let
-  mailboxFilterScript = pkgs.writePythonScriptBin "mailbox-filter.py" (ps: [ ps.toml ps.mail-parser ]) ''
-    from email.header import Header, decode_header, make_header
-    import sys, re
-    import toml, mailparser
-
-    def filter_mail(config, sender, recipient, subject):
-      for type in [ 'recipient', 'subject', 'sender' ]:
-        if type not in config:
-          continue
-
-        for key, value in config[type].items():
-          if(re.search("^" + key + "$", str(eval(type)))):
-            print(value.replace(",", "\n"))
-            sys.exit(0)
-
-    try:
-      account_name = sys.argv[1]
-      config       = toml.load('/etc/maddy/filters/mailbox/' + account_name + '.toml')
-
-      if len(sys.argv) > 2:
-        filter_mail(config, sys.argv[2], sys.argv[3], make_header(decode_header(sys.argv[4])))
-      else:
-        sender = subject = ""
-        message          = mailparser.parse_from_string(sys.stdin.read())
-
-        if len(message.from_) > 0:
-          if len(message.from_[0]) == 2:
-            sender = message.from_[0][1]
-
-        if message.subject is not None:
-          subject = message.subject
-
-        for recipient in message.to:
-          filter_mail(config, sender, recipient[1], subject)
-
-    except:
-      pass
-
-    sys.exit(0)
-  '';
-
-  receiveFilterScript = pkgs.writePythonScriptBin "receive-filter.py" (ps: [ ps.toml ]) ''
-    import sys, toml
-
-    try:
-      sender    = sys.argv[1]
-      recipient = sys.argv[2]
-      config    = toml.load('/etc/maddy/filters/receive.toml')
-
-      for type in [ 'recipient', 'sender' ]:
-        if type not in config:
-          continue
-
-        if 'reject' in config[type]:
-          if eval(type) in config[type]['reject']:
-            sys.exit(10)
-
-        if 'quarantine' in config[type]:
-          if eval(type) in config[type]['quarantine']:
-            sys.exit(20)
-
-    except SystemExit as e:
-      sys.exit(e)
-
-    except:
-      pass
-
-    sys.exit(0)
-  '';
-
-in {
-
-  environment.etc."maddy/filters/mailbox/leah@ctu.cx.toml".text = "${inputs.nix-std.lib.serde.toTOML inputs.local-secrets.maddy.mailboxFilter}";
-  environment.etc."maddy/filters/receive.toml".text             = "${inputs.nix-std.lib.serde.toTOML inputs.local-secrets.maddy.receiveFilter}";
-
-  security.acme.certs."osterei.ctu.cx".reloadServices           = [ "maddy.service" ];
-
-  systemd.services.maddy.serviceConfig.ReadOnlyPaths            = [ "/etc/maddy/filters" ];
-
-  age.secrets.restic-maddy.file                                 = ../../secrets/osterei/restic/maddy.age;
-
-  networking.firewall.allowedTCPPorts                           = [ 25 143 465 587 993 ];
-
-  dns.zones = with pkgs.dns.lib.combinators; let
-    TXT   = [ "v=spf1 a mx ip4:185.232.70.80 +ip6:2a03:4000:4e:af1::1 ~all" ];
-    DMARC = "v=DMARC1; p=none";
-    MX    = with mx; [ (mx 10 "osterei.ctu.cx.") ];
-   in {
-    "ctu.cx" = {
-      inherit MX TXT;
-
-      SRV = [
-        { proto = "tcp"; service = "imaps"; priority = 0; weight = 1; port = 993; target = "osterei.ctu.cx."; }
-        { proto = "tcp"; service = "imap"; priority = 0; weight = 1; port = 143; target = "osterei.ctu.cx."; }
-        { proto = "tcp"; service = "submission"; priority = 0; weight = 1; port = 587; target = "osterei.ctu.cx."; }
-      ];
-
-      subdomains = {
-        _dmarc.TXT               = [ DMARC ];
-        "default._domainkey".TXT = [ "v=DKIM1; k=ed25519; p=nWRKCHE19fL1RHJ2cVkC8Xvfzm9OtgeF5VC2lD+EaEo=" ];
-      };
-    };
-
-    "ctucx.de" = {
-      inherit MX TXT;
-
-      subdomains = {
-        _dmarc.TXT               = [ DMARC ];
-        "default._domainkey".TXT = [ "v=DKIM1; k=ed25519; p=U9JMZlv7BpLXGIpO7WdJ/7ephxwJtJ02jaVUUadyP9s" ];
-      };
-    };
-
-    "thein.ovh" = {
-      inherit MX TXT;
-
-      subdomains = {
-        _dmarc.TXT               = [ DMARC ];
-        "default._domainkey".TXT = [ "v=DKIM1; k=ed25519; p=KYkebiXYSc/+7Rtdz/ZZFRAXAsQnyLPYA6r2uboh5oc=" ];
-      };
-    };
-
-    "zug.network" = {
-      inherit MX TXT;
-
-      subdomains = {
-        _dmarc.TXT               = [ DMARC ];
-        "default._domainkey".TXT = [ "v=DKIM1; k=ed25519; p=dH1h2nvPlmT0lIrGddpYRZFTm0AD6D+fsU36McEso2g=" ];
-      };
-    };
-  };
-
-  users.groups.maddy = {};
-  users.users.maddy = {
-    isSystemUser = true;
-    home         = "/var/lib/maddy";
-    group        = "maddy";
-    extraGroups  = [ "nginx" ];
-  };
-
-  restic-backups.maddy = {
-    user         = "maddy";
-    passwordFile = config.age.secrets.restic-maddy.path;
-    paths        = [ "/var/lib/maddy" ];
-  };
-
-  services.maddy = {
-    enable        = true;
-    user          = "maddy";
-    group         = "maddy";
-    hostname      = "osterei.ctu.cx";
-    primaryDomain = "ctu.cx";
-    localDomains  = [
-      "$(hostname)"
-      "$(primary_domain)"
-      "thein.ovh"
-      "ctucx.de"
-      "trans-agenda.de"
-      "zug.network"
-    ];
-
-    config = ''
-      #debug on
-      log syslog
-      
-      tls file /var/lib/acme/osterei.ctu.cx/fullchain.pem /var/lib/acme/osterei.ctu.cx/key.pem
-      
-      # ----------------------------------------------------------------------------
-      # Local storage & authentication
-      
-      # pass_table provides local hashed passwords storage for authentication of
-      # users. It can be configured to use any "table" module, in default
-      # configuration a table in SQLite DB is used.
-      # Table can be replaced to use e.g. a file for passwords. Or pass_table module
-      # can be replaced altogether to use some external source of credentials (e.g.
-      # PAM, /etc/shadow file).
-      #
-      # If table module supports it (sql_table does) - credentials can be managed
-      # using 'maddyctl creds' command.
-      
-      auth.pass_table local_authdb {
-          table sql_table {
-              driver sqlite3
-              dsn credentials.db
-              table_name passwords
-          }
-      }
-      
-      # imapsql module stores all indexes and metadata necessary for IMAP using a
-      # relational database. It is used by IMAP endpoint for mailbox access and
-      # also by SMTP & Submission endpoints for delivery of local messages.
-      #
-      # IMAP accounts, mailboxes and all message metadata can be inspected using
-      # imap-* subcommands of maddyctl utility.
-      
-      storage.imapsql local_mailboxes {
-          driver sqlite3
-          dsn imapsql.db
-          disable_recent false
-          compression zstd
-
-          imap_filter {
-              command ${mailboxFilterScript} {account_name}
-          }
-      }
-      
-      # ----------------------------------------------------------------------------
-      # SMTP endpoints + message routing
-
-      msgpipeline local_routing {
-          dmarc yes
-          check {
-              require_mx_record
-              dkim
-              spf {
-                  permerr_action ignore
-              }
-              command ${receiveFilterScript} {sender} {rcpts} {
-                  run_on rcpt
-
-                  code 1 ignore
-                  code 2 ignore
-                  code 10 reject
-                  code 20 quarantine
-              }
-          }
-      
-          destination postmaster $(local_domains) {
-              modify {
-                  replace_rcpt static {
-                     entry postmaster             postmaster@$(primary_domain)
-                     entry leon@thein.ovh         leah@ctu.cx
-                     entry aufsicht@zug.network   mail@zug.network
-                     entry verwaltung@zug.network mail@zug.network
-                  }
-      
-                  # Implement plus-address notation.
-                  replace_rcpt regexp "(.+)\+(.+)@(.+)" "$1@$3"
-      
-                  replace_rcpt regexp "(.+)@ctucx.de" "leah@ctu.cx"
-                  replace_rcpt regexp "(.+)@ctu.cx"   "leah@ctu.cx"
-              }
-      
-              deliver_to &local_mailboxes
-          }
-      
-          default_destination {
-              reject 550 5.1.1 "User doesn't exist"
-          }
-      }
-      
-      smtp tcp://0.0.0.0:25 {
-          limits {
-              # Up to 20 msgs/sec across max. 10 SMTP connections.
-              all rate 20 1s
-              all concurrency 10
-          }
-      
-          source $(local_domains) {
-              reject 501 5.1.8 "Use Submission for outgoing SMTP"
-          }
-      
-          default_source {
-              destination postmaster $(local_domains) {
-                  deliver_to &local_routing
-              }
-      
-              default_destination {
-                  reject 550 5.1.1 "User doesn't exist"
-              }
-          }
-      }
-      
-      submission tls://0.0.0.0:465 tcp://0.0.0.0:587 {
-          limits {
-              # Up to 50 msgs/sec across any amount of SMTP connections.
-              all rate 50 1s
-          }
-      
-          auth &local_authdb
-      
-          source $(local_domains) {
-              destination postmaster $(local_domains) {
-                  deliver_to &local_routing
-              }
-      
-              default_destination {
-                  modify {
-                      dkim $(primary_domain) $(local_domains) default {
-                          newkey_algo ed25519
-                      }
-                  }
-                  deliver_to &remote_queue
-              }
-          }
-      
-          default_source {
-              reject 501 5.1.8 "Non-local sender domain"
-          }
-      }
-      
-      target.remote outbound_delivery {
-          limits {
-              # Up to 20 msgs/sec across max. 10 SMTP connections
-              # for each recipient domain.
-              destination rate 20 1s
-              destination concurrency 10
-          }
-      
-          mx_auth {
-              dane
-      
-              mtasts {
-                  cache fs
-                  fs_dir mtasts_cache/
-              }
-      
-              local_policy {
-                  min_tls_level encrypted
-                  min_mx_level none
-              }
-          }
-      }
-      
-      target.queue remote_queue {
-          target &outbound_delivery
-          max_parallelism 16
-          max_tries 4
-      
-          autogenerated_msg_domain $(primary_domain)
-      
-          bounce {
-              destination postmaster $(local_domains) {
-                  deliver_to &local_routing
-              }
-      
-              default_destination {
-                  reject 550 5.0.0 "Refusing to send DSNs to non-local addresses"
-              }
-          }
-      }
-      
-      # ----------------------------------------------------------------------------
-      # IMAP endpoints
-      
-      imap tls://0.0.0.0:993 tcp://0.0.0.0:143 {
-          auth &local_authdb
-          storage &local_mailboxes
-      }
-    '';
-  };
-
-}
diff --git a/machines/osterei/mail.nix b/machines/osterei/mail.nix
@@ -1,129 +0,0 @@
-{ inputs, pkgs, config, ... }:
-
-{
-
-  imports = [
-    inputs.simple-nixos-mailserver.nixosModule
-  ];
-
-  age.secrets.restic-mail.file              = ../../secrets/osterei/restic/mail.age;
-  age.secrets.mail-password-leah.file       = ../../secrets/osterei/mail/password-leah-ctu.cx.age;
-  age.secrets.mail-password-zugnetwork.file = ../../secrets/osterei/mail/password-mail-zug.network.age;
-
-  dns.zones = with pkgs.dns.lib.combinators; let
-    TXT   = [ "v=spf1 a mx ip4:185.232.70.80 +ip6:2a03:4000:4e:af1::1 ~all" ];
-    DMARC = "v=DMARC1; p=none";
-    MX    = with mx; [ (mx 10 "osterei.ctu.cx.") ];
-   in {
-    "ctu.cx" = {
-      inherit MX TXT;
-
-      SRV = [
-        { proto = "tcp"; service = "imaps"; priority = 0; weight = 1; port = 993; target = "osterei.ctu.cx."; }
-        { proto = "tcp"; service = "imap"; priority = 0; weight = 1; port = 143; target = "osterei.ctu.cx."; }
-        { proto = "tcp"; service = "submission"; priority = 0; weight = 1; port = 587; target = "osterei.ctu.cx."; }
-      ];
-
-      subdomains = {
-        _dmarc.TXT               = [ DMARC ];
-        "mail._domainkey".TXT    = [ "v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDKryfX99NkcU5Xe4AmG+kO/sfuYSXk5RqJhzxS4uMqERE8UszgEGdteXcD8pqON2MfDmA3G6cA+Oa+N4tIWdIYNwTISVXXMGdHvjFIsVUEW0turM104tXESELaPRntkCvDBk/yOgsBDRZQHSx5MdGwpzeRC8TLdCbalh3W0jp5PQIDAQA" ];
-      };
-    };
-
-    "ctucx.de" = {
-      inherit MX TXT;
-
-      subdomains = {
-        _dmarc.TXT               = [ DMARC ];
-        "mail._domainkey".TXT    = [ "v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC5fu690bKYCZLPAFfQQK+nl+aAmtetaWBKCWzGj6pt7HjpFjystgtgnQ6+DZLFXWUp8GRfMEycySB5kQULtYtSMUmx0gQBnTTLsRj+e55/CYUllLV6YXb5uca7LuVhlWPpH3sCr6TvC2VFWe4t0UC3uIXhYPrCm6p8OE7g+TdHHwIDAQAB" ];
-      };
-    };
-
-    "thein.ovh" = {
-      inherit MX TXT;
-
-      subdomains = {
-        _dmarc.TXT               = [ DMARC ];
-        "mail._domainkey".TXT    = [ "v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC8oumqNkHboF/S4dnKue+hEC3V226ToMmL/fmXqbAhsW88m+jUuLgZE8Nl7kc/lzD9yY7JmCXcWFzoLJWE8xusfmT1yMOW9sQmee7g0tHsm1fVqFMUetmC4+QuqAdvjIGU5QndjdWHP/gssIoLPT7lCNUL4/lkaPmFiiDyvaMpkQIDAQAB" ];
-      };
-    };
-
-    "zug.network" = {
-      inherit MX TXT;
-
-      subdomains = {
-        _dmarc.TXT               = [ DMARC ];
-        "mail._domainkey".TXT    = [ "v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCl+VU9Bx/MAxyiYttChdXIlvEMUT5jY4k7n/g5d2ISHBqsH6is8fttOZlBv+N8vEQ1pD+DKNdFKqb2ZXfXPwln4rL5FbzN6O0BRXuATxRpI5WA1Fi3N+us17I8b0YaGK+Se5eQKcqpxZ6x8Ao2f7cRCW7xa6aqGtyDwexsmuw+4QIDAQAB" ];
-      };
-    };
-  };
-
-  services.nginx = {
-    enable = true;
-    virtualHosts."${config.networking.fqdn}" = {
-      enableACME = true;
-      forceSSL   = true;
-    };
-  };
-
-  mailserver = {
-    enable  = true;
-    fqdn    = config.networking.fqdn;
-
-    openFirewall        = true;
-    localDnsResolver    = false;
-    virusScanning       = false;
-
-    certificateScheme   = 1;
-    certificateFile     = "${config.security.acme.certs.${config.networking.fqdn}.directory}/fullchain.pem";
-    keyFile             = "${config.security.acme.certs.${config.networking.fqdn}.directory}/key.pem";
-
-    enableManageSieve   = true;
-    enableSubmission    = true;
-    enableSubmissionSsl = true;
-    enableImap          = true;
-    enableImapSsl       = true;
-    enablePop3          = false;
-    enablePop3Ssl       = false;
-
-    sieveDirectory      = "/var/lib/sieve";
-
-    domains = [
-      "ctu.cx"
-      "ctucx.de"
-      "thein.ovh"
-      "zug.network"
-    ];
-
-    forwards = {
-      "mail@zug.network" = "isabelle.kleinheuer@gmail.com";
-    };
-
-    loginAccounts = {
-      "leah@ctu.cx" = {
-        hashedPasswordFile = config.age.secrets.mail-password-leah.path;
-        aliases = [
-          "@ctu.cx"
-          "@ctucx.de"
-          "leah@thein.ovh"
-          "leon@thein.ovh"
-        ];
-      };
-
-      "mail@zug.network" = {
-        hashedPasswordFile = config.age.secrets.mail-password-zugnetwork.path;      	
-      };
-    };
-  };
-
-  restic-backups.mail = {
-    passwordFile = config.age.secrets.restic-mail.path;
-    paths        = [
-      "/var/vmail"
-      "/var/lib/sieve"
-    ];
-  };
-
-  networking.firewall.allowedTCPPorts = [ 80 443 ];
-
-}
diff --git a/machines/trabbi/configuration.nix b/machines/trabbi/configuration.nix
@@ -19,6 +19,7 @@
     # communication
     ./matrix-synapse.nix
     ./pleroma
+    ./mail.nix
 
     # websites
     ./websites
diff --git a/machines/trabbi/maddy.nix b/machines/trabbi/maddy.nix
@@ -0,0 +1,359 @@
+{ inputs, config, lib, pkgs, ... }:
+
+let
+  mailboxFilterScript = pkgs.writePythonScriptBin "mailbox-filter.py" (ps: [ ps.toml ps.mail-parser ]) ''
+    from email.header import Header, decode_header, make_header
+    import sys, re
+    import toml, mailparser
+
+    def filter_mail(config, sender, recipient, subject):
+      for type in [ 'recipient', 'subject', 'sender' ]:
+        if type not in config:
+          continue
+
+        for key, value in config[type].items():
+          if(re.search("^" + key + "$", str(eval(type)))):
+            print(value.replace(",", "\n"))
+            sys.exit(0)
+
+    try:
+      account_name = sys.argv[1]
+      config       = toml.load('/etc/maddy/filters/mailbox/' + account_name + '.toml')
+
+      if len(sys.argv) > 2:
+        filter_mail(config, sys.argv[2], sys.argv[3], make_header(decode_header(sys.argv[4])))
+      else:
+        sender = subject = ""
+        message          = mailparser.parse_from_string(sys.stdin.read())
+
+        if len(message.from_) > 0:
+          if len(message.from_[0]) == 2:
+            sender = message.from_[0][1]
+
+        if message.subject is not None:
+          subject = message.subject
+
+        for recipient in message.to:
+          filter_mail(config, sender, recipient[1], subject)
+
+    except:
+      pass
+
+    sys.exit(0)
+  '';
+
+  receiveFilterScript = pkgs.writePythonScriptBin "receive-filter.py" (ps: [ ps.toml ]) ''
+    import sys, toml
+
+    try:
+      sender    = sys.argv[1]
+      recipient = sys.argv[2]
+      config    = toml.load('/etc/maddy/filters/receive.toml')
+
+      for type in [ 'recipient', 'sender' ]:
+        if type not in config:
+          continue
+
+        if 'reject' in config[type]:
+          if eval(type) in config[type]['reject']:
+            sys.exit(10)
+
+        if 'quarantine' in config[type]:
+          if eval(type) in config[type]['quarantine']:
+            sys.exit(20)
+
+    except SystemExit as e:
+      sys.exit(e)
+
+    except:
+      pass
+
+    sys.exit(0)
+  '';
+
+in {
+
+  environment.etc."maddy/filters/mailbox/leah@ctu.cx.toml".text = "${inputs.nix-std.lib.serde.toTOML inputs.local-secrets.maddy.mailboxFilter}";
+  environment.etc."maddy/filters/receive.toml".text             = "${inputs.nix-std.lib.serde.toTOML inputs.local-secrets.maddy.receiveFilter}";
+
+  security.acme.certs."${config.networking.fqdn}".reloadServices           = [ "maddy.service" ];
+
+  systemd.services.maddy.serviceConfig.ReadOnlyPaths            = [ "/etc/maddy/filters" ];
+  systemd.services.maddy-restarter = {
+  	script  = "${pkgs.systemd}/bin/systemctl restart maddy.service";
+  	startAt = "0/12:00:00";
+  };
+
+  age.secrets.restic-maddy.file                                 = ../../secrets/trabbi/restic/maddy.age;
+
+  networking.firewall.allowedTCPPorts                           = [ 25 143 465 587 993 ];
+
+  dns.zones = with pkgs.dns.lib.combinators; let
+    TXT   = [ "v=spf1 a mx ip4:185.232.70.80 +ip6:2a03:4000:4e:af1::1 ~all" ];
+    DMARC = "v=DMARC1; p=none";
+    MX    = with mx; [ (mx 10 "${config.networking.fqdn}.") ];
+   in {
+    "ctu.cx" = {
+      inherit MX TXT;
+
+      SRV = [
+        { proto = "tcp"; service = "imaps"; priority = 0; weight = 1; port = 993; target = "${config.networking.fqdn}."; }
+        { proto = "tcp"; service = "imap"; priority = 0; weight = 1; port = 143; target = "${config.networking.fqdn}."; }
+        { proto = "tcp"; service = "submission"; priority = 0; weight = 1; port = 587; target = "${config.networking.fqdn}."; }
+      ];
+
+      subdomains = {
+        _dmarc.TXT               = [ DMARC ];
+        "default._domainkey".TXT = [ "v=DKIM1; k=ed25519; p=nWRKCHE19fL1RHJ2cVkC8Xvfzm9OtgeF5VC2lD+EaEo=" ];
+      };
+    };
+
+    "ctucx.de" = {
+      inherit MX TXT;
+
+      subdomains = {
+        _dmarc.TXT               = [ DMARC ];
+        "default._domainkey".TXT = [ "v=DKIM1; k=ed25519; p=U9JMZlv7BpLXGIpO7WdJ/7ephxwJtJ02jaVUUadyP9s" ];
+      };
+    };
+
+    "thein.ovh" = {
+      inherit MX TXT;
+
+      subdomains = {
+        _dmarc.TXT               = [ DMARC ];
+        "default._domainkey".TXT = [ "v=DKIM1; k=ed25519; p=KYkebiXYSc/+7Rtdz/ZZFRAXAsQnyLPYA6r2uboh5oc=" ];
+      };
+    };
+
+    "zug.network" = {
+      inherit MX TXT;
+
+      subdomains = {
+        _dmarc.TXT               = [ DMARC ];
+        "default._domainkey".TXT = [ "v=DKIM1; k=ed25519; p=dH1h2nvPlmT0lIrGddpYRZFTm0AD6D+fsU36McEso2g=" ];
+      };
+    };
+  };
+
+  users.groups.maddy = {};
+  users.users.maddy = {
+    isSystemUser = true;
+    home         = "/var/lib/maddy";
+    group        = "maddy";
+    extraGroups  = [ "nginx" ];
+  };
+
+  restic-backups.maddy = {
+    user         = "maddy";
+    passwordFile = config.age.secrets.restic-maddy.path;
+    paths        = [ "/var/lib/maddy" ];
+  };
+
+  services.maddy = {
+    enable        = true;
+    user          = "maddy";
+    group         = "maddy";
+    hostname      = config.networking.fqdn;
+    primaryDomain = "ctu.cx";
+    localDomains  = [
+      "$(hostname)"
+      "$(primary_domain)"
+      "thein.ovh"
+      "ctucx.de"
+      "trans-agenda.de"
+      "zug.network"
+    ];
+
+    config = ''
+      #debug on
+      log syslog
+      
+      tls file /var/lib/acme/${config.networking.fqdn}/fullchain.pem /var/lib/acme/${config.networking.fqdn}/key.pem
+      
+      # ----------------------------------------------------------------------------
+      # Local storage & authentication
+      
+      # pass_table provides local hashed passwords storage for authentication of
+      # users. It can be configured to use any "table" module, in default
+      # configuration a table in SQLite DB is used.
+      # Table can be replaced to use e.g. a file for passwords. Or pass_table module
+      # can be replaced altogether to use some external source of credentials (e.g.
+      # PAM, /etc/shadow file).
+      #
+      # If table module supports it (sql_table does) - credentials can be managed
+      # using 'maddyctl creds' command.
+      
+      auth.pass_table local_authdb {
+          table sql_table {
+              driver sqlite3
+              dsn credentials.db
+              table_name passwords
+          }
+      }
+      
+      # imapsql module stores all indexes and metadata necessary for IMAP using a
+      # relational database. It is used by IMAP endpoint for mailbox access and
+      # also by SMTP & Submission endpoints for delivery of local messages.
+      #
+      # IMAP accounts, mailboxes and all message metadata can be inspected using
+      # imap-* subcommands of maddyctl utility.
+      
+      storage.imapsql local_mailboxes {
+          driver sqlite3
+          dsn imapsql.db
+          disable_recent false
+          compression zstd
+
+          imap_filter {
+              command ${mailboxFilterScript} {account_name}
+          }
+      }
+      
+      # ----------------------------------------------------------------------------
+      # SMTP endpoints + message routing
+
+      msgpipeline local_routing {
+          dmarc yes
+          check {
+              require_mx_record
+              dkim
+              spf {
+                  permerr_action ignore
+              }
+              command ${receiveFilterScript} {sender} {rcpts} {
+                  run_on rcpt
+
+                  code 1 ignore
+                  code 2 ignore
+                  code 10 reject
+                  code 20 quarantine
+              }
+          }
+      
+          destination postmaster $(local_domains) {
+              modify {
+                  replace_rcpt static {
+                     entry postmaster             postmaster@$(primary_domain)
+                     entry leon@thein.ovh         leah@ctu.cx
+                     entry aufsicht@zug.network   mail@zug.network
+                     entry verwaltung@zug.network mail@zug.network
+                  }
+      
+                  # Implement plus-address notation.
+                  replace_rcpt regexp "(.+)\+(.+)@(.+)" "$1@$3"
+      
+                  replace_rcpt regexp "(.+)@ctucx.de" "leah@ctu.cx"
+                  replace_rcpt regexp "(.+)@ctu.cx"   "leah@ctu.cx"
+              }
+      
+              deliver_to &local_mailboxes
+          }
+      
+          default_destination {
+              reject 550 5.1.1 "User doesn't exist"
+          }
+      }
+      
+      smtp tcp://0.0.0.0:25 {
+          limits {
+              # Up to 20 msgs/sec across max. 10 SMTP connections.
+              all rate 20 1s
+              all concurrency 10
+          }
+      
+          source $(local_domains) {
+              reject 501 5.1.8 "Use Submission for outgoing SMTP"
+          }
+      
+          default_source {
+              destination postmaster $(local_domains) {
+                  deliver_to &local_routing
+              }
+      
+              default_destination {
+                  reject 550 5.1.1 "User doesn't exist"
+              }
+          }
+      }
+      
+      submission tls://0.0.0.0:465 tcp://0.0.0.0:587 {
+          limits {
+              # Up to 50 msgs/sec across any amount of SMTP connections.
+              all rate 50 1s
+          }
+      
+          auth &local_authdb
+      
+          source $(local_domains) {
+              destination postmaster $(local_domains) {
+                  deliver_to &local_routing
+              }
+      
+              default_destination {
+                  modify {
+                      dkim $(primary_domain) $(local_domains) default {
+                          newkey_algo ed25519
+                      }
+                  }
+                  deliver_to &remote_queue
+              }
+          }
+      
+          default_source {
+              reject 501 5.1.8 "Non-local sender domain"
+          }
+      }
+      
+      target.remote outbound_delivery {
+          limits {
+              # Up to 20 msgs/sec across max. 10 SMTP connections
+              # for each recipient domain.
+              destination rate 20 1s
+              destination concurrency 10
+          }
+      
+          mx_auth {
+              dane
+      
+              mtasts {
+                  cache fs
+                  fs_dir mtasts_cache/
+              }
+      
+              local_policy {
+                  min_tls_level encrypted
+                  min_mx_level none
+              }
+          }
+      }
+      
+      target.queue remote_queue {
+          target &outbound_delivery
+          max_parallelism 16
+          max_tries 4
+      
+          autogenerated_msg_domain $(primary_domain)
+      
+          bounce {
+              destination postmaster $(local_domains) {
+                  deliver_to &local_routing
+              }
+      
+              default_destination {
+                  reject 550 5.0.0 "Refusing to send DSNs to non-local addresses"
+              }
+          }
+      }
+      
+      # ----------------------------------------------------------------------------
+      # IMAP endpoints
+      
+      imap tls://0.0.0.0:993 tcp://0.0.0.0:143 {
+          auth &local_authdb
+          storage &local_mailboxes
+      }
+    '';
+  };
+
+}
diff --git a/machines/trabbi/mail.nix b/machines/trabbi/mail.nix
@@ -0,0 +1,158 @@
+{ inputs, pkgs, config, ... }:
+
+{
+
+  imports = [
+    inputs.simple-nixos-mailserver.nixosModule
+  ];
+
+  age.secrets.restic-mail.file              = ../../secrets/trabbi/restic/mail.age;
+  age.secrets.mail-password-leah.file       = ../../secrets/trabbi/mail/password-leah-ctu.cx.age;
+  age.secrets.mail-password-f2k1de.file     = ../../secrets/trabbi/mail/password-hi-f2k1.de.age;
+  age.secrets.mail-password-zugnetwork.file = ../../secrets/trabbi/mail/password-mail-zug.network.age;
+
+  dns.zones = with pkgs.dns.lib.combinators; let
+    TXT   = [ "v=spf1 a mx ip4:89.58.62.171 +ip6:2a0a:4cc0:1:2d7::1 ~all" ];
+    DMARC = "v=DMARC1; p=none";
+    MX    = with mx; [ (mx 10 "${config.networking.fqdn}.") ];
+   in {
+    "ctu.cx" = {
+      inherit MX TXT;
+
+      SRV = [
+        { proto = "tcp"; service = "imaps"; priority = 0; weight = 1; port = 993; target = "${config.networking.fqdn}."; }
+        { proto = "tcp"; service = "imap"; priority = 0; weight = 1; port = 143; target = "${config.networking.fqdn}."; }
+        { proto = "tcp"; service = "submission"; priority = 0; weight = 1; port = 587; target = "${config.networking.fqdn}."; }
+      ];
+
+      subdomains = {
+        _dmarc.TXT               = [ DMARC ];
+        "mail._domainkey".TXT    = [ "v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDKryfX99NkcU5Xe4AmG+kO/sfuYSXk5RqJhzxS4uMqERE8UszgEGdteXcD8pqON2MfDmA3G6cA+Oa+N4tIWdIYNwTISVXXMGdHvjFIsVUEW0turM104tXESELaPRntkCvDBk/yOgsBDRZQHSx5MdGwpzeRC8TLdCbalh3W0jp5PQIDAQAB" ];
+      };
+    };
+
+    "ctucx.de" = {
+      inherit MX TXT;
+
+      subdomains = {
+        _dmarc.TXT               = [ DMARC ];
+        "mail._domainkey".TXT    = [ "v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC5fu690bKYCZLPAFfQQK+nl+aAmtetaWBKCWzGj6pt7HjpFjystgtgnQ6+DZLFXWUp8GRfMEycySB5kQULtYtSMUmx0gQBnTTLsRj+e55/CYUllLV6YXb5uca7LuVhlWPpH3sCr6TvC2VFWe4t0UC3uIXhYPrCm6p8OE7g+TdHHwIDAQAB" ];
+      };
+    };
+
+    "thein.ovh" = {
+      inherit MX TXT;
+
+      subdomains = {
+        _dmarc.TXT               = [ DMARC ];
+        "mail._domainkey".TXT    = [ "v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC8oumqNkHboF/S4dnKue+hEC3V226ToMmL/fmXqbAhsW88m+jUuLgZE8Nl7kc/lzD9yY7JmCXcWFzoLJWE8xusfmT1yMOW9sQmee7g0tHsm1fVqFMUetmC4+QuqAdvjIGU5QndjdWHP/gssIoLPT7lCNUL4/lkaPmFiiDyvaMpkQIDAQAB" ];
+      };
+    };
+
+    "zug.network" = {
+      inherit MX TXT;
+
+      subdomains = {
+        _dmarc.TXT               = [ DMARC ];
+        "mail._domainkey".TXT    = [ "v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCl+VU9Bx/MAxyiYttChdXIlvEMUT5jY4k7n/g5d2ISHBqsH6is8fttOZlBv+N8vEQ1pD+DKNdFKqb2ZXfXPwln4rL5FbzN6O0BRXuATxRpI5WA1Fi3N+us17I8b0YaGK+Se5eQKcqpxZ6x8Ao2f7cRCW7xa6aqGtyDwexsmuw+4QIDAQAB" ];
+      };
+    };
+
+    "flauschehorn.sexy" = {
+      inherit MX TXT;
+
+      subdomains = {
+        _dmarc.TXT               = [ DMARC ];
+        "mail._domainkey".TXT    = [ "v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCvEPR8068KtlsiWiexSPWqagKmd07ggGvDcYICzOvhxVB0MDrn+/VYIXEbVX0Y9z60oT1ynjkhFjDWEofk11EoXwrg7xjkqZuszDrhdYqUnoLrzlugmnK4jXO3cAD0qeblX0rDmu30cmPP1Aj21tLTU6loYpORY+y4VaVfwtHswwIDAQAB" ];
+      };
+    };
+  };
+
+  services.nginx = {
+    enable = true;
+    virtualHosts."${config.networking.fqdn}" = {
+      enableACME = true;
+      forceSSL   = true;
+    };
+  };
+
+  mailserver = {
+    enable  = true;
+    fqdn    = config.networking.fqdn;
+
+    openFirewall        = true;
+    localDnsResolver    = false;
+    virusScanning       = false;
+
+    certificateScheme   = 1;
+    certificateFile     = "${config.security.acme.certs.${config.networking.fqdn}.directory}/fullchain.pem";
+    keyFile             = "${config.security.acme.certs.${config.networking.fqdn}.directory}/key.pem";
+
+    enableManageSieve   = true;
+    enableSubmission    = true;
+    enableSubmissionSsl = true;
+    enableImap          = true;
+    enableImapSsl       = true;
+    enablePop3          = false;
+    enablePop3Ssl       = false;
+
+    sieveDirectory      = "/var/lib/sieve";
+
+    domains = [
+      "ctu.cx"
+      "ctucx.de"
+      "thein.ovh"
+      "zug.network"
+      "flauschehorn.sexy"
+      "f2k1.de"
+    ];
+
+    forwards = let 
+      f2k1deGMail = "isabelle.kleinheuer@gmail.com";
+    in {
+      "mail@zug.network"        = f2k1deGMail;
+      "@f2k1.de"                = f2k1deGMail;
+      "luna@flauschehorn.sexy"  = f2k1deGMail;
+      "jules@flauschehorn.sexy" = f2k1deGMail;
+    };
+
+    loginAccounts = {
+      "leah@ctu.cx" = {
+        hashedPasswordFile = config.age.secrets.mail-password-leah.path;
+        aliases = [
+          "@ctu.cx"
+          "@ctucx.de"
+          "leah@thein.ovh"
+          "leon@thein.ovh"
+        ];
+      };
+
+      "hi@f2k1.de" = {
+        hashedPasswordFile = config.age.secrets.mail-password-f2k1de.path;      	
+        aliases = [
+          "@f2k1.de"
+          "luna@flauschehorn.sexy"
+          "jules@flauschehorn.sexy"
+		];
+      };
+
+      "mail@zug.network" = {
+        hashedPasswordFile = config.age.secrets.mail-password-zugnetwork.path;      	
+        aliases = [
+          "@zug.network"
+		];
+      };
+    };
+  };
+
+  restic-backups.mail = {
+    passwordFile = config.age.secrets.restic-mail.path;
+    paths        = [
+      "/var/vmail"
+      "/var/lib/sieve"
+    ];
+  };
+
+  networking.firewall.allowedTCPPorts = [ 80 443 ];
+
+}
diff --git a/secrets/osterei/mail/password-leah-ctu.cx.age b/secrets/osterei/mail/password-leah-ctu.cx.age
@@ -1,10 +0,0 @@
-age-encryption.org/v1
--> X25519 FO3OdOxS0r3BjdAbaW6bG9/PY2XnpMUJFykZ4EwRnEU
-52gk0WMZ7kWiLNWe8cc5D05xmwIY3r8H6M7+fBxv7V0
--> ssh-ed25519 YtLkIw CDq8sMvxkTQw1mL+LSit0qzM4mfcXiAByFwIFjY2YA4
-tW2j6Ndh+oMeKXeVVT6BMj52xI1qT+/lBph+yKFrScA
--> ':6:46c-grease 4tr\-3+( ?XW8
-Dk1wNczSfkEnyo+/ZkoS6Q13+rqc+4toHisQJio
---- cMuExgPkB0lVkq1NrV1e7TVEX8R8Fm0HJspvC9UqhJc
-e
->q4Nzѹ`;O|ZDU߻}fZ2`G#{
`D=沙*(X>+}ww-
\ No newline at end of file
diff --git a/secrets/osterei/mail/password-mail-zug.network.age b/secrets/osterei/mail/password-mail-zug.network.age
@@ -1,10 +0,0 @@
-age-encryption.org/v1
--> X25519 jM6TcdXhxBQwwuCFoRbluBB3zpKF3eKcACCNzGogcgk
-B5i51+0LZET7TMhCLV9ieBbmAplobRZo7rroXwGfnFc
--> ssh-ed25519 YtLkIw OUm9LIlbU2K5uh1EB2mT03qj8SA6XV0P9SuvKD3rsVg
-cXsO9X06aOkJvEEhivG/3VrBRnuOu6hcPiKZ+0prGgQ
--> N-grease 83( Dzlt %Y%P4Nfr @x\9Q`
-dicuHfdWYbh6DWWbZ4jKdEIRj18phFbhlJF5IhvwIMRy2Dtc0qTFrT0Y2+ApADKr
-bw3dsKu9BDbvVbMBgU/DPE5EpgGQkG9Zd+roZvfUCxAVncZx7AEDqQyvDJdq
---- HHF5yKveFP0SkQhMnTtioGLsZH3OnqobqSFNGxGBUXE
-0q׎?rILtw)CJO	;w~=g1h,|c<=;~2+-1״JQ|$<	O7]0j-
\ No newline at end of file
diff --git a/secrets/osterei/restic/maddy.age b/secrets/osterei/restic/maddy.age  Binary files differ.
diff --git a/secrets/osterei/restic/mail.age b/secrets/osterei/restic/mail.age
@@ -1,10 +0,0 @@
-age-encryption.org/v1
--> X25519 cCSDbJ7zu5HtOlzpRywyHXgwA4eiSU8xNe03E8PZGA8
-BIgMg2l0Q2Nu33DSQWjwNCG7z3yjzanykRnetTpKEQA
--> ssh-ed25519 YtLkIw zxiLHeUfKpYtJ87W0zTKpLVqPNDDkU0WxZa5HY42eDo
-SZ0zRqtMlPoUTavjV1L22chFLpua4B9g0JcU0Z3Rdc8
--> 3q_#-grease 7{nBL L:c($" LqM1l
-Wj8i8sG2iXUyzZQ1+LT2bQNTRKljscxo7Vezs6zV5XnYBc8zqffzg2erM1bQ
---- 32iEWmy2KzWLbiBKYJgbVKCovbQTi4xEkPWoZE6Qp9c
-w =3	
-.Tz8!yx>r\ONov;'_QfFOޜl>-
\ No newline at end of file
diff --git a/secrets/secrets.nix b/secrets/secrets.nix
@@ -58,13 +58,6 @@ in {
   "desastro/restic/syncthing-wiki.age".publicKeys             = [ leah desastro ];
 
 
-  "osterei/mail/password-leah-ctu.cx.age".publicKeys          = [ leah osterei ];
-  "osterei/mail/password-mail-zug.network.age".publicKeys     = [ leah osterei ];
-
-  "osterei/restic/maddy.age".publicKeys                       = [ leah osterei ];
-  "osterei/restic/mail.age".publicKeys                        = [ leah osterei ];
-
-
   "taurus/syncthing/key.age".publicKeys                       = [ leah taurus ];
   "taurus/syncthing/cert.age".publicKeys                      = [ leah taurus ];
 

@@ -78,5 +71,10 @@ in {
   "trabbi/restic/gitolite.age".publicKeys                     = [ leah trabbi ];
   "trabbi/restic/pleroma.age".publicKeys                      = [ leah trabbi ];
   "trabbi/restic/matrix-synapse.age".publicKeys               = [ leah trabbi ];
+  "trabbi/restic/mail.age".publicKeys                         = [ leah trabbi ];
+
+  "trabbi/mail/password-leah-ctu.cx.age".publicKeys           = [ leah trabbi ];
+  "trabbi/mail/password-mail-zug.network.age".publicKeys      = [ leah trabbi ];
+  "trabbi/mail/password-hi-f2k1.de.age".publicKeys            = [ leah trabbi ];
 
 }
diff --git a/secrets/trabbi/mail/password-hi-f2k1.de.age b/secrets/trabbi/mail/password-hi-f2k1.de.age
@@ -0,0 +1,11 @@
+age-encryption.org/v1
+-> X25519 XZyoo4FCVLWBeQ00RbAle7isGO1fgbtIF/qnPKVJ7j4
+546fJ+wXlF9W7U/VBZ7urlCUrdHN10ioS2u6+g4G5JI
+-> ssh-ed25519 V0uUrw qNC3uzdKvLFzB9A1qjcQzuoCHzhUaGnSTAZ/60e4jDc
+xDWaORFCcNd+B5mQRPTmUQiHCO4D/Qf9WgSO6noBz+k
+-> '%+UVl-grease
+dSJbcG6mfL/F6S1Fzkp2YjVTnz5p95E95jAcxptL6SHASxDav5PRnEEVpFD7fe0D
+NosjPkVNJU1fvNT0HYy1JXE4na4GonqWgJt+qkfVJrs
+--- iPqoX9v3g8XC3WJApj291Ap7pYht9JPihfV0E00cMcw
+V{>z"ZP^&1O<7-ېT|3Ih*xzyX)^ݥ
+Z,@΀kP!ꆒ;SW%Z+
\ No newline at end of file
diff --git a/secrets/trabbi/mail/password-leah-ctu.cx.age b/secrets/trabbi/mail/password-leah-ctu.cx.age
@@ -0,0 +1,10 @@
+age-encryption.org/v1
+-> X25519 J3xl715kbassDnB/897gOqLocKxih1vyjzNCTiRrpk8
+orFBsIZ4tFcsei1fMM8aPqt3CDU4CFEL4T/Gh0ucweQ
+-> ssh-ed25519 V0uUrw QO8ZMvxHBKBC9/8+ZtHPiAego1rNsaKGJdbw2y/lqAg
+snRBumNgdh8PctXtUd8ULIH8pSSQJO3Agm3+LBzHRU4
+-> K$rfa3-grease
+9mGxzw+MqSpztgSwDx8KlA8
+--- IR3n4OUmXMCO7GE8f5Ude9pO9/6d3+40q1edasUYXCA
+{=!\[h}?b"NM<LjIJLg
+fCmK8Lj'	,1?h5+
\ No newline at end of file
diff --git a/secrets/trabbi/mail/password-mail-zug.network.age b/secrets/trabbi/mail/password-mail-zug.network.age
@@ -0,0 +1,9 @@
+age-encryption.org/v1
+-> X25519 R5XejExMhGQ682LEZgyraz5miW7WWmsArHEcOmdrvnQ
+DQ7BBzkumyhIU1rBptLolj2+MaRoKqBDXdBCtwCtees
+-> ssh-ed25519 V0uUrw 9MoCNlpVZJwiQCDT8SFfnSB2WcfozOzQcU2cno6Z5QI
+K5jWpjTrlED/uMx82K99oeJqbF4+uOZ62m7/q+iRIrM
+-> bB-U$gk-grease *l] t< {>wnp ^4XjF
+5s6vCwc5ZVv5YrpbYwnb
+--- vW/3ki8vaPkGmcnjU101qJNF3+wfQkUj7wYO5zsxLzE
+sdnǟ<4R[,ޓ~{j$z}l@iPvkWė	\&{LB=>lpdK95+
\ No newline at end of file
diff --git a/secrets/trabbi/restic/mail.age b/secrets/trabbi/restic/mail.age  Binary files differ.