diff --git a/common/options.nix b/common/options.nix
index 252192ea..1713fdf8 100644
--- a/common/options.nix
+++ b/common/options.nix
@@ -7,8 +7,8 @@
       type = lib.types.nullOr lib.types.str;
     };
 
-    skipVhosts = lib.mkOption {
-      description = "Skip compiling the list of vhosts. Useful for development boxes";
+    skipCustomVhosts = lib.mkOption {
+      description = "Skip all vhosts that are not based on the TLD. Useful for development boxes";
       default = false;
       defaultText = "False (compile the vhosts)";
       type = lib.types.nullOr lib.types.bool;
@@ -20,5 +20,17 @@
       defaultText = "Null (this is a master)";
       type = lib.types.nullOr lib.types.str;
     };
+
+    smtpBindAddress = lib.mkOption {
+      description = "Address that Postfix expects to send and receive mail on";
+      default = "192.168.0.158";
+      type = lib.types.str;
+    };
+
+    smtpExternalAddress = lib.mkOption {
+      description = "The appropriate public IP forwarding port 587/993 for this mail host";
+      default = "136.206.15.3";
+      type = lib.types.str;
+    };
   };
 }
diff --git a/common/sysconfig.nix b/common/sysconfig.nix
index 5fde9858..2683afc1 100644
--- a/common/sysconfig.nix
+++ b/common/sysconfig.nix
@@ -3,7 +3,10 @@ let
   common = import ./variables.nix;
   tld = config.redbrick.tld;
 in {
-  imports = [ ./options.nix ];
+  imports = [
+    ./options.nix
+    ../packages/overlays
+  ];
 
   time.timeZone = "Europe/Dublin";
   i18n.defaultLocale = "en_IE.UTF-8";
@@ -57,6 +60,14 @@ in {
     ];
   };
 
+  # Enabled Spare cpu cycles to be used for folding@home
+  services.foldingathome = {
+    enable = true;
+    user = "redbrick";
+    team = 43166;
+    extraArgs = [ "--power" "light" ];
+  };
+
   # Enable LDAP
   users.ldap.enable = true;
   users.ldap.timeLimit = 2;
diff --git a/common/variables.nix b/common/variables.nix
index 644f2485..aa9a5dd4 100644
--- a/common/variables.nix
+++ b/common/variables.nix
@@ -13,11 +13,9 @@ rec {
     "djbdns.now.ie" = "djbdns.now.ie";
     "romana.now.ie" = "djbdns.now.ie";
     "www.luxgaa.lu" = "www.luxgaa.lu";
-    "www.iahpc.ie" = "www.iahpc.ie";
     "techweek.dcu.ie" = "techweek.dcu.ie";
     "games.dcu.ie" = "www.games.dcu.ie";
     "www.games.dcu.ie" = "www.games.dcu.ie";
-    "interlan.dcu.ie" = "interlan.dcu.ie";
   };
 
   userWebtree = uid: "${webtreeDir}/${builtins.substring 0 1 uid}/${uid}";
@@ -40,10 +38,6 @@ rec {
     sslServerCert = "${certsDir}/${domain}/fullchain.pem";
   };
 
-  dovecotHost = "192.168.0.135";
-  dovecotSaslPort = 3659;
-  dovecotLmtpPort = 24;
-
   # Hard coded otherwise NSCD will crash systems during boot if network is down
   # 50 = daedalus
   ldapHostIp = "192.168.0.50";
diff --git a/hosts/hardcase/configuration.nix b/hosts/hardcase/configuration.nix
index 0f101244..16d7f35c 100644
--- a/hosts/hardcase/configuration.nix
+++ b/hosts/hardcase/configuration.nix
@@ -12,6 +12,8 @@ in {
     ../../services/thelounge.nix
     ../../services/certs
     ../../services/httpd
+    ../../services/postfix
+    ../../services/dovecot
     ../../services/grafana
     ../../services/loki.nix
     ../../services/prometheus.nix
diff --git a/hosts/hardcase/hardware-configuration.nix b/hosts/hardcase/hardware-configuration.nix
index 02ef1bd4..96324b2f 100644
--- a/hosts/hardcase/hardware-configuration.nix
+++ b/hosts/hardcase/hardware-configuration.nix
@@ -29,6 +29,13 @@
   systemd.targets.nfs-client.requiredBy = [ "storage.mount" ];
   systemd.targets.nfs-client.before = [ "storage.mount" ];
 
+  fileSystems."/var/spool/mail" =
+    { device = "icarus.internal:/zbackup/mail";
+      fsType = "nfs";
+    };
+  systemd.services.dovecot2.requires = [ "var-spool-mail.mount" ];
+  systemd.services.dovecot2.after = [ "var-spool-mail.mount" ];
+
   # zfs create -o dedup=off -o mountpoint=legacy -o recordsize=4K  zroot/postgres
   fileSystems."/var/db/postgres" =
     { device = "zroot/postgres";
diff --git a/hosts/m1vm/configuration.nix b/hosts/m1vm/configuration.nix
index 918111f4..c8d63b07 100644
--- a/hosts/m1vm/configuration.nix
+++ b/hosts/m1vm/configuration.nix
@@ -9,7 +9,6 @@
     ../../services/postfix
     ../../services/dovecot
     ../../services/certs
-    ../../services/thelounge.nix
     ../../services/postgres.nix
   ];
 
@@ -17,7 +16,7 @@
   # compatible, in order to avoid breaking some software such as database
   # servers. You should change this only after NixOS release notes say you
   # should.
-  system.stateVersion = "19.09";
+  system.stateVersion = "20.09";
 
   # Use the GRUB 2 boot loader.
   boot.loader.grub.enable = true;
@@ -35,8 +34,10 @@
   };
 
   # Dev box, skip loading vhosts
-  redbrick.skipVhosts = true;
-  redbrick.tld = "redbricktest.ml";
+  redbrick.tld = "redbricktest.cf";
+  redbrick.skipCustomVhosts = true;
+  redbrick.smtpBindAddress = "192.168.0.135";
+  redbrick.smtpExternalAddress = "136.206.15.5";
 
   users.users.lucasade = {
     isNormalUser = true;
diff --git a/hosts/m1vm/hardware-configuration.nix b/hosts/m1vm/hardware-configuration.nix
index ef6d8db4..3b9ffdbb 100644
--- a/hosts/m1vm/hardware-configuration.nix
+++ b/hosts/m1vm/hardware-configuration.nix
@@ -24,6 +24,13 @@
   systemd.targets.nfs-client.requiredBy = [ "storage.mount" ];
   systemd.targets.nfs-client.before = [ "storage.mount" ];
 
+  fileSystems."/var/spool/mail" =
+    { device = "icarus.internal:/zbackup/mailtest";
+      fsType = "nfs";
+    };
+  systemd.services.dovecot2.requires = [ "var-spool-mail.mount" ];
+  systemd.services.dovecot2.after = [ "var-spool-mail.mount" ];
+
   swapDevices =
     [ { device = "/dev/disk/by-uuid/7ca217d6-538e-4919-a57f-c5cbaeb93832"; }
     ];
diff --git a/hosts/metharme/configuration.nix b/hosts/metharme/configuration.nix
new file mode 100644
index 00000000..c1137d79
--- /dev/null
+++ b/hosts/metharme/configuration.nix
@@ -0,0 +1,31 @@
+{ config, pkgs, ... }:
+let
+  variables = import ../../common/variables.nix;
+in {
+  imports = [
+    ./hardware-configuration.nix
+    ../../common/sysconfig.nix
+    ../../services/ssh.nix
+  ];
+
+  # This value determines the NixOS release with which your system is to be
+  # compatible, in order to avoid breaking some software such as database
+  # servers. You should change this only after NixOS release notes say you
+  # should.
+  system.stateVersion = "20.09";
+
+  # Use the GRUB 2 boot loader.
+  boot.loader.grub.enable = true;
+  boot.loader.grub.version = 2;
+  boot.loader.grub.devices = [ "/dev/sda" "/dev/sdb" ];
+
+  networking = {
+    hostName = "metharme";
+    hostId = "1f03060e";
+    defaultGateway = "192.168.0.254";
+    interfaces.eno2.ipv4.addresses = [{
+      address = "192.168.0.156";
+      prefixLength = 24;
+    }];
+  };
+}
diff --git a/hosts/metharme/hardware-configuration.nix b/hosts/metharme/hardware-configuration.nix
new file mode 100644
index 00000000..57cd2d22
--- /dev/null
+++ b/hosts/metharme/hardware-configuration.nix
@@ -0,0 +1,32 @@
+# Do not modify this file!  It was generated by ‘nixos-generate-config’
+# and may be overwritten by future invocations.  Please make changes
+# to /etc/nixos/configuration.nix instead.
+{ config, lib, pkgs, ... }:
+
+{
+  imports =
+    [ <nixpkgs/nixos/modules/installer/scan/not-detected.nix>
+    ];
+
+  boot.initrd.availableKernelModules = [ "uhci_hcd" "ehci_pci" "ata_piix" "mpt3sas" "usbhid" "sd_mod" "sr_mod" ];
+  boot.initrd.kernelModules = [ ];
+  boot.kernelModules = [ "kvm-intel" ];
+  boot.extraModulePackages = [ ];
+
+  fileSystems."/" =
+    { device = "zroot/nixos";
+      fsType = "zfs";
+    };
+
+  fileSystems."/nix" =
+    { device = "zroot/nixos/store";
+      fsType = "zfs";
+    };
+
+  swapDevices =
+    [ { device = "/dev/disk/by-uuid/b99bbf80-002b-4c10-9d72-96195bb64f4f"; priority = 100; }
+      { device = "/dev/disk/by-uuid/49a2deff-5fcc-4736-895b-130202648b59"; priority = 100; }
+    ];
+
+  nix.maxJobs = lib.mkDefault 16;
+}
diff --git a/hosts/motherlode/configuration.nix b/hosts/motherlode/configuration.nix
new file mode 100644
index 00000000..678b930d
--- /dev/null
+++ b/hosts/motherlode/configuration.nix
@@ -0,0 +1,28 @@
+{ config, pkgs, ... }:
+let
+  variables = import ../../common/variables.nix;
+in {
+  imports = [
+    ./hardware-configuration.nix
+    ../../common/sysconfig.nix
+    ../../services/ssh.nix
+    ../../services/libvirt.nix
+  ];
+
+  # This value determines the NixOS release with which your system is to be
+  # compatible, in order to avoid breaking some software such as database
+  # servers. You should change this only after NixOS release notes say you
+  # should.
+  system.stateVersion = "20.09";
+
+  # Use the GRUB 2 boot loader.
+  boot.loader.grub.enable = true;
+  boot.loader.grub.version = 2;
+  boot.loader.grub.device = "/dev/sda";
+
+  networking = {
+    hostName = "motherlode";
+    hostId = "fccc9415";
+    defaultGateway = "192.168.0.254";
+  } // (variables.bondConfig [ "eno1" "eno2" ] "192.168.0.130");
+}
diff --git a/hosts/motherlode/hardware-configuration.nix b/hosts/motherlode/hardware-configuration.nix
new file mode 100644
index 00000000..bb5d5509
--- /dev/null
+++ b/hosts/motherlode/hardware-configuration.nix
@@ -0,0 +1,22 @@
+{ config, lib, pkgs, ... }:
+
+{
+  imports =
+    [ <nixpkgs/nixos/modules/installer/scan/not-detected.nix>
+    ];
+
+  boot.initrd.availableKernelModules = [ "uhci_hcd" "ehci_pci" "ata_piix" "megaraid_sas" "usbhid" "sd_mod" "sr_mod" ];
+  boot.kernelModules = [ "kvm-intel" ];
+  boot.extraModulePackages = [ ];
+
+  fileSystems."/" =
+    { device = "/dev/disk/by-uuid/8c34aaec-4f61-4b29-a451-cd8e8d2bd394";
+      fsType = "ext4";
+    };
+
+  swapDevices =
+    [ { device = "/dev/disk/by-uuid/28c3f2fd-7b98-4bf6-a778-f73b8064c381"; }
+    ];
+
+  nix.maxJobs = lib.mkDefault 8;
+}
diff --git a/nixops.nix b/nixops.nix
new file mode 100644
index 00000000..37ed284d
--- /dev/null
+++ b/nixops.nix
@@ -0,0 +1,5 @@
+{
+  icarus = { config, pkgs, ... }: {
+    deployment.targetHost = "192.168.0.150";
+  };
+}
diff --git a/packages/overlays/default.nix b/packages/overlays/default.nix
new file mode 100644
index 00000000..597c1440
--- /dev/null
+++ b/packages/overlays/default.nix
@@ -0,0 +1,5 @@
+{
+  imports = [
+    ./rsyslog.nix
+  ];
+}
diff --git a/packages/overlays/rsyslog.nix b/packages/overlays/rsyslog.nix
new file mode 100644
index 00000000..76f68dd1
--- /dev/null
+++ b/packages/overlays/rsyslog.nix
@@ -0,0 +1,11 @@
+{
+  # libksi has an outdated dependency on openssl_1_0_2
+  # We don't need it, so remove it from rsyslog
+  nixpkgs.overlays = [
+    (self: super: {
+      rsyslog = super.rsyslog.override {
+        libksi = null;
+      };
+    })
+  ];
+}
diff --git a/services/dns/default.nix b/services/dns/default.nix
index d743fff7..7428e98a 100644
--- a/services/dns/default.nix
+++ b/services/dns/default.nix
@@ -35,13 +35,13 @@ in {
       {
         # Not using tld here becaue we actually want to configure
         # a specific domain
-        file = "${zonePath}/redbricktest.ml";
+        file = "${zonePath}/redbricktest.cf";
         master = true;
-        name = "redbricktest.ml";
+        name = "redbricktest.cf";
         extraConfig = "allow-update { key ${keyName}; };";
       }
       {
-        file = "${zonePath}/redbricktest.ml.rr";
+        file = "${zonePath}/redbricktest.cf.rr";
         master = true;
         name = "15.206.136.in-addr.arpa";
       }
diff --git a/services/dns/redbricktest.ml b/services/dns/redbricktest.ml
index d9a5ef4e..cee9ebfd 100644
--- a/services/dns/redbricktest.ml
+++ b/services/dns/redbricktest.ml
@@ -1,7 +1,7 @@
 $ORIGIN redbricktest.ml.
 $TTL 300
 @       IN      SOA     ns1.redbricktest.ml.     admins.redbricktest.ml. (
-                        2019060506      ; Serial
+                        2019060507      ; Serial
                         1M              ; Slave refresh interval
                         5M              ; Query retry interval
                         1H              ; Expiry
@@ -20,3 +20,4 @@ mail            IN      A       136.206.15.5
 
 www             IN      CNAME   server1
 wiki            IN      CNAME   server1
+lists           IN      CNAME   mail
diff --git a/services/dovecot/auth.nix b/services/dovecot/auth.nix
index ee8b7b21..ab7da526 100644
--- a/services/dovecot/auth.nix
+++ b/services/dovecot/auth.nix
@@ -1,11 +1,10 @@
-{common, pkgs, vmailUserName, ...}:
+{common, pkgs, tld, ...}:
 let
   bindCreds = import /var/secrets/dovecot_auth.nix;
 
   ldapConfig = pkgs.writeText "dovecot-ldap-config" ''
+    !include /var/secrets/dovecot_auth.conf
     hosts = ${common.ldapHost}
-    dn = ${bindCreds.dn}
-    dnpass = ${bindCreds.password}
     ldap_version = 3
     auth_bind = no
     base = ou=accounts,o=redbrick
@@ -15,6 +14,8 @@ let
     user_filter = (&(objectclass=posixAccount)(uid=%n))
     pass_attrs = uid=uid,homeDirectory=home,userPassword=password
     pass_filter = (&(objectclass=posixAccount)(uid=%n))
+    iterate_attrs = =user=%{ldap:uid}
+    iterate_filter = (objectClass=posixAccount)
     default_pass_scheme = CRYPT
   '';
 
@@ -24,9 +25,15 @@ in pkgs.writeText "dovecot-auth-config" ''
   auth_cache_ttl = 1 hour
   auth_cache_negative_ttl = 1 hour
 
+  # Set domain for login names without a domain specified
+  auth_default_realm = ${tld}
+
   # only use plain username/password auth - OK since everything is over TLS
   auth_mechanisms = plain
 
+  # Don't strip domain from username. Means that mail_location can reference %d
+  auth_username_format = %Lu
+
   # passdb specifies how users are authenticated - LDAP in my case
   passdb {
     driver = ldap
diff --git a/services/dovecot/default.nix b/services/dovecot/default.nix
index 2c6adee7..85dffc96 100644
--- a/services/dovecot/default.nix
+++ b/services/dovecot/default.nix
@@ -4,21 +4,54 @@ let
 
   common = import ../../common/variables.nix;
 
-  commonDovecot = import ./variables.nix;
+  sieveConfig = import ./sieve.nix { inherit pkgs; };
+  authConfig = import ./auth.nix { inherit common pkgs tld; };
+  masterConfig = import ./master.nix { inherit pkgs; };
+
+  # Fixed uid + gid so that the config can roam systems safely
+  vmailId = 975;
+
+  # Fix mtime comparison so that scripts can be precompiled
+  # See https://github.com/NixOS/nixpkgs/pull/35536/files
+  # and https://github.com/dovecot/pigeonhole/pull/4
+  pigeonhole = pkgs.dovecot_pigeonhole.overrideAttrs (old: {
+    patches = [
+      (pkgs.fetchpatch {
+        name = "binary-mtime.patch";
+        url = https://github.com/dovecot/pigeonhole/commit/3defbec146e195edad336a2c218f108462b0abd7.patch;
+        sha256 = "09mvdw8gjzq9s2l759dz4aj9man8q1akvllsq2j1xa2qmwjfxarp";
+      })
+    ];
+  });
 
-  vmailUserName = "vmail";
-
-  authConfig = import ./auth.nix { inherit common pkgs vmailUserName; };
-  masterConfig = import ./master.nix { inherit common pkgs; };
 in {
-  networking.firewall.allowedTCPPorts = [ 993 common.dovecotSaslPort common.dovecotLmtpPort ];
+  networking.firewall.allowedTCPPorts = [ 993 ];
 
   security.dhparams.enable = true;
   # Name found in https://github.com/NixOS/nixpkgs/blob/d7752fc0ebf9d49dc47c70ce4e674df024a82cfa/nixos/modules/services/mail/dovecot.nix#L26
   security.dhparams.params.dovecot2.bits = 2048;
 
+  # Create the user + group that will own /var/mail
+  # Uid not actually used. If the year is 2021 and this config is in production
+  # you can remove uid=uid from user_attrs in auth.nix and simplify the perms
+  # in /var/mail. Read the blog post for more info
+  users.users.vmail = {
+    description = "Owns mail written by dovecot2";
+    isSystemUser = true;
+    group = "vmail";
+    shell = "/dev/null";
+    home = "/dev/null";
+    uid = vmailId;
+  };
+  users.groups.vmail.gid = vmailId;
+
+  # Increase ulimit due to service_auth client_limit (2000)
+  systemd.services.dovecot2.serviceConfig.LimitNOFILE = 2500;
+
   services.dovecot2 = {
     enable = true;
+    modules = [ pigeonhole ];
+
     enableImap = true;
     enableLmtp = true;
     enablePAM = false;
@@ -30,9 +63,13 @@ in {
 
     # We don't want all members to be able to read other member's mail
     # Force a specific group
-    mailGroup = vmailUserName;
+    createMailUser = false;
+    mailUser = "vmail";
+    mailGroup = "vmail";
 
-    mailLocation = "mdbox:~/mdbox";
+    # ENSURE /var/mail IS CHMOD 3770
+    # See https://wiki.dovecot.org/SharedMailboxes/Permissions
+    mailLocation = "mdbox:/var/mail/%d/%n";
 
     mailboxes = [{
       name = "Junk";
@@ -49,13 +86,9 @@ in {
     }];
 
     extraConfig = ''
-      # to improve performance, disable fsync globally - we will enable it for
-      # some specific services later on
-      mail_fsync = never
-
-      auth_verbose = yes
-
-      mail_debug = yes
+      # Having trouble? Try enabling these
+      auth_verbose = no
+      mail_debug = no
 
       namespace inbox {
         separator = /
@@ -66,21 +99,26 @@ in {
         # max IMAP connections per IP address
         mail_max_userip_connections = 50
         # imap_sieve will be used for spam training by rspamd
-        mail_plugins = $mail_plugins # imap_sieve
+        mail_plugins = $mail_plugins imap_sieve
       }
 
       protocol lmtp {
         mail_fsync = optimized
-        mail_plugins = $mail_plugins
+        mail_plugins = $mail_plugins sieve
       }
 
       # require SSL for all non-localhost connections
       ssl = required
 
-      mail_home = /var/mail/%n
+      # Only the mail user should be authorized to write mail
+
+      mail_home = /var/mail/%d/%n
       mail_attachment_dir = /var/mail/attachments
       mail_attachment_min_size = 64k
 
+      # When copying perms from /var/mail, use this group
+      mail_access_groups = vmail
+
       # require modern crypto - taken from Mozilla's SSL recommendations page
       ssl_min_protocol = TLSv1.2
       ssl_cipher_list = ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256
@@ -94,23 +132,9 @@ in {
       !include ${masterConfig}
 
       # Enable sieve scripts
-      # protocols = $protocols sieve
-
-      # plugin {
-        # location of users' sieve directory and their "active" sieve script
-        # sieve = file:~/sieve;active=~/.dovecot.sieve
-
-        # directory of global sieve scripts to run before and after processing ALL
-        # incoming mail
-        # sieve_before = /usr/local/etc/dovecot/sieve-before.d
-        # sieve_after  = /usr/local/etc/dovecot/sieve-after.d
-
-        # make sieve aware of user+tag@domain.tld aliases
-        # recipient_delimiter = +
+      protocols = $protocols sieve
 
-        # maximum size of all user's sieve scripts
-        # sieve_quota_max_storage = 10M
-      # }
+      !include ${sieveConfig}
     '';
   };
 }
diff --git a/services/dovecot/master.nix b/services/dovecot/master.nix
index 5b562ea1..06fd7f72 100644
--- a/services/dovecot/master.nix
+++ b/services/dovecot/master.nix
@@ -1,9 +1,5 @@
-{common, pkgs, ...}:
+{pkgs, ...}:
 pkgs.writeText "dovecot-master-config" ''
-# to improve performance, disable fsync globally - we will enable it for
-# some specific services later on
-mail_fsync = never
-
 service imap-login {
   # plain-text IMAP should only be accessible from localhost
   inet_listener imap {
@@ -29,22 +25,31 @@ service pop3-login {
 
 # enable semi-long-lived IMAP processes to improve performance
 service imap {
-  service_count = 256
-  # set to the number of CPU cores on your server
-  process_min_avail = 3
+  # Service count must be 1 to prevent imap uid leaking and setuid issues
+  # Seen as "imap(foo)<5288><JWXxCE2ks4ZZE0NM>: Fatal: setuid(foo from userdb lookup) failed with euid=bar: Operation not permitted"
+  # See https://doc.dovecot.org/configuration_manual/service_configuration/
+  # Docs specify you should just do this if you have multiple UIDs
+  service_count = 1
+  process_limit = 2000
 }
 
 # Listen on LMTP port for postfix to deliver mail
 service lmtp {
-  inet_listener {
-    port = ${builtins.toString common.dovecotLmtpPort}
+  client_limit = 1
+  unix_listener /var/run/dovecot2_lmtp.sock {
+    user = postfix
+    group = postfix
+    mode = 0600
   }
 }
 
 # Listen on auth socket for postfix to authenticate users
 service auth {
-  inet_listener {
-    port = ${builtins.toString common.dovecotSaslPort}
+  client_limit = 2000
+  unix_listener /var/run/dovecot2_sasl.sock {
+    user = postfix
+    group = postfix
+    mode = 0600
   }
 }
 ''
diff --git a/services/dovecot/sieve.nix b/services/dovecot/sieve.nix
new file mode 100644
index 00000000..f21ac5e2
--- /dev/null
+++ b/services/dovecot/sieve.nix
@@ -0,0 +1,147 @@
+{pkgs}:
+with pkgs.stdenv;
+let
+
+  # These scripts are used by the imapsieve plugin to learn spam and ham
+  learnSpamScript = pkgs.writeShellScript "learn-spam.sh" ''
+    ${pkgs.rspamd}/bin/rspamc -h /run/rspamd/rspamd.sock learn_spam
+  '';
+
+  learnHamScript = pkgs.writeShellScript "learn-ham.sh" ''
+    ${pkgs.rspamd}/bin/rspamc -h /run/rspamd/rspamd.sock learn_ham
+  '';
+
+  sievePipeBinaries = mkDerivation {
+    name = "sieve-pipe-binaries";
+
+    phases = [ "copyPhase" "fixupPhase" ];
+
+    copyPhase = ''
+      mkdir -p $out/bin
+      cp -a ${learnHamScript} $out/bin/learn-ham.sh
+      cp -a ${learnSpamScript} $out/bin/learn-spam.sh
+    '';
+  };
+
+  # sieve-before script to put spam into Junk folder
+  spamFilter = pkgs.writeText "spam-filter.sieve" ''
+    require ["fileinto"];
+
+    if header :is "X-Spam" "Yes" {
+      fileinto "Junk";
+    }
+  '';
+
+  # imapsieve script to detect when a user marks an email as spam
+  reportSpamFilter = pkgs.writeText "report-spam.sieve" ''
+    require ["vnd.dovecot.pipe", "copy", "imapsieve", "environment", "variables"];
+
+    if environment :matches "imap.email" "*" {
+      set "email" "''${1}";
+    }
+
+    pipe :copy "learn-spam.sh" [ "''${email}" ];
+  '';
+
+  # imapsieve script to detect when a user moves an email out of spam
+  reportHamFilter = pkgs.writeText "report-ham.sieve" ''
+    require ["vnd.dovecot.pipe", "copy", "imapsieve", "environment", "variables"];
+
+    if environment :matches "imap.mailbox" "*" {
+      set "mailbox" "''${1}";
+    }
+
+    if string "''${mailbox}" "Trash" {
+      stop;
+    }
+
+    if environment :matches "imap.email" "*" {
+      set "email" "''${1}";
+    }
+
+    pipe :copy "learn-ham.sh" [ "''${email}" ];
+  '';
+
+  sieveSimpleConfig = ''
+    sieve_plugins = sieve_imapsieve sieve_extprograms
+    sieve_global_extensions = +vnd.dovecot.pipe
+    sieve_pipe_bin_dir = ${sievePipeBinaries}/bin
+  '';
+
+  sieveCompileConfig = pkgs.writeText "sieve-compile-config" ''
+    plugin {
+    ${sieveSimpleConfig}
+    }
+  '';
+
+  sieveScripts = mkDerivation {
+    name = "sieve-scripts";
+
+    buildInputs = [ pkgs.dovecot_pigeonhole ];
+
+    phases = [ "copyPhase" ];
+
+    copyPhase = ''
+      mkdir -p $out/{before,after,imap}
+      cd $out/before
+      cp ${spamFilter} spam-filter.sieve
+      sievec -c "${sieveCompileConfig}" spam-filter.sieve
+      cd $out/imap
+      cp ${reportSpamFilter} report-spam.sieve
+      cp ${reportHamFilter} report-ham.sieve
+      sievec -c "${sieveCompileConfig}" report-spam.sieve
+      sievec -c "${sieveCompileConfig}" report-ham.sieve
+    '';
+
+    meta = with lib; {
+      description = "Redbrick compiled sieve scripts for Dovecot";
+      platforms = platforms.linux;
+      maintainers = [ maintainers.m1cr0man ];
+    };
+  };
+
+in pkgs.writeText "dovecot-sieve-config" ''
+  service managesieve-login {
+    inet_listener sieve {
+      name = sieve
+      address = 127.0.0.1
+      port = 4190
+      ssl = yes
+    }
+  }
+
+  service managesieve {
+    # Blank line required syntactically
+  }
+
+  plugin {
+    ${sieveSimpleConfig}
+
+    # location of users' sieve directory and their "active" sieve script
+    sieve = file:~/sieve;active=~/.dovecot.sieve
+
+    # directory of global sieve scripts to run before and after processing ALL
+    # incoming mail
+    sieve_before = ${sieveScripts}/before
+    sieve_after  = ${sieveScripts}/after
+
+    # make sieve aware of user+tag@domain.tld aliases
+    recipient_delimiter = +
+
+    # maximum size of all user's sieve scripts
+    sieve_quota_max_storage = 2M
+
+    ## Spam and Ham learning ##
+
+    # From elsewhere to Junk folder
+    imapsieve_mailbox1_name = Junk
+    imapsieve_mailbox1_causes = COPY
+    imapsieve_mailbox1_before = file:${sieveScripts}/imap/report-spam.sieve
+
+    # From Junk folder to elsewhere
+    imapsieve_mailbox2_name = *
+    imapsieve_mailbox2_from = Junk
+    imapsieve_mailbox2_causes = COPY
+    imapsieve_mailbox2_before = file:${sieveScripts}/imap/report-ham.sieve
+  }
+''
diff --git a/services/dovecot/variables.nix b/services/dovecot/variables.nix
deleted file mode 100644
index b1d1dc80..00000000
--- a/services/dovecot/variables.nix
+++ /dev/null
@@ -1,3 +0,0 @@
-{
-    configPath = "/etc/dovecot.d";
-}
diff --git a/services/httpd/default.nix b/services/httpd/default.nix
index ebc8ecc8..2c8167ab 100644
--- a/services/httpd/default.nix
+++ b/services/httpd/default.nix
@@ -1,10 +1,8 @@
 { config, pkgs, lib, ... }:
+with (import ./shared.nix { tld = config.redbrick.tld; });
 let
-  tld = config.redbrick.tld;
-  common = import ../../common/variables.nix;
   vhosts = import ./vhosts.nix { inherit config; };
   errorPages = import ../../packages/httpd-error-pages { inherit pkgs; };
-  adminAddr = "webmaster@${tld}";
 
   # Define a base vhost for all TLDs. This will serve only ACME on port 80
   # Everything else is promoted to HTTPS
@@ -67,6 +65,7 @@ in {
     ./php-fpm.nix
     ./mediawiki.nix
     ./privatebin.nix
+    ./mailman.nix
   ];
 
   # Enable suexec support
@@ -120,7 +119,6 @@ in {
       </Directory>
 
       AddHandler cgi-script .cgi
-      AddHandler cgi-script .py
       AddHandler cgi-script .sh
       AddHandler server-parsed .shtml
       AddHandler server-parsed .html
diff --git a/services/httpd/mailman.nix b/services/httpd/mailman.nix
new file mode 100644
index 00000000..cd4e86db
--- /dev/null
+++ b/services/httpd/mailman.nix
@@ -0,0 +1,46 @@
+# Manual steps post-deploy:
+# cd /var/lib/mailman-web && sudo -u wwwrun mailman-web createsuperuser
+{ pkgs, lib, config, ... }:
+with (import ./shared.nix { tld = config.redbrick.tld; });
+let
+  webRoot = config.services.mailman.webRoot;
+  generatedDataPath = "/var/lib/mailman-web";
+
+  # Build mod_wsgi with python3
+  wsgiPkg = with pkgs; mod_wsgi.overrideAttrs (oldAttrs: {
+    buildInputs = [ apacheHttpd python3 ncurses ];
+  });
+
+  vhostConfig = {
+    adminAddr = "webmaster@${tld}";
+    serverAliases = [ "localmail.${tld}" ];
+    servedDirs = [ { dir = "${generatedDataPath}/static"; urlPath = "/static"; } ];
+    extraConfig = ''
+      <Location /accounts/signup>
+        Order allow,deny
+        Deny from all
+      </Location>
+      <Directory "${webRoot}">
+        Options ExecCGI
+        <Files wsgi.py>
+          Require all granted
+        </Files>
+        WSGIProcessGroup mailman
+      </Directory>
+      WSGIScriptAlias / ${webRoot}/mailman_web/wsgi.py
+    '';
+  };
+in {
+  services.httpd = {
+    extraModules = [ { name = "wsgi"; path = "${wsgiPkg}/modules/mod_wsgi.so"; } ];
+    extraConfig = ''
+      WSGISocketPrefix /run/httpd/wsgi
+      WSGIDaemonProcess mailman threads=4 home=${generatedDataPath} python-path=/etc/mailman3:${webRoot}:${
+        lib.makeSearchPath pkgs.python3.sitePackages
+          pkgs.python3Packages.mailman-web.requiredPythonModules
+      }
+    '';
+
+    virtualHosts."lists.${tld}" = vhostConfig // { onlySSL = true; } // (vhostCerts tld);
+  };
+}
diff --git a/services/httpd/shared.nix b/services/httpd/shared.nix
index 063f8696..2da08382 100644
--- a/services/httpd/shared.nix
+++ b/services/httpd/shared.nix
@@ -52,4 +52,9 @@ in {
       ProxyPassReverse / ${proxyAddress}/
     '';
   };
+
+  vhostCerts = domain: {
+    sslServerKey = "${common.certsDir}/${domain}/key.pem";
+    sslServerCert = "${common.certsDir}/${domain}/fullchain.pem";
+  };
 }
diff --git a/services/httpd/vhosts.nix b/services/httpd/vhosts.nix
index 8baecbb7..a6d81786 100644
--- a/services/httpd/vhosts.nix
+++ b/services/httpd/vhosts.nix
@@ -9,6 +9,7 @@ let
     "paste"
     "wiki"
     "cmtwiki"
+    "lists"
   ];
 
   # This is appended at the top
@@ -21,13 +22,11 @@ let
       group = user.gid;
     };
   }) (filter (user: !elem user.uid userBlacklist) users));
-in (if (config.redbrick.skipVhosts) then {} else userVhosts // {
-  "abovethefold.es" = vhost {
-    documentRoot = "${webtree}/r/receive/abovethefold";
-    user = "receive";
-    group = "staff";
-    wwwRedirect = true;
-    serverAliases = [ "www.abovethefold.es" ];
+in (userVhosts // {
+  "bash.${tld}" = vhost {
+    documentRoot = "${webtree}/y/yosarian/bash";
+    user = "yosarian";
+    group = "associat";
   };
   "blog.${tld}" = vhost {
     documentRoot = "${webtree}/vhosts/blog.redbrick.dcu.ie";
@@ -64,13 +63,6 @@ in (if (config.redbrick.skipVhosts) then {} else userVhosts // {
     user = "bugs";
     group = "redbrick";
   };
-  "butterflyexplosion.com" = vhost {
-    documentRoot = "${webtree}/c/carr";
-    user = "carr";
-    group = "associat";
-    wwwRedirect = true;
-    serverAliases = [ "www.butterflyexplosion.com" ];
-  };
   "ca2wiki.${tld}" = vhost {
     documentRoot = "${webtree}/s/sonic/wiki";
     user = "sonic";
@@ -86,11 +78,6 @@ in (if (config.redbrick.skipVhosts) then {} else userVhosts // {
     user = "sonic";
     group = "member";
   };
-  "ciankehoe.ie" = vhost {
-    documentRoot = "${webtree}/c/cianky/ciankehoe.ie/public";
-    user = "cianky";
-    group = "member";
-  };
   "colors.${tld}" = vhost {
     documentRoot = "${webtree}/vhosts/colors.redbrick.dcu.ie";
     user = "wwwrun";
@@ -101,18 +88,6 @@ in (if (config.redbrick.skipVhosts) then {} else userVhosts // {
     user = "chair";
     group = "committe";
   };
-  "dcudrama.ie" = vhost {
-    documentRoot = "${webtree}/d/drama";
-    user = "drama";
-    group = "society";
-    wwwRedirect = true;
-    serverAliases = [ "www.dcudrama.ie" ];
-  };
-  "djbdns.now.ie" = vhost {
-    documentRoot = "${webtree}/l/lecter/djbdns";
-    user = "lecter";
-    group = "associat";
-  };
   "forbidden.${tld}" = vhost {
     documentRoot = "${webtree}/vhosts/forbidden.redbrick.dcu.ie";
     user = "stolnart";
@@ -128,20 +103,6 @@ in (if (config.redbrick.skipVhosts) then {} else userVhosts // {
     user = "bunbun";
     group = "member";
   };
-  "grahambartley.com" = vhost {
-    documentRoot = "${webtree}/d/dedoctor";
-    user = "dedoctor";
-    group = "member";
-    wwwRedirect = true;
-    serverAliases = [ "www.grahambartley.com" ];
-  };
-  "h8.work" = vhost {
-    documentRoot = "${webtree}/a/ainran/domains/h8.work";
-    user = "ainran";
-    group = "member";
-    wwwRedirect = true;
-    serverAliases = [ "www.h8.work" ];
-  };
   "hack.${tld}" = vhost {
     documentRoot = "${webtree}/n/newbrick";
     user = "newbrick";
@@ -152,18 +113,6 @@ in (if (config.redbrick.skipVhosts) then {} else userVhosts // {
     user = "newbrick";
     group = "redbrick";
   };
-  "halenger.com" = vhost {
-    documentRoot = "${home}/associat/h/halenger/domains/halenger.com";
-    user = "halenger";
-    group = "associat";
-    wwwRedirect = true;
-    serverAliases = [ "www.halenger.com" ];
-  };
-  "interlan.dcu.ie" = vhost {
-    documentRoot = "${webtree}/vhosts/www.interlan.dcu.ie";
-    user = "gamessoc";
-    group = "society";
-  };
   "gamessoc.${tld}" = vhost {
     documentRoot = "${webtree}/g/games";
     user = "gamessoc";
@@ -174,18 +123,6 @@ in (if (config.redbrick.skipVhosts) then {} else userVhosts // {
       RedirectMatch 301 "^gamesoc\.(.*)$" "https://www.games.$1"
     '';
   };
-  "lessthanthree.be" = vhost {
-    documentRoot = "${webtree}/o/ornat";
-    user = "ornat";
-    group = "member";
-    wwwRedirect = true;
-    serverAliases = [ "www.lessthanthree.be" ];
-  };
-  "blog.lessthanthree.be" = vhost {
-    documentRoot = "${webtree}/o/ornat/blog";
-    user = "ornat";
-    group = "member";
-  };
   "mak.${tld}" = vhost {
     documentRoot = "${webtree}/m/mak/mak";
     user = "mak";
@@ -196,20 +133,6 @@ in (if (config.redbrick.skipVhosts) then {} else userVhosts // {
     user = "mcmahon";
     group = "member";
   };
-  "mlane.org" = vhost {
-    documentRoot = "${webtree}/a/ainran/mlane";
-    user = "ainran";
-    group = "associat";
-    wwwRedirect = true;
-    serverAliases = [ "www.mlane.org" ];
-  };
-  "obrienronan.com" = vhost {
-    documentRoot = "${webtree}/vhosts/www.obrienronan.com";
-    user = "mellow";
-    group = "associat";
-    wwwRedirect = true;
-    serverAliases = [ "www.obrienronan.com" ];
-  };
   "packages.${tld}" = vhost {
     documentRoot = "${webtree}/r/rbpkg/apt";
     user = "rbpkg";
@@ -235,6 +158,172 @@ in (if (config.redbrick.skipVhosts) then {} else userVhosts // {
     user = "d_fens";
     group = "associat";
   };
+  "room.${tld}" = vhost {
+    documentRoot = "${webtree}/e/edu/n109a";
+    user = "edu";
+    group = "redbrick";
+    serverAliases = ["n109a.${tld}"];
+  };
+  "sadsoc.${tld}" = vhost {
+    documentRoot = "${webtree}/a/art_wolf/sadsoc";
+    user = "art_wolf";
+    group = "associat";
+  };
+  "security.${tld}" = vhost {
+    documentRoot = "${webtree}/d/d_fens/security";
+    user = "d_fens";
+    group = "associat";
+  };
+  "signup.${tld}" = vhost {
+    documentRoot = "${webtree}/e/events/csday";
+    user = "events";
+    group = "redbrick";
+  };
+  "surfnsail.${tld}" = vhost {
+    documentRoot = "${webtree}/s/sailing";
+    user = "sailing";
+    group = "club";
+  };
+  "wanderers.${tld}" = vhost {
+    documentRoot = "${webtree}/w/wander/";
+    user = "wander";
+    group = "projects";
+  };
+  "webmail.${tld}" = vhost {
+    documentRoot = "${webtree}/vhosts/rainloop";
+    user = "wwwrun";
+    group = "wwwrun";
+  };
+  "x-files.${tld}" = vhost {
+    documentRoot = "${webtree}/f/fox_chic";
+    user = "fox_chic";
+    group = "associat";
+  };
+  "yfg.${tld}" = vhost {
+    documentRoot = "${webtree}/f/finegael";
+    user = "finegael";
+    group = "associat";
+  };
+  "youth2000.${tld}" = vhost {
+    documentRoot = "${webtree}/y/youth2k";
+    user = "gamessoc";
+    group = "society";
+  };
+  "git.${tld}" = vhostProxy "http://localhost:3000/";
+  "prometheus.${tld}" = vhostProxy "http://localhost:9090/";
+  "graphs.${tld}" = vhostProxy "http://localhost:3001/";
+  "dcufm.${tld}" = vhostProxy "http://localhost:8002/";
+  "jakarta.${tld}" = vhostProxy "http://136.206.15.59:8080/";
+  "macspayn.${tld}" = vhostProxy "http://136.206.15.25:3007/";
+  "portaldev.${tld}" = vhostProxy "http://136.206.15.61:9080/";
+  "radio.${tld}" = vhostProxy "http://radio.${tld}:8000/";
+  "riainccc.${tld}" = vhostProxy "http://http://136.206.15.25:3000/";
+  "tomcat.dregin.${tld}" = vhostProxy "http://136.206.15.14:20002/";
+  "webchat.${tld}" = vhostProxy "http://127.0.0.1:16667/";
+  "werdztomcat.${tld}" = vhostProxy "http://136.206.15.14:20001/";
+  "www.${tld}" = vhostRedirect "https://${tld}/";
+  "admin.${tld}" = vhostRedirect "https://blog.${tld}/";
+  "admins.${tld}" = vhostRedirect "https://blog.${tld}/";
+  "ajaxterm.${tld}" = vhostRedirect "https://term.${tld}/";
+  "anyterm.${tld}" = vhostRedirect "https://term.${tld}/";
+  "dconcannon.${tld}" = vhostRedirect "https://shimoda.${tld}/";
+  "dermot.${tld}" = vhostRedirect "https://homer.${tld}/";
+  "devnull.${tld}" = vhostRedirect "https://colmmacc.${tld}/";
+  "devrandom.${tld}" = vhostRedirect "https://marvin.${tld}/";
+  "events.${tld}" = vhostRedirect "https://${tld}/events/";
+  "fosdem.${tld}" = vhostRedirect "https://redbrickdcu.typeform.com/to/ZwETj0";
+  "github.${tld}" = vhostRedirect "https://github.com/redbrick/";
+  "help.${tld}" = vhostRedirect "https://wiki.${tld}/mw/Helpdesk";
+  "helpdesk.${tld}" = vhostRedirect "https://wiki.${tld}/mw/Helpdesk";
+  "helpdeskexam.${tld}" = vhostRedirect "https://md.${tld}/s/SJzip7F9X#";
+  "mail.${tld}" = vhostRedirect "https://webmail.${tld}/";
+  "hoodies.${tld}" = vhostRedirect "https://redbrickdcu.typeform.com/to/Q4uIzR";
+  "parlour.${tld}" = vhostRedirect "https://songsfromtheparlour.com/";
+  "sistem.${tld}" = vhostRedirect "https://sistem.intersocs.ie/";
+  "techweek.${tld}" = vhostRedirect "https://techweek.dcu.ie/";
+  "tickets.${tld}" = vhostRedirect "https://dcusu.ticketsolve.com/shows/873599383/events/128190598";
+  "ubuntu.${tld}" = vhostRedirect "https://wiki.${tld}/mw/RedBrick_Ubuntu";
+
+} // (if (config.redbrick.skipCustomVhosts) then {} else {
+
+  "abovethefold.es" = vhost {
+    documentRoot = "${webtree}/r/receive/abovethefold";
+    user = "receive";
+    group = "staff";
+    wwwRedirect = true;
+    serverAliases = [ "www.abovethefold.es" ];
+  };
+  "butterflyexplosion.com" = vhost {
+    documentRoot = "${webtree}/c/carr";
+    user = "carr";
+    group = "associat";
+    wwwRedirect = true;
+    serverAliases = [ "www.butterflyexplosion.com" ];
+  };
+  "ciankehoe.ie" = vhost {
+    documentRoot = "${webtree}/c/cianky/ciankehoe.ie/public";
+    user = "cianky";
+    group = "member";
+  };
+  "dcudrama.ie" = vhost {
+    documentRoot = "${webtree}/d/drama";
+    user = "drama";
+    group = "society";
+    wwwRedirect = true;
+    serverAliases = [ "www.dcudrama.ie" ];
+  };
+  "djbdns.now.ie" = vhost {
+    documentRoot = "${webtree}/l/lecter/djbdns";
+    user = "lecter";
+    group = "associat";
+  };
+  "grahambartley.com" = vhost {
+    documentRoot = "${webtree}/d/dedoctor";
+    user = "dedoctor";
+    group = "member";
+    wwwRedirect = true;
+    serverAliases = [ "www.grahambartley.com" ];
+  };
+  "h8.work" = vhost {
+    documentRoot = "${webtree}/a/ainran/domains/h8.work";
+    user = "ainran";
+    group = "member";
+    wwwRedirect = true;
+    serverAliases = [ "www.h8.work" ];
+  };
+  "halenger.com" = vhost {
+    documentRoot = "${home}/associat/h/halenger/domains/halenger.com";
+    user = "halenger";
+    group = "associat";
+    wwwRedirect = true;
+    serverAliases = [ "www.halenger.com" ];
+  };
+  "lessthanthree.be" = vhost {
+    documentRoot = "${webtree}/o/ornat";
+    user = "ornat";
+    group = "member";
+    wwwRedirect = true;
+    serverAliases = [ "www.lessthanthree.be" ];
+  };
+  "blog.lessthanthree.be" = vhost {
+    documentRoot = "${webtree}/o/ornat/blog";
+    user = "ornat";
+    group = "member";
+  };
+  "mlane.org" = vhost {
+    documentRoot = "${webtree}/a/ainran/mlane";
+    user = "ainran";
+    group = "associat";
+    wwwRedirect = true;
+    serverAliases = [ "www.mlane.org" ];
+  };
+  "obrienronan.com" = vhost {
+    documentRoot = "${webtree}/vhosts/www.obrienronan.com";
+    user = "mellow";
+    group = "associat";
+    wwwRedirect = true;
+    serverAliases = [ "www.obrienronan.com" ];
+  };
   "richardwalsh.ie" = vhost {
     documentRoot = "${webtree}/k/koffee/";
     user = "koffee";
@@ -252,12 +341,6 @@ in (if (config.redbrick.skipVhosts) then {} else userVhosts // {
       "honk.for.faggots-on-strike.com"
     ];
   };
-  "room.${tld}" = vhost {
-    documentRoot = "${webtree}/e/edu/n109a";
-    user = "edu";
-    group = "redbrick";
-    serverAliases = ["n109a.${tld}"];
-  };
   "ryanmcdyer.com" = vhost {
     documentRoot = "${webtree}/r/ryanmcd";
     user = "ryanmcd";
@@ -265,16 +348,6 @@ in (if (config.redbrick.skipVhosts) then {} else userVhosts // {
     wwwRedirect = true;
     serverAliases = [ "www.ryanmcdyer.com" ];
   };
-  "sadsoc.${tld}" = vhost {
-    documentRoot = "${webtree}/a/art_wolf/sadsoc";
-    user = "art_wolf";
-    group = "associat";
-  };
-  "security.${tld}" = vhost {
-    documentRoot = "${webtree}/d/d_fens/security";
-    user = "d_fens";
-    group = "associat";
-  };
   "shaunneary.com" = vhost {
     documentRoot = "${webtree}/s/shaun/koken";
     user = "shaun";
@@ -284,11 +357,6 @@ in (if (config.redbrick.skipVhosts) then {} else userVhosts // {
     '';
     # TODO shout at shaun, his www. NS points to a different server
   };
-  "signup.${tld}" = vhost {
-    documentRoot = "${webtree}/e/events/csday";
-    user = "events";
-    group = "redbrick";
-  };
   "solarsystemscanlan.com" = vhost {
     documentRoot = "${webtree}/s/singer/solarsystemscanlan.com/";
     user = "singer";
@@ -303,11 +371,6 @@ in (if (config.redbrick.skipVhosts) then {} else userVhosts // {
     wwwRedirect = true;
     serverAliases = [ "www.songsfromtheparlour.com" ];
   };
-  "surfnsail.${tld}" = vhost {
-    documentRoot = "${webtree}/s/sailing";
-    user = "sailing";
-    group = "club";
-  };
   "techweek.dcu.ie" = vhost {
     documentRoot = "${webtree}/t/techwk/dist";
     user = "techwk";
@@ -354,11 +417,6 @@ in (if (config.redbrick.skipVhosts) then {} else userVhosts // {
     user = "nettles";
     group = "associat";
   };
-  "wanderers.${tld}" = vhost {
-    documentRoot = "${webtree}/w/wander/";
-    user = "wander";
-    group = "projects";
-  };
   "wiki.colmreilly.com" = vhost {
     documentRoot = "${webtree}/n/nettles/wiki/";
     user = "nettles";
@@ -371,11 +429,6 @@ in (if (config.redbrick.skipVhosts) then {} else userVhosts // {
     wwwRedirect = true;
     serverAliases = [ "www.ejmitchell.com" ];
   };
-  "www.iahpc.ie" = vhost {
-    documentRoot = "${home}/guest/iahpc/public_html";
-    user = "iahpc";
-    group = "guest";
-  };
   "www.luxgaa.lu" = vhost {
     documentRoot = "${webtree}/s/shivo/LuxGAA";
     user = "shivo";
@@ -386,63 +439,5 @@ in (if (config.redbrick.skipVhosts) then {} else userVhosts // {
     user = "shivo";
     group = "associat";
   };
-  "x-files.${tld}" = vhost {
-    documentRoot = "${webtree}/f/fox_chic";
-    user = "fox_chic";
-    group = "associat";
-  };
-  "yfg.${tld}" = vhost {
-    documentRoot = "${webtree}/f/finegael";
-    user = "finegael";
-    group = "associat";
-  };
-  "youth2000.${tld}" = vhost {
-    documentRoot = "${webtree}/y/youth2k";
-    user = "gamessoc";
-    group = "society";
-  };
-  "webmail.${tld}" = vhost {
-    documentRoot = "${webtree}/vhosts/rainloop";
-    user = "wwwrun";
-    group = "wwwrun";
-  };
-  "bash.${tld}" = vhost {
-    documentRoot = "${webtree}/y/yosarian/bash";
-    user = "yosarian";
-    group = "associat";
-  };
-  "git.${tld}" = vhostProxy "http://localhost:3000";
-  "prometheus.${tld}" = vhostProxy "http://localhost:9090";
-  "graphs.${tld}" = vhostProxy "http://localhost:3001";
-  "dcufm.${tld}" = vhostProxy "http://localhost:8002";
-  "jakarta.${tld}" = vhostProxy "http://136.206.15.59:8080";
-  "lists.${tld}" = vhostProxy "http://mail.internal:80";
-  "macspayn.${tld}" = vhostProxy "http://136.206.15.25:3007";
-  "portaldev.${tld}" = vhostProxy "http://136.206.15.61:9080";
-  "radio.${tld}" = vhostProxy "http://radio.${tld}:8000";
-  "riainccc.${tld}" = vhostProxy "http://http://136.206.15.25:3000";
-  "tomcat.dregin.${tld}" = vhostProxy "http://136.206.15.14:20002";
-  "webchat.${tld}" = vhostProxy "http://127.0.0.1:16667";
-  "werdztomcat.${tld}" = vhostProxy "http://136.206.15.14:20001";
-  "www.${tld}" = vhostRedirect "https://${tld}";
-  "admin.${tld}" = vhostRedirect "https://blog.${tld}";
-  "admins.${tld}" = vhostRedirect "https://blog.${tld}";
-  "ajaxterm.${tld}" = vhostRedirect "https://term.${tld}";
-  "anyterm.${tld}" = vhostRedirect "https://term.${tld}";
-  "dconcannon.${tld}" = vhostRedirect "https://shimoda.${tld}";
-  "dermot.${tld}" = vhostRedirect "https://homer.${tld}";
-  "devnull.${tld}" = vhostRedirect "https://colmmacc.${tld}";
-  "devrandom.${tld}" = vhostRedirect "https://marvin.${tld}";
-  "events.${tld}" = vhostRedirect "https://${tld}/events";
-  "fosdem.${tld}" = vhostRedirect "https://redbrickdcu.typeform.com/to/ZwETj0";
-  "github.${tld}" = vhostRedirect "https://github.com/redbrick";
-  "help.${tld}" = vhostRedirect "https://wiki.${tld}/mw/Helpdesk";
-  "helpdesk.${tld}" = vhostRedirect "https://wiki.${tld}/mw/Helpdesk";
-  "helpdeskexam.${tld}" = vhostRedirect "https://md.${tld}/s/SJzip7F9X#";
-  "hoodies.${tld}" = vhostRedirect "https://redbrickdcu.typeform.com/to/Q4uIzR";
-  "parlour.${tld}" = vhostRedirect "https://songsfromtheparlour.com";
   "radio.theinternets.be" = vhostRedirect "https://radio.${tld}";
-  "techweek.${tld}" = vhostRedirect "https://techweek.dcu.ie";
-  "tickets.${tld}" = vhostRedirect "https://dcusu.ticketsolve.com/shows/873599383/events/128190598";
-  "ubuntu.${tld}" = vhostRedirect "https://wiki.${tld}/mw/RedBrick_Ubuntu";
-})
+}))
diff --git a/services/libvirt.nix b/services/libvirt.nix
new file mode 100644
index 00000000..637db563
--- /dev/null
+++ b/services/libvirt.nix
@@ -0,0 +1,6 @@
+{
+  virtualisation.libvirtd = {
+    enable = true;
+    onShutdown = "shutdown";
+  };
+}
diff --git a/services/postfix/aliases.nix b/services/postfix/aliases.nix
new file mode 100644
index 00000000..ca74163b
--- /dev/null
+++ b/services/postfix/aliases.nix
@@ -0,0 +1,460 @@
+{tld}: {
+  # System, committee and important email aliases.
+  #
+  # NOTES
+  # =====
+  # - For committee position aliases during the changeover period, list the
+  #   outgoing person(s) first followed by the incoming person(s).
+
+  #-----------------#
+  # SYSTEM ACCOUNTS #
+  #-----------------#
+
+  # Redirections for system and pseudo accounts.
+  "MAILER-DAEMON" = "postmaster";
+  "postmaster" = "root";
+  "bin" = "root";
+  "daemon" = "root";
+  "man" = "root";
+  "news" = "root";
+  "nobody" = "/dev/null";
+  "operator" = "root";
+  "pop" = "root";
+  "system" = "root";
+  "toor" = "root";
+  "usenet" = "news";
+  "uucp" = "root";
+  "xten" = "root";
+  "postfix" = "root";
+  "abuse" = "rb-admins,chair,sec";
+  "security" = "root";
+  "ftp" = "root";
+  "ftp-bugs" = "ftp";
+  "hostmaster" = "root";
+  "nagios" = "root";
+  "_nagios" = "root";
+  "ossec" = "root";
+
+  # Mailman
+  "mailman" = "root";
+  "mailman-owner" = "mailman";
+  "mailman-request" = "mailman";
+  "mailman-bounces" = "mailman";
+  "mailman-admin" = "mailman";
+
+  # Where root mail goes. VERY IMPORTANT!
+  "root" = "rb-admins";
+
+  #----------------#
+  # Administrators #
+  #----------------#
+
+  # Who wants to get system reports, cron job output etc.
+  "system-reports" = "rb-admins";
+  "audit_warn" = "system-reports";
+
+  # Offical way to contact admins for requests.
+  "admin-request" = "rb-admins, ticket";
+  "elected-admin" = "elected-admins";
+
+  # Where mail addressed to generic 'admins' goes.
+  "admin" = "admin-request";
+  "admins" = "admin-request";
+
+  # DCU admin list.
+  #
+  # <plop>: Thu May 28 11:07:05 BST 1998
+  "dcu-admin-list" = "rb-admins, sysops@dcu.ie, mcgorman@compapp.dcu.ie";
+
+  # Admin Mailing lists
+  "rb-admins" = "rb-admins@lists.${tld}";
+  "elected-admins" = "elected-admins@lists.${tld}";
+  "admin-discuss" = "admin-discuss@lists.${tld}";
+  "trainee-admins" = "trainee-admins@lists.${tld}";
+
+  #-------------#
+  # Web related #
+  #-------------#
+
+  # webmaster is a mailman list
+  "httpd" = "webmaster";
+  "www" = "webmaster";
+  "webmaster" = "webmaster@lists.${tld}";
+
+  #---------------------#
+  # Committee & Society #
+  #---------------------#
+
+  # "The Founders" (TM)
+  "founders" = "drjolt, wibble, sandman, fergus, swipe, hyper";
+
+  # Committee is a mailing list (handled by mailman)
+  "committee" = "committee@lists.${tld}";
+
+  # HELP! requests.
+  "help" = "helpdesk";
+  "support" = "helpdesk";
+  "help-request" = "helpdesk";
+  "helpdesk-request" = "helpdesk";
+  "helpdesk" = "helpdesk@lists.${tld}";
+
+  # Chairperson alias.
+  "chairperson" = "chair";
+
+  # Treasurer alias.
+  "treasurer" = "treasure";
+
+  # Secretary alias.
+  "secretary" = "sec";
+
+  # Events alias.
+  "ents" = "events";
+  "birthday" = "events";
+
+  # Accounts alias:
+  "accounts" = "elected-admins, treasurer, chair";
+
+  # Redbrick encyclopedia sybmissions/queries.
+  #
+  # wishkah gave webgroup encylopedia, on conditions he
+  # be included in mails about it - <bubble>
+  #
+  "encyclopedia" = "wishkah";
+
+  # c-hey maintainters alias.
+  "c-hey" = "pooka, colmmacc, bobb";
+
+  # DNS.
+  "dns" = "committee";
+
+  # cancel-announce
+  # Cthulhu Thu Jan 27 01:00:00 GMT 2000
+  "cancel-announce" = "bobb";
+
+  #---------------#
+  # Miscellaneous #
+  #---------------#
+
+  # Mailing List aliases
+  "commonroom" = "\"commonroom@lists.${tld}\"";
+  "latvia" = "\"latvia@lists.${tld}\"";
+  "renting" = "\"rental@lists.${tld}\"";
+  # this one is for pooka's mailing list
+  "learning-journal" = "learning-journal@lists.${tld}";
+
+  # User aliases
+  "wimax" = "johan";
+  # Alias for STOCS to go with Web Page URL. (Added cthulhu)
+  "sillicon" = "stocs";
+  "spamtastic" = "bubble";
+  "su-webgroup" = "phil, arioch, p, esoteric";
+  "senseigoclub" = "pooka, belial, plop+go";
+  "blog" = "atlas";
+  "skyhawk" = "declan";
+  "tom.doyle" = "greenday";
+  "hairforceone" = "greenday";
+  "david.craig" = "vexation";
+  "dave.murphy" = "drjolt";
+  "david.murphy" = "drjolt";
+  "s.maher" = "snoopie";
+  "Robert.Carew" = "rob";
+  "diablo" = "fergus";
+  "antarbh" = "supres";
+  "colin.whittaker" = "grimnar";
+  "meaigs" = "afrodite";
+  "Margaret.McGaley" = "afrodite";
+  "Kevin.Cannon" = "p";
+  "raz" = "ivor";
+  "fergusos" = "shocks";
+  "zirconia" = "zircon";
+  "eoin.mcgrath" = "bob";
+  "acahill" = "ace";
+  "Ian.Hollingsworth" = "lemming";
+  "comet" = "helmet";
+  "thomas.kelly" = "kudo";
+  "debug" = "ubiquity";
+  "webradio" = "kudo, singer, cain, celery, thayl";
+  "red_giant" = "redgiant";
+  "dong" = "tunney";
+  "donal.mulligan" = "thor";
+  "donal" = "thor";
+  "Tanya.Reilly" = "toaster";
+  "mark.dunne" = "pixies";
+  "cathal.thorne" = "bodie";
+  "lickylips" = "lickylip";
+  "youknowwho" = "lickylip";
+  "john.canavan" = "tibor";
+  "john" = "tibor";
+  "john.lyons" = "homerj";
+  "brian.scanlan" = "singer";
+  "caroline.sheedy" = "bootie";
+  "jon.lundberg" = "spock";
+  "jonathan.lundberg" = "spock";
+  "damien.martin" = "otto";
+  "orla.mcgann" = "orly";
+  "nigel.parkes" = "elmer";
+  "eileen.gavin" = "munchkin";
+  "john.looney" = "valen";
+  "cian.synnott" = "pooka";
+  "mothlamp" = "pooka";
+  "kachun.leung" = "plop";
+  "dermot.hanley" = "wibble";
+  "mike.mchugh" = "sandman";
+  "james.raferty" = "lecter";
+  "james.raftery" = "lecter";
+  "aoife.mcgoveran" = "hms";
+  "andrew.lawless" = "andy";
+  "shane.ohuid" = "wishkah";
+  "daire.mckenna" = "fatwa";
+  "john.barker" = "barkerj";
+  "sean.cullen" = "hyper";
+  "paraic.oceallaigh" = "swipe";
+  "micheal.mchugh" = "sandman";
+  "fergus.donohue" = "fergus";
+  "barry.oneill" = "bubble";
+  "aoife.cahill" = "ace";
+  "sheila.pollard" = "sheila";
+  "robert.crosbie" = "bobb";
+  "bobb-spam" = "bobb";
+  "bobb-spam0" = "bobb";
+  "bobb-spam1" = "bobb";
+  "bobb-spam2" = "bobb";
+  "bobb-spam3" = "bobb";
+  "bobb-spam4" = "bobb";
+  "bobb-spam5" = "bobb";
+  "bobb-spam6" = "bobb";
+  "bobb-spam7" = "bobb";
+  "bobb-spam8" = "bobb";
+  "bobb-spam9" = "bobb";
+  "adam.kelly" = "cthulhu";
+  "justin.moran" = "cain";
+  "cecily.murray" = "celery";
+  "karl.podesta" = "kpodesta";
+  "ronan.ryan" = "ledge";
+  "johnny" = "jonny";
+  "andrew.phillips" = "jesus";
+  "hoi.chau.wong" = "whc";
+  "paddy.grant" = "floppy";
+  "patrick.grant" = "floppy";
+  "julie.kerin" = "julie";
+  "iddqd" = "macbain";
+  "michael.mcginness" = "mikka";
+  "conor.okane" = "cokane";
+  "donal.hunt" = "redgiant";
+  "brian.bambrick" = "moridin";
+  "declan.brennan" = "wilma";
+  "grainne.walsh" = "grainy";
+  "philip.reynolds" = "phil";
+  "phil.reynolds" = "phil";
+  "mglennon" = "magluby";
+  "sigh" = "mort";
+  "mort" = "arioch";
+  "craygor" = "element";
+  "ledg" = "ledge";
+  "amanda" = "dipso";
+  "djhooker" = "wilma";
+  "dell" = "sandman";
+  "patsy" = "heavenly";
+  "raf" = "turiel";
+  "littlemisscomedy" = "lmc";
+  "samresh" = "doomgod";
+  "conor.coyle" = "squaw";
+  "kevinbrennan" = "zircon";
+  "alanthecat" = "alantc";
+  "colm.maccarthaigh" = "colmmacc";
+  "pizzateam" = "sonic";
+  "holyspambatman" = "sonic";
+  "david.johnston" = "emperor";
+  "dermot.duffy" = "dizer";
+  "dizerspam" = "dizer";
+  "eoin.campbell" = "cambo";
+  "neil.walsh" = "marvin";
+  "barry" = "bubble";
+  "anthony.moyles" = "huey";
+  "mclarke" = "prolix";
+  "mclark" = "prolix";
+  "mark_campbell" = "mark";
+  "mark.campbell" = "mark";
+  "mcampbell" = "mark";
+  "campbellm" = "mark";
+  "pronane" = "kaos";
+  "shane_tallon" = "del_boy";
+  "wesley.gorman" = "badboy";
+  "ciaran.kenny" = "goratrix";
+  "trevor.johnston" = "trevj";
+  "sinead.mcgivney" = "neady";
+  "enda_dowling" = "deano";
+  "david.concannon" = "shimoda";
+  "gary_ludgate" = "dice";
+  "martin.clarke" = "prolix";
+  "cambo1982" = "cambo";
+  "jonathan.walsh" = "melmoth";
+  "ann.byrne" = "halfpint";
+  "martin.harte" = "tuama";
+  "lee.cash" = "brodie";
+  "declan.oneill" = "dec";
+  "keith.mcdonnell" = "bkeeper";
+  "john.ruddy" = "phase";
+  "peter.sinnott" = "link";
+  "michael.dowling" = "mickeyd";
+  "mickeydspam" = "mickeyd";
+  "eoghan" = "atlas";
+  "dru" = "drusilla";
+  "wavehunt" = "johan";
+  "stephen.ryan37" = "ryaner";
+  "natashamaher" = "7of9";
+  "huskerdu" = "phl";
+  "credak" = "creadak";
+  "craig.gavagan" = "creadak";
+  "joeh762" = "kuze";
+  "macattac" = "mak";
+  "failho" = "carri";
+  "bunny" = "bunbun";
+  # This burd wanted this alias. She's a friend of mine (singer).
+  # She used to be these addresses. Get rid of them and you're
+  # all dead. Yes, even you, son of drjolt in the year 2525.
+  # ok boomer - m1cr0man, 2020
+  "zorro" = "\"Aisling.NiCheallachain@irishlife.ie\"";
+  "aisfc" = "\"Aisling.NiCheallachain@irishlife.ie\"";
+  "assassins" = "art_wolf";
+  "sarunas" = "svan";
+  "sarunas.v" = "svan";
+  "sarunas.vancevicius" = "svan";
+  "eoghan.gaffney" = "atlas";
+  "carbonkid" = "gaara";
+  "waf" = "dregin";
+  "surfnsail" = "sailing";
+  "andrew.martin" = "werdz";
+  "mieows" = "angelkat";
+  "cocowtf" = "cocao";
+  "patrickswayze" = "goldfish";
+  "wolfhead" = "drg";
+  "david.lynam" = "coconut";
+  "cian.brennan" = "lil_cain";
+  "cian" = "lil_cain";
+  "biggestpenis" = "lil_cain";
+  "eoghan.cotter" = "johan";
+  "austin.halpin" = "haus";
+  "shane.stacey" = "isaac702";
+  "fruitcake" = "fructus";
+  "fruitcaek" = "fructus";
+  "lotta.mikkonen" = "attol";
+  "diarmaid.mcmanus" = "elephant";
+  "caroline.fuery" = "carri";
+  "damien.rhatigan" = "dano";
+  "jennifer.flynn" = "jennyf";
+  "michael.odowd" = "nanaki";
+  "microman" = "m1cr0man";
+  "cian.butler" = "butlerx";
+  # guess which one is the correct one
+  "kat.farrell" = "angelkat";
+  "kat.farrel" = "angelkat";
+  "kat.farell" = "angelkat";
+  "kat.farel" = "angelkat";
+  #for sonic
+  "alan.walsh" = "sonic";
+  "alwalsh" = "sonic";
+  "alanwalsh" = "sonic";
+  "sonicthehedgehog" = "sonic";
+  # aliases for receive :)
+  "andrew.harford" = "receive";
+  "andrew.j.harford" = "receive";
+  "andy.harford" = "receive";
+  "winchair" = "angelkat";
+  "winkat" = "angelkat";
+  "starbuck" = "receive";
+  # people spell bad
+  "recieve" = "receive";
+  "andrew.hartford" = "receive";
+  "lotta" = "attol";
+  "powertax" = "pwrtaxi";
+
+  "gamestocs" = "gamessoc";
+  "games1" = "gamessoc";
+  "games2" = "gamessoc";
+  "gamessoc1" = "gamessoc";
+  "gamessoc2" = "gamessoc";
+  "gamessoc3" = "gamessoc";
+  "gamessoc4" = "gamessoc";
+  "gamessoc5" = "gamessoc";
+  "gamessoc6" = "gamessoc";
+  "gamessoc7" = "gamessoc";
+  "gamessoc8" = "gamessoc";
+  "gamessoc9" = "gamessoc";
+  "gamessoc10" = "gamessoc";
+  "gamessoc11" = "gamessoc";
+  "gamessoc12" = "gamessoc";
+  "gamessoc13" = "gamessoc";
+
+  "jennyf" = "ribbons";
+  "niall.gaffney" = "gamma";
+  "richard.walsh" = "koffee";
+  "vadim" = "vadimck";
+  "lorcan.boyle" = "zergless";
+  "robert.devereux" = "kylar";
+  "christopher.boyle" = "greyman";
+  "cboyle" = "greyman";
+  "cliodhna.harrison" = "thegirl";
+  "safrole" = "saf";
+  "jonny" = "banjo";
+  "xmeabhx" = "timelady";
+  "jamesreilly" = "fun";
+  "snowda" = "fordy";
+  "funtrain" = "admins";
+  "ladmins" = "admins";
+  "tron" = "jammy";
+  "amadan" = "marvin";
+  "mike_d" = "marvin";
+  "loky" = "chikatee";
+  "scunni" = "jericho";
+  "the_rock" = "jericho";
+  "iruane" = "stark";
+  "aurora" = "smf";
+  "smithers" = "manuel";
+  "jonnyb" = "nedd";
+  "wwallace" = "epic";
+  "lizard" = "creech";
+  "lep" = "phat";
+  "crispy" = "chris";
+  "account" = "afsoc";
+  "odie" = "me";
+  "hriding" = "equest";
+  "equestrian" = "equest";
+
+  "economosoc" = "econosoc";
+
+  # now a spamtrap - colmmacc
+  "sarahb" = "/dev/null";
+  "audi_58" = "AUDI_S8";
+  "c_clay" = "fallen";
+  "rcummi" = "4aces";
+  "imelda" = "fruity";
+  "mulinho" = "mullins";
+  "sully1" = "sully";
+  "robert" = "gandalf";
+  "d_omall" = "domall";
+  "dhunt" = "zoro";
+  "seth" = "stranger";
+  "jolt-9" = "chalk";
+  "gee_sus" = "gee";
+  "jruddy" = "phase";
+  "lynchman" = "mloc";
+  "brianh" = "alantc";
+  "sutty" = "yosarian";
+  "ryanks" = "rhino";
+  "aliastom" = "ayatolah";
+  "osprey" = "pariah";
+  "jbolger" = "x";
+  "joey_p" = "mrs_girl";
+  "vigdis" = "tom";
+  "dimitri" = "grover";
+  "oasis" = "geezer";
+  "games" = "gamessoc";
+  "smeghead" = "jeebers";
+  "piesoc" = "matsoc";
+  "cherub" = "crumbs";
+  "dme3" = "dme4";
+  "garyod2" = "gary";
+  "haus17" = "haus";
+  "nadned" = "damnson";
+}
diff --git a/services/postfix/default.nix b/services/postfix/default.nix
index 5fa29413..677cfe40 100644
--- a/services/postfix/default.nix
+++ b/services/postfix/default.nix
@@ -1,40 +1,82 @@
 # Requires rspamadm dkim_keygen -k /var/secrets/${tld}.$(hostname).dkim.key -b 2048 -s $(hostname) -d ${tld}
 # chown rspamd:root chmod 400
-{config, pkgs, ...}:
+{config, pkgs, lib, ...}:
 let
   tld = config.redbrick.tld;
   common = import ../../common/variables.nix;
 
+  aliases = import ./aliases.nix { inherit tld; };
+  # TLD needs to be appended to use aliases as sender address with smtpd_sender_login_maps
+  # Only supports 1:1 mappings but could be modified to support 1:Many
+  aliasesAbsolute = lib.mapAttrs (alias: owner: if (builtins.match "^[a-zA-Z0-9_\\-\\+\\.]+$" owner) != null then "${owner}@${tld}" else owner) aliases;
+  aliasesFile = pkgs.writeText "postfix-aliases" (builtins.concatStringsSep "\n" (lib.mapAttrsToList (k: v: "${k}: ${v}") aliasesAbsolute));
+
   ldapCommon = ''
     server_host = ldap://${common.ldapHost}/
     version = 3
     bind = no
+    search_base = ou=accounts,o=redbrick
   '';
 
-  ldapAliasMap = pkgs.writeText "virt-mailbox-maps" (ldapCommon + ''
-    search_base = ou=accounts,o=redbrick
+  # Authenticated users we always accept mail from over port 587
+  # Allows mailman to spoof addresses
+  sender_whitelist = pkgs.writeText "sender_whitelist" ''
+    mailmgr@${tld} OK
+  '';
+
+  ldapSenderMap = pkgs.writeText "postfix-sender-maps" (ldapCommon + ''
     query_filter = (&(objectClass=posixAccount)(uid=%u))
     result_attribute = uid
     result_format = %s@${tld}
   '');
 
+  # Addresses we reject mail from over port 25
+  sender_blacklist = pkgs.writeText "sender_blacklist" ''
+    ${tld} REJECT
+  '';
+
+  # IPs we reject unauthenticated connections from
+  # Rspamd explicitly allows mail from local addresses which is dangerous for us
+  # List taken from rspamd's local_addrs option
+  unauth_ip_blacklist = pkgs.writeText "unauth_ip_blacklist" ''
+    127.0.0.0/8     REJECT
+    192.168.0.0/16  REJECT
+    10.0.0.0/8      REJECT
+    172.16.0.0/12   REJECT
+    fd00::/8        REJECT
+    169.254.0.0/16  REJECT
+    fe80::/10       REJECT
+  '';
+
   commonRestrictions = [
-    "permit_mynetworks" "permit_sasl_authenticated"
+    "permit_sasl_authenticated"
     "reject_unauth_pipelining"
   ];
 in {
   imports = [
+    ../redis.nix
+    ./mailman.nix
+    ./rspamd.nix
     ./postsrsd.nix
   ];
 
+  # Add postfix to redis group
+  users.users.postfix.extraGroups = [
+    "redis"
+  ];
+
   # Ensure postsrsd is started before postfix
   systemd.services.postfix = {
-    requires = [ "postsrsd.service" ];
-    after = [ "postsrsd.service" ];
+    requires = [ "postsrsd.service" "redis.service" "rspamd.service" ];
+    after = [ "postsrsd.service" "redis.service" "rspamd.service" ];
   };
 
   networking.firewall.allowedTCPPorts = [ 25 587 ];
 
+  # Since the TLD cert is a wildcard, this allows us to use TLS
+  # over localhost and authenticate correctly. Used in mailman
+  networking.hosts."127.0.0.1" = [ "localmail.${tld}" ];
+
   security.dhparams.enable = true;
   security.dhparams.params.smtpd_512.bits = 512;
   security.dhparams.params.smtpd_2048.bits = 2048;
@@ -64,18 +106,27 @@ in {
     # smtp_inet found in https://github.com/NixOS/nixpkgs/blob/54361cde9226ae6346b53b34acea9b493f803509/nixos/modules/services/mail/postfix.nix#L768
     masterConfig.smtp_inet.args = [ "-o" "smtpd_sasl_auth_enable=no" ];
 
+    # Files that need postmap run on them
+    # Added to /var/lib/postfix/conf/<name>
+    mapFiles.sender_whitelist = sender_whitelist;
+    mapFiles.sender_blacklist = sender_blacklist;
+    mapFiles.unauth_ip_blacklist = unauth_ip_blacklist;
+
+    # Aliases
+    aliasFiles.redbrick_aliases = aliasesFile;
+
     config = {
       # IP address used by postfix to send outgoing mail. You only need this if
       # your machine has multiple IP addresses - set it to your MX address to
       # satisfy your SPF record.
-      smtp_bind_address = "192.168.0.135";
+      smtp_bind_address = config.redbrick.smtpBindAddress;
       # http://www.postfix.org/BASIC_CONFIGURATION_README.html#proxy_interfaces
-      proxy_interfaces = "136.206.15.5";
+      proxy_interfaces = config.redbrick.smtpExternalAddress;
 
-      #virtual_mailbox_domains = tld;
-      #virtual_mailbox_maps = "hash:/var/lib/postfix/aliases";
-      #virtual_alias_maps = "ldap:" ++ ./ldap-virtual-alias-maps.cf;
-      # alias_maps = "hash:/etc/aliases, ldap:";
+      # Some bad clients...like MAILMAN... forget to add some important headers
+      # In particular I saw mailman forget Message-ID. This setting permits postfix
+      # to fix them
+      local_header_rewrite_clients = "permit_sasl_authenticated";
 
       # Generate own DHParams
       smtpd_tls_dh512_param_file = config.security.dhparams.params.smtpd_512.path;
@@ -86,11 +137,23 @@ in {
       # https://wiki.dovecot.org/HowTo/PostfixAndDovecotSASL
       smtpd_sasl_auth_enable = true;
       smtpd_sasl_type = "dovecot";
-      smtpd_sasl_path = "inet:${common.dovecotHost}:${builtins.toString common.dovecotSaslPort}";
+      smtpd_sasl_path = "unix:/var/run/dovecot2_sasl.sock";
 
-      # deliver mail for virtual users to Dovecot's TCP socket
+      # Deliver mail for all users to Dovecot's LMTP socket
       # http://www.postfix.org/lmtp.8.html
-      mailbox_transport = "lmtp:inet:${common.dovecotHost}:${builtins.toString common.dovecotLmtpPort}";
+      mailbox_transport = "lmtp:unix:/var/run/dovecot2_lmtp.sock";
+
+      # For the sake of possible NixOS overrides,
+      # set the default local_recipient_maps explicitly
+      # The $alias_maps trick means that aliases will resolve correctly
+      # Lightly documented here: http://www.postfix.org/LOCAL_RECIPIENT_README.html#main_config
+      local_recipient_maps = [ "ldap:${ldapSenderMap}" "$alias_maps" ];
+
+      # Require that registered accounts are authenticated to send mail as them
+      smtpd_sender_login_maps = [ "ldap:${ldapSenderMap}" "$alias_maps" ];
+
+      # Written to /etc/postfix by the nix config
+      alias_maps = [ "hash:/etc/postfix/redbrick_aliases" ];
 
       # Configure postsrsd so that forwarded mail is "remailed" with a safe from address
       sender_canonical_maps = "tcp:127.0.0.1:${builtins.toString config.services.postsrsd.forwardPort}";
@@ -171,14 +234,25 @@ in {
       smtpd_helo_required = true;
 
       smtpd_helo_restrictions = builtins.concatStringsSep ", " (commonRestrictions ++ [
+        # Allow hosts with any hostname internally to connect
+        "permit_mynetworks"
         "reject_invalid_helo_hostname" "reject_non_fqdn_helo_hostname"
         # This will reject all incoming mail without a HELO hostname that
         # properly resolves in DNS. This is a somewhat restrictive check and may
         # reject legitimate mail.
         "reject_unknown_helo_hostname"
       ]);
-      smtpd_sender_restrictions = builtins.concatStringsSep ", " (commonRestrictions ++ [
+      smtpd_sender_restrictions = builtins.concatStringsSep ", " ([
+        # Check the user isn't mailmgr
+        "check_sasl_access hash:/var/lib/postfix/conf/sender_whitelist"
+        # Not even good users should break these rules
         "reject_non_fqdn_sender" "reject_unknown_sender_domain"
+        # Allow authenticated users to send their email as themselves
+        "reject_sender_login_mismatch" "permit_sasl_authenticated"
+        # Prevent anyone from @${tld} sending mail unauthenticated
+        "check_sender_access hash:/var/lib/postfix/conf/sender_blacklist"
+        "reject_unlisted_sender" "reject_unauth_pipelining"
+        "warn_if_reject" "reject_unverified_sender"
       ]);
       smtpd_recipient_restrictions = builtins.concatStringsSep ", " (commonRestrictions ++ [
         "reject_non_fqdn_recipient" "reject_unknown_recipient_domain"
@@ -188,31 +262,29 @@ in {
         "reject_multi_recipient_bounce"
       ]);
       smtpd_relay_restrictions = builtins.concatStringsSep ", " [
-        "permit_mynetworks" "permit_sasl_authenticated"
+        # Check the user isn't mailmgr
+        "check_sasl_access hash:/var/lib/postfix/conf/sender_whitelist"
+        "reject_unlisted_sender"
+        "reject_sender_login_mismatch" "permit_sasl_authenticated"
+        # TODO Consider explicit reject here
         # !!! THIS SETTING PREVENTS YOU FROM BEING AN OPEN RELAY !!!
         "reject_unauth_destination"
         # !!!      DO NOT REMOVE IT UNDER ANY CIRCUMSTANCES      !!!
       ];
+      smtpd_client_restrictions = builtins.concatStringsSep ", " (commonRestrictions ++ [
+        # Allow hosts with any hostname internally to connect
+        "permit_mynetworks"
+        # Reject failed reverse records
+        "reject_unknown_reverse_client_hostname"
+        # Reject unauthenticated connections from local addresses. This is due to
+        # a limitation in rspamd which prevents us checking for spoofing internally
+        # see rspamd.nix
+        "check_client_a_access cidr:/var/lib/postfix/conf/unauth_ip_blacklist"
+        # This will reject all incoming connections without a reverse DNS
+        # entry that resolves back to the client's IP address. This is a very
+        # restrictive check and may reject legitimate mail.
+        "warn_if_reject" "reject_unknown_client_hostname"
+      ]);
     };
-
-    # Sets smtpd_client_restrictions
-    dnsBlacklists = commonRestrictions ++ [
-      "reject_unknown_reverse_client_hostname"
-      # This will reject all incoming connections without a reverse DNS
-      # entry that resolves back to the client's IP address. This is a very
-      # restrictive check and may reject legitimate mail.
-      "reject_unknown_client_hostname"
-    ];
-  };
-
-  # Enable rspamd and connect to postfix.
-  services.rspamd = {
-    enable = true;
-    postfix.enable = true;
-    locals."dkim_signing.conf".text = ''
-      path = "/var/secrets/$domain.$selector.dkim.key";
-      selector = "${config.networking.hostName}";
-      allow_username_mismatch = true;
-    '';
   };
 }
diff --git a/services/postfix/exim2nix.py b/services/postfix/exim2nix.py
new file mode 100755
index 00000000..c459c0be
--- /dev/null
+++ b/services/postfix/exim2nix.py
@@ -0,0 +1,20 @@
+#!/usr/bin/env python3
+# Python 3.6+
+# Converts exim aliases text file to Nix
+# Usage: cat exim_aliases.txt | ./exim2nix.py > aliases.nix
+import sys
+
+print('{')
+for l in sys.stdin:
+    if '#' not in l and ':' in l:
+        try:
+            alias, to = l.strip().split(':')
+        except:
+            print("Broken line", l.strip())
+            continue
+        alias = alias.strip()
+        to = to.strip().replace('"', r'\"')
+        print(f"  \"{alias}\" = \"{to}\";")
+    else:
+        print(l.strip())
+print('}')
diff --git a/services/postfix/mailman.nix b/services/postfix/mailman.nix
new file mode 100644
index 00000000..f96c9281
--- /dev/null
+++ b/services/postfix/mailman.nix
@@ -0,0 +1,160 @@
+# Manual steps post-deploy:
+# cd /var/lib/mailman && sudo -u mailman mailman aliases && sysemctl restart postfix
+{pkgs, config, lib, ...}:
+let
+  common = import ../../common/variables.nix;
+
+  tld = config.redbrick.tld;
+  secretsFile = "/var/secrets/mailman.json";
+  secrets = import /var/secrets/mailman.nix;
+  postgresHost = "127.0.0.1";
+
+  mailServer = "localmail.${tld}";
+
+  # Mailman needs access to hyperkitty, which is on the same host
+  hyperkittyLocal = mailServer;
+in {
+  services.mailman = {
+    enable = true;
+    siteOwner = "admins+mailman@${tld}";
+    webHosts = [ "lists.${tld}" ];
+    hyperkitty = {
+      enable = true;
+      baseUrl = "https://${hyperkittyLocal}/hyperkitty/";
+    };
+  };
+
+  # Mailman core has no real NixOS Options
+  # Extend the etc file..
+  environment.etc."mailman.cfg" = {
+    mode = "0400";
+    user = "mailman";
+    text = ''
+      [mta]
+      smtp_host: ${mailServer}
+      smtp_port: 587
+      smtp_user: ${secrets.emailUser}
+      smtp_pass: ${secrets.emailPassword}
+      smtp_secure_mode: starttls
+    '';
+  };
+
+  # Our ldap has combined first name + last name (cn), and no email field
+  # If there's ever a Django dev looking at this and sees a better way to do it,
+  # PLEASE DO IT
+  environment.etc."mailman3/rbapp/__init__.py".text = ''
+    __version__ = '1.0.0'
+    default_app_config = 'rbapp.apps.RBAppConfig'
+  '';
+  environment.etc."mailman3/rbapp/signals.py".text = ''
+    from django_auth_ldap.backend import populate_user
+    from django.dispatch import receiver
+
+    @receiver(populate_user)
+    def on_populate_user(sender, **kwargs):
+        """Process population of a user."""
+        user = kwargs.get('user', None)
+        ldap_user = kwargs.get('ldap_user', None)
+
+        if not (user and ldap_user):
+            return
+
+        user.email = user.username + "@${tld}"
+
+        name_split = user.first_name.split()
+        if len(name_split) != 2:
+          return
+
+        first_name, last_name = name_split
+        user.first_name = first_name
+        user.last_name = last_name
+  '';
+  environment.etc."mailman3/rbapp/apps.py".text = ''
+    from django.apps import AppConfig
+
+    class RBAppConfig(AppConfig):
+        name = 'rbapp'
+        verbose_name = 'RB App'
+
+        def ready(self):
+            import rbapp.signals
+  '';
+  environment.etc."mailman3/settings.py".text = ''
+    # Add ldap to search path
+    # Package must be updated if main python3 version changes
+    import site
+    site.addsitedir('${pkgs.python37Packages.ldap}/lib/python3.7/site-packages')
+    site.addsitedir('${pkgs.python37Packages.pyasn1-modules}/lib/python3.7/site-packages')
+    site.addsitedir('${pkgs.python37Packages.django-auth-ldap}/lib/python3.7/site-packages')
+
+    import ldap
+    import json
+    from django_auth_ldap.config import LDAPSearch, PosixGroupType
+
+    TIME_ZONE = 'Europe/Dublin'
+    ALLOWED_HOSTS = [ 'lists.${tld}', 'localmail.${tld}' ]
+
+    # When initialising Mailman, comment this line out until you go to /admin and add a site
+    # Otherwise you might get "Site matching query does not exist"
+    SITE_ID = 2
+
+    INSTALLED_APPS = INSTALLED_APPS + ['rbapp']
+
+    AUTHENTICATION_BACKENDS = (
+        'django_auth_ldap.backend.LDAPBackend',
+        'django.contrib.auth.backends.ModelBackend',
+    )
+
+    AUTH_LDAP_SERVER_URI = "ldap://${common.ldapHost}"
+
+    # Use the user's own credentials to bind to LDAP. Allows for reading of
+    # special fields, and no need for a mailman LDAP user
+    AUTH_LDAP_BIND_AS_AUTHENTICATING_USER = True
+    AUTH_LDAP_USER_ATTRLIST = ["*", "+"]
+    AUTH_LDAP_USER_DN_TEMPLATE = "uid=%(user)s,ou=accounts,o=redbrick"
+    AUTH_LDAP_USER_ATTR_MAP = {
+        "username": "uid",
+        "first_name": "cn",
+    }
+
+    AUTH_LDAP_GROUP_TYPE = PosixGroupType()
+    AUTH_LDAP_USER_SEARCH = LDAPSearch("ou=accounts,o=redbrick", ldap.SCOPE_SUBTREE, "(uid=%(user)s)")
+    AUTH_LDAP_GROUP_SEARCH = LDAPSearch("ou=groups,o=redbrick", ldap.SCOPE_SUBTREE, "(objectClass=posixGroup)")
+
+    with open('${secretsFile}', 'r') as db_pass_file:
+        secrets = json.load(db_pass_file)
+
+    DATABASES = {
+        'default': {
+            'ENGINE': 'django.db.backends.postgresql',
+            'NAME': 'mailman',
+            'USER': secrets['db_user'],
+            'PASSWORD': secrets['db_password'],
+            'HOST': '${postgresHost}',
+            'PORT': '5432',
+        }
+    }
+
+    EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
+    EMAIL_HOST = '${mailServer}'
+    EMAIL_PORT = 587
+    EMAIL_USE_TLS = True
+    EMAIL_HOST_USER = secrets['email_user']
+    EMAIL_HOST_PASSWORD = secrets['email_password']
+    DEFAULT_FROM_EMAIL = 'mailmgr@${tld}'
+    ACCOUNT_EMAIL_VERIFICATION = 'none'
+
+    AUTH_LDAP_MIRROR_GROUPS = True
+    AUTH_LDAP_USER_FLAGS_BY_GROUP = {
+        "is_superuser": "cn=mailadm,ou=groups,o=redbrick"
+    }
+  '';
+
+  services.postfix = {
+    relayDomains = [ "hash:/var/lib/mailman/data/postfix_domains" ];
+    config.transport_maps = [ "hash:/var/lib/mailman/data/postfix_lmtp" ];
+    config.local_recipient_maps = [ "hash:/var/lib/mailman/data/postfix_lmtp" ];
+  };
+
+  networking.firewall.allowedTCPPorts = [ 80 ];
+}
diff --git a/services/postfix/rspamd.nix b/services/postfix/rspamd.nix
new file mode 100644
index 00000000..fc1756f0
--- /dev/null
+++ b/services/postfix/rspamd.nix
@@ -0,0 +1,116 @@
+{ config, ... }:
+let
+  milterPort = "/run/rspamd/milter.sock";
+in {
+  # Not necessary since it's in the postfix import, but just in case
+  imports = [
+    ../redis.nix
+  ];
+
+  # Add rspamd to redis group
+  users.users.rspamd.extraGroups = [
+    "redis"
+  ];
+
+  # Ensure redis is started before rspamd
+  systemd.services.postfix = {
+    requires = [ "redis.service" ];
+    after = [ "redis.service" ];
+  };
+
+  services.rspamd = {
+    enable = true;
+    postfix = {
+      enable = true;
+      config = {
+        smtpd_milters = [ "unix:${milterPort}" ];
+        non_smtpd_milters = [ "unix:${milterPort}" ];
+      };
+    };
+    overrides."options.inc".text = ''
+      # Don't whitelist any IP addresses for SPF/DKIM checks
+      # This unfortunately doesn't work, at least when implemented in 2020
+      # and thus we have to blacklist local IPs sending unauthed mail in postfix
+      local_addrs = [];
+    '';
+    locals."redis.conf".text = ''
+      # Redis is needed for a number of modules
+      servers = "/run/redis/redis.sock";
+    '';
+    locals."dkim_signing.conf".text = ''
+      path = "/var/secrets/$domain.$selector.dkim.key";
+      selector = "${config.networking.hostName}";
+      allow_username_mismatch = true;
+      sign_local = false;
+      sign_authenticated = true;
+	  use_esld = false;
+    '';
+    locals."worker-controller.inc".text = ''
+      # generate a password hash using the `rspamadm pw` command and put it here
+      # This is git safe - it's a hash, for god sake
+      password = "$2$6znhwcxm4f3aja5d4cwaj1pheayfddms$13x4n1kn8frfnnx6mwkpchi3twd9napyqpf4pyom5rrqktxdgobb";
+      enable_password = "$2$6znhwcxm4f3aja5d4cwaj1pheayfddms$13x4n1kn8frfnnx6mwkpchi3twd9napyqpf4pyom5rrqktxdgobb";
+
+      # dovecot will use this socket to communicate with rspamd
+      # RuntimeDirectory created by systemd in nixpkgs module for rspamd
+      bind_socket = "/run/rspamd/rspamd.sock mode=0660 owner=rspamd group=dovecot2";
+
+      # you can comment this out if you don't need the web interface
+      bind_socket = "127.0.0.1:11334";
+    '';
+    locals."worker-normal.inc".text = ''
+      # we're not running rspamd in a distributed setup, so this can be disabled
+      # the proxy worker will handle all the spam filtering
+      enabled = false;
+    '';
+    locals."worker-proxy.inc".text = ''
+      # this worker will be used as postfix milter
+      milter = yes;
+
+      # RuntimeDirectory created by systemd in nixpkgs module for rspamd
+      bind_socket = "${milterPort} mode=0660 owner=rspamd group=postfix";
+
+      # the following specifies self-scan mode, for when rspamd is on the same
+      # machine as postfix
+      timeout = 120s;
+      upstream "local" {
+        default = yes;
+        self_scan = yes;
+      }
+    '';
+    locals."classifier-bayes.conf".text = ''
+      autolearn = true;
+      backend = "redis";
+    '';
+    locals."maillist.conf".text = "";
+    locals."mx_check.conf".text = ''
+      enabled = true;
+    '';
+    locals."phishing.conf".text = ''
+      openphis_enabled = true;
+      phishtank_enabled = true;
+    '';
+    locals."replies.conf".text = ''
+      action = "no action";
+    '';
+    locals."spf.conf".text = ''
+      spf_cache_size = 2k;
+      spf_cache_expire = 1d;
+      max_dns_nesting = 8;
+      max_dns_requests = 20;
+      min_cache_ttl = 5m;
+      disable_ipv6 = false;
+    '';
+    locals."dkim.conf".text = ''
+      dkim_cache_size = 2k;
+    '';
+    locals."url_reputation.conf".text = ''
+      # Scan URLs
+      enabled = true;
+    '';
+    locals."url_tags.conf".text = ''
+      # Redis caching of URL tags
+      enabled = true;
+    '';
+  };
+}
diff --git a/services/redis.nix b/services/redis.nix
new file mode 100644
index 00000000..420a1b96
--- /dev/null
+++ b/services/redis.nix
@@ -0,0 +1,24 @@
+{
+  users.groups.redis = {};
+  users.users.redis.group = "redis";
+
+  # Create a log directory with systemd <3
+  systemd.services.redis.serviceConfig.LogsDirectory = "redis";
+
+  services.redis = {
+    enable = true;
+    port = 0;
+    unixSocket = "/run/redis/redis.sock";
+
+    # Journal
+    logfile = "/var/log/redis/redis.log";
+    syslog = false;
+
+    extraConfig = ''
+      rdbcompression yes
+      maxmemory 2G
+      maxmemory-policy allkeys-lru
+      unixsocketperm 660
+    '';
+  };
+}
diff --git a/services/ssh.nix b/services/ssh.nix
index e7292afb..b1506679 100644
--- a/services/ssh.nix
+++ b/services/ssh.nix
@@ -1,15 +1,50 @@
-{
-  services.openssh.enable = true;
+{ pkgs, ... }:
+let
+  whodir = "/run/whoroot";
+
+  # A wrapper over w which replaces root with the username from the key
+  # of the logged in rooter. The files in whodir are written by the
+  # loginShellInit
+  # The first regex removes the slash from the tty
+  whoroot = pkgs.writeShellScriptBin "wroot" ''
+    sedscript="s!pts/!pts!g;"
+    for f in ${whodir}/*; do
+      bf=$(basename $f)
+      sedscript="''${sedscript}s!root +$bf!$(cat $f)\t$bf!g;"
+    done
+    ${pkgs.procps}/bin/w | ${pkgs.gnused}/bin/sed -E "$sedscript"
+  '';
+in {
+  services.openssh = {
+    enable = true;
+    extraConfig = ''
+      PermitUserEnvironment yes
+    '';
+  };
+
   users.users.root.openssh.authorizedKeys.keys = [
-    "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINnVLSh0OStxZTkXE6oGgwfFvsbvN6bFPlVfDYOwtnzn lucas@oatfield"
-    "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPR+OTAIYr02f/WKQSXo7zYy9tkuAHYpy0ajqY6aJ7Nk m1cr0man@redbrick.dcu.ie"
-    "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDYKnYP4Mmyk4wQE7J6Tyr27XToKtxAhXBZr5HkEXiFq root@gelandewagen"
-    "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHvabMrrJILDua2sedVqBStb6YKBHpgCO5HOM98l7uwf greenday@DESKTOP-NJVR3G4"
-    "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOjro8OS7cWf6xBcrs4erZqjN5JdztoGqpMXFQwzd9pV mctastic@azazel.redbrick.dcu.ie"
-    "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDLSLIF2IOo/OzbmbMGp4kt6VP2z8zNCuuVNyuxyBU0A8cOeUhkAbVibVmFqPlcHDJ4+zhkNN0GDnEJEUAmBNi+yc9EJG7StxdguEAKPlA9gQ/Z73cMrfMHtTPOHj/uCKUqi9vzb3tlOltJmuS3SwF0B5dk58j/cwr3nEEzikMmQIykxI+F+rxMnxaQXtNBGz3ednAaJ4Lvv9JSxWcExEtU0lM0X1MgZgkYFr48uQwsDUE+j23+wifMrOA+zhC0uRcuIapnxsyoW/wDOYrQZFlw6acrVX+zNtxcCQoqIX4oAobCXn7tYz9peKrV8TmJwQOspsmyY75xIAbyz0AiD3oh kyle@HP-Kyle"
-    "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIG/8Xf5/DtcOPjZKfag4ATBe5a3I1HvhYqi8fV7si4OU butlerx@notthe.cloud"
-    "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIhA5mm1sBzz6tcrUF2FzW6wrckW1IsQAyS8Bfu4yJRJ d_fens@redbrick.dcu.ie"
-    "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGybjW48+tQaykqDIuSeuH/3GLQRHZDa1toJOIB/FrD4 fraz@azazel.redbrick.dcu.ie"
-    "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKVc8zqnZkzsOHzfycpJ3QbB9SJ2FxmRRifYbBuuixk2 galvinio@azazel.redbrick.dcu.ie"
+    "environment=\"REMOTEUSER=m1cr0man\" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINnVLSh0OStxZTkXE6oGgwfFvsbvN6bFPlVfDYOwtnzn m1cr0man@redbrick.dcu.ie"
+    "environment=\"REMOTEUSER=m1cr0man\" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPR+OTAIYr02f/WKQSXo7zYy9tkuAHYpy0ajqY6aJ7Nk m1cr0man@redbrick.dcu.ie"
+    "environment=\"REMOTEUSER=m1cr0man\" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDYKnYP4Mmyk4wQE7J6Tyr27XToKtxAhXBZr5HkEXiFq m1cr0man@redbrick.dcu.ie"
+    "environment=\"REMOTEUSER=greenday\" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHvabMrrJILDua2sedVqBStb6YKBHpgCO5HOM98l7uwf greenday@redbrick.dcu.ie"
+    "environment=\"REMOTEUSER=mctastic\" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOjro8OS7cWf6xBcrs4erZqjN5JdztoGqpMXFQwzd9pV mctastic@azazel.redbrick.dcu.ie"
+    "environment=\"REMOTEUSER=ylmcc\" ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDLSLIF2IOo/OzbmbMGp4kt6VP2z8zNCuuVNyuxyBU0A8cOeUhkAbVibVmFqPlcHDJ4+zhkNN0GDnEJEUAmBNi+yc9EJG7StxdguEAKPlA9gQ/Z73cMrfMHtTPOHj/uCKUqi9vzb3tlOltJmuS3SwF0B5dk58j/cwr3nEEzikMmQIykxI+F+rxMnxaQXtNBGz3ednAaJ4Lvv9JSxWcExEtU0lM0X1MgZgkYFr48uQwsDUE+j23+wifMrOA+zhC0uRcuIapnxsyoW/wDOYrQZFlw6acrVX+zNtxcCQoqIX4oAobCXn7tYz9peKrV8TmJwQOspsmyY75xIAbyz0AiD3oh ylmcc@redbrick.dcu.ie"
+    "environment=\"REMOTEUSER=butlerx\" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIG/8Xf5/DtcOPjZKfag4ATBe5a3I1HvhYqi8fV7si4OU butlerx@redbrick.dcu.ie"
+    "environment=\"REMOTEUSER=d_fens\" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIhA5mm1sBzz6tcrUF2FzW6wrckW1IsQAyS8Bfu4yJRJ d_fens@redbrick.dcu.ie"
+    "environment=\"REMOTEUSER=fraz\" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGybjW48+tQaykqDIuSeuH/3GLQRHZDa1toJOIB/FrD4 fraz@redbrick.dcu.ie"
+    "environment=\"REMOTEUSER=galvinio\" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKVc8zqnZkzsOHzfycpJ3QbB9SJ2FxmRRifYbBuuixk2 galvinio@redbrick.dcu.ie"
+    "environment=\"REMOTEUSER=mcmahon\" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBPzJW/r9XddCBa8Y1wLCt1FGNvGB3OD/fFzo19AE6/B mcmahon@redbrick.dcu.ie"
+  ];
+
+  environment.loginShellInit = ''
+    if [ -n "$REMOTEUSER" ]; then
+      mkdir -p ${whodir}
+      # The regex removes the slashes and /dev from the tty
+      echo $REMOTEUSER > ${whodir}/$(tty | sed -E 's!(/dev)?/!!g')
+    fi
+  '';
+
+  environment.systemPackages = [
+    whoroot
   ];
 }