about summary refs log tree commit diff stats
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--flake.lock48
-rw-r--r--hosts/by-name/server2/configuration.nix20
-rw-r--r--hosts/by-name/server3/configuration.nix15
-rw-r--r--modules/by-name/co/constants/module.nix16
-rw-r--r--modules/by-name/ng/nginx/module.nix5
-rw-r--r--modules/by-name/sh/sharkey/module.nix297
-rw-r--r--modules/by-name/st/stalwart-mail/module.nix106
-rw-r--r--modules/by-name/st/stalwart-mail/settings.nix34
-rw-r--r--modules/by-name/ta/taskchampion-sync/module.nix10
-rw-r--r--pkgs/by-name/sh/sharkey/package.nix4
-rw-r--r--pkgs/by-name/sh/sharkey/unstable_package.nix176
-rw-r--r--pkgs/by-name/st/stalwart-mail-free/package.nix4
-rw-r--r--tests/by-name/em/email-dns/nodes/mail_server.nix14
-rw-r--r--tests/by-name/em/email-dns/nodes/name_server.nix234
-rw-r--r--tests/by-name/em/email-dns/nodes/user.nix8
-rw-r--r--tests/by-name/em/email-dns/test.nix38
-rw-r--r--tests/by-name/em/email-http/nodes/mail_server.nix57
-rw-r--r--tests/by-name/em/email-http/nodes/user.nix26
-rw-r--r--tests/by-name/em/email-http/test.nix115
-rw-r--r--tests/by-name/em/email-ip/test.nix4
-rw-r--r--tests/by-name/sh/sharkey/test.nix118
-rwxr-xr-xtests/common/acme/certs/generate (renamed from tests/by-name/em/email-dns/nodes/acme/certs/generate)0
-rwxr-xr-xtests/common/acme/certs/generate.ca (renamed from tests/by-name/em/email-dns/nodes/acme/certs/generate.ca)0
-rwxr-xr-xtests/common/acme/certs/generate.client (renamed from tests/by-name/em/email-dns/nodes/acme/certs/generate.client)0
-rw-r--r--tests/common/acme/certs/output/acme.test.cert.pem (renamed from tests/by-name/em/email-dns/nodes/acme/certs/output/acme.test.cert.pem)0
-rw-r--r--tests/common/acme/certs/output/acme.test.key.pem (renamed from tests/by-name/em/email-dns/nodes/acme/certs/output/acme.test.key.pem)0
-rw-r--r--tests/common/acme/certs/output/acme.test.template (renamed from tests/by-name/em/email-dns/nodes/acme/certs/output/acme.test.template)0
-rw-r--r--tests/common/acme/certs/output/ca.cert.pem (renamed from tests/by-name/em/email-dns/nodes/acme/certs/output/ca.cert.pem)0
-rw-r--r--tests/common/acme/certs/output/ca.key.pem (renamed from tests/by-name/em/email-dns/nodes/acme/certs/output/ca.key.pem)0
-rw-r--r--tests/common/acme/certs/output/ca.template (renamed from tests/by-name/em/email-dns/nodes/acme/certs/output/ca.template)0
-rw-r--r--tests/common/acme/certs/snakeoil-certs.nix (renamed from tests/by-name/em/email-dns/nodes/acme/certs/snakeoil-certs.nix)0
-rw-r--r--tests/common/acme/client.nix (renamed from tests/by-name/em/email-dns/nodes/acme/client.nix)0
-rw-r--r--tests/common/acme/scripts.nix30
-rw-r--r--tests/common/acme/server.nix (renamed from tests/by-name/em/email-dns/nodes/acme/default.nix)27
-rw-r--r--tests/common/dns/client.nix10
-rw-r--r--tests/common/dns/server.nix43
-rw-r--r--tests/common/email/dkim/alice.com/private.age (renamed from tests/by-name/em/email-dns/secrets/dkim/alice.com/private.age)0
-rw-r--r--tests/common/email/dkim/alice.com/public (renamed from tests/by-name/em/email-dns/secrets/dkim/alice.com/public)0
-rw-r--r--tests/common/email/dkim/bob.com/private.age (renamed from tests/by-name/em/email-dns/secrets/dkim/bob.com/private.age)0
-rw-r--r--tests/common/email/dkim/bob.com/public (renamed from tests/by-name/em/email-dns/secrets/dkim/bob.com/public)0
-rwxr-xr-xtests/common/email/dkim/gen_key.sh (renamed from tests/by-name/em/email-dns/secrets/dkim/gen_key.sh)0
-rw-r--r--tests/common/email/dkim/mail1.server.com/private.age (renamed from tests/by-name/em/email-dns/secrets/dkim/mail1.server.com/private.age)0
-rw-r--r--tests/common/email/dkim/mail1.server.com/public (renamed from tests/by-name/em/email-dns/secrets/dkim/mail1.server.com/public)0
-rw-r--r--tests/common/email/dkim/mail2.server.com/private.age (renamed from tests/by-name/em/email-dns/secrets/dkim/mail2.server.com/private.age)0
-rw-r--r--tests/common/email/dkim/mail2.server.com/public (renamed from tests/by-name/em/email-dns/secrets/dkim/mail2.server.com/public)0
-rw-r--r--tests/common/email/hostKey (renamed from tests/by-name/em/email-dns/secrets/hostKey)0
-rwxr-xr-xupdate.sh3
-rw-r--r--zones/vhack.eu/zone.nix3
48 files changed, 1164 insertions, 301 deletions
diff --git a/flake.lock b/flake.lock
index a23f22a..75b1bc4 100644
--- a/flake.lock
+++ b/flake.lock
@@ -43,11 +43,11 @@
     },
     "crane": {
       "locked": {
-        "lastModified": 1742394900,
-        "narHash": "sha256-vVOAp9ahvnU+fQoKd4SEXB2JG2wbENkpqcwlkIXgUC0=",
+        "lastModified": 1745022865,
+        "narHash": "sha256-tXL4qUlyYZEGOHUKUWjmmcvJjjLQ+4U38lPWSc8Cgdo=",
         "owner": "ipetkov",
         "repo": "crane",
-        "rev": "70947c1908108c0c551ddfd73d4f750ff2ea67cd",
+        "rev": "25ca4c50039d91ad88cc0b8feacb9ad7f748dedf",
         "type": "github"
       },
       "original": {
@@ -111,11 +111,11 @@
         ]
       },
       "locked": {
-        "lastModified": 1741786315,
-        "narHash": "sha256-VT65AE2syHVj6v/DGB496bqBnu1PXrrzwlw07/Zpllc=",
+        "lastModified": 1745224732,
+        "narHash": "sha256-0OWgbEKhpMLpk3WQi3ugOwxWW4Y6JVpKiQ+o0nuNzus=",
         "owner": "nix-community",
         "repo": "disko",
-        "rev": "0d8c6ad4a43906d14abd5c60e0ffe7b587b213de",
+        "rev": "1770bf1ae5da05564f86b969ef21c7228cc1a70b",
         "type": "github"
       },
       "original": {
@@ -214,11 +214,11 @@
     },
     "nixpkgs": {
       "locked": {
-        "lastModified": 1743036386,
-        "narHash": "sha256-W1Qap/jwnpvWPXC+cUp0PlaZsJO05sfQfzffRrYW7YY=",
+        "lastModified": 1745186762,
+        "narHash": "sha256-vn1ixtFWtellgcZpdIoxCVu9agdK3647hi7lcc/58eQ=",
         "owner": "NixOS",
         "repo": "nixpkgs",
-        "rev": "1751c9cb80247edc5fed79b90211a92c56bf91e6",
+        "rev": "c16961fda203155a314b0c75c13961c29e9ea7b0",
         "type": "github"
       },
       "original": {
@@ -245,11 +245,11 @@
     },
     "nixpkgs-unstable": {
       "locked": {
-        "lastModified": 1743039536,
-        "narHash": "sha256-O3GFPU0Uyv80LKVMMukVqrfxSzWKkBwQIHN2UnRSCZk=",
+        "lastModified": 1745215074,
+        "narHash": "sha256-JjkdlVI9BImDV5RrCiJk17cMSIqbefUXBM9trHRif+c=",
         "owner": "NixOS",
         "repo": "nixpkgs",
-        "rev": "0ab4a506520ea9247e673e9849ecf84c72f88be2",
+        "rev": "78e2cd1a1590f8c70b329cbc7d13bb2ab5b5a16c",
         "type": "github"
       },
       "original": {
@@ -278,11 +278,11 @@
         ]
       },
       "locked": {
-        "lastModified": 1741508717,
-        "narHash": "sha256-iQf1WdNxaApOFHIx4RLMRZ4f8g+8Xp0Z1/E/Mz2rLxY=",
+        "lastModified": 1744897914,
+        "narHash": "sha256-GIVU92o2TZBnKQXTb76zpQbWR4zjU2rFqWKNIIpXnqA=",
         "owner": "yaxitech",
         "repo": "ragenix",
-        "rev": "2a2bea99d74927e54adf53cbf113219def67d5c9",
+        "rev": "40f2e17ecaeab4d78ec323e96a04548c0aaa5223",
         "type": "github"
       },
       "original": {
@@ -317,11 +317,11 @@
         ]
       },
       "locked": {
-        "lastModified": 1743042789,
-        "narHash": "sha256-yPlxN0r3pQjUIwyX/qeWSTdpHjWy/AfmM0PK1bYkO18=",
+        "lastModified": 1745289264,
+        "narHash": "sha256-7nt+UJ7qaIUe2J7BdnEEph9n2eKEwxUwKS/QIr091uA=",
         "owner": "oxalica",
         "repo": "rust-overlay",
-        "rev": "b4d2dee9d16e7725b71969f28862ded3a94a7934",
+        "rev": "3b7171858c20d5293360042936058fb0c4cb93a9",
         "type": "github"
       },
       "original": {
@@ -342,11 +342,11 @@
         "nixpkgs-24_11": "nixpkgs-24_11"
       },
       "locked": {
-        "lastModified": 1742413977,
-        "narHash": "sha256-NkhM9GVu3HL+MiXtGD0TjuPCQ4GFVJPBZ8KyI2cFDGU=",
+        "lastModified": 1745164839,
+        "narHash": "sha256-+0T3pBcl5BD3qHB2nGfwwnRhb4EeNlJEI0BUm5fXPmE=",
         "owner": "simple-nixos-mailserver",
         "repo": "nixos-mailserver",
-        "rev": "b4fbffe79c00f19be94b86b4144ff67541613659",
+        "rev": "42651ce2d337921c99ae0c293ed9af49f7a89c6a",
         "type": "gitlab"
       },
       "original": {
@@ -378,11 +378,11 @@
         ]
       },
       "locked": {
-        "lastModified": 1742982148,
-        "narHash": "sha256-aRA6LSxjlbMI6MmMzi/M5WH/ynd8pK+vACD9za3MKLQ=",
+        "lastModified": 1744961264,
+        "narHash": "sha256-aRmUh0AMwcbdjJHnytg1e5h5ECcaWtIFQa6d9gI85AI=",
         "owner": "numtide",
         "repo": "treefmt-nix",
-        "rev": "61c88349bf6dff49fa52d7dfc39b21026c2a8881",
+        "rev": "8d404a69efe76146368885110f29a2ca3700bee6",
         "type": "github"
       },
       "original": {
diff --git a/hosts/by-name/server2/configuration.nix b/hosts/by-name/server2/configuration.nix
index ba0de14..24513f4 100644
--- a/hosts/by-name/server2/configuration.nix
+++ b/hosts/by-name/server2/configuration.nix
@@ -83,6 +83,26 @@
     };
     redlib.enable = true;
     rust-motd.enable = true;
+    sharkey = {
+      enable = true;
+      fqdn = "sharkey.vhack.eu";
+      settings = {
+        id = "aidx";
+
+        maxNoteLength = 8192;
+        maxFileSize = 1024 * 1024 * 1024;
+        proxyRemoteFiles = true;
+
+        # > At the suggestion of Sharkey maintainers,
+        # > this allows the server to run multiple workers
+        # > and without this (and postgres tuning), the instance runs slowly.
+        # Copied from: https://github.com/sodiboo/system/blob/b63c7b27f49043e8701b3ff5e1441cd27d5a2fff/sharkey.mod.nix#L21-L23
+        clusterLimit = 3;
+
+        signToActivityPubGet = true;
+        CheckActivityPubGetSigned = false;
+      };
+    };
     taskchampion-sync.enable = true;
     users.enable = true;
   };
diff --git a/hosts/by-name/server3/configuration.nix b/hosts/by-name/server3/configuration.nix
index 7f5bce5..17085e8 100644
--- a/hosts/by-name/server3/configuration.nix
+++ b/hosts/by-name/server3/configuration.nix
@@ -71,6 +71,7 @@
       enable = true;
       fqdn = "mail.vhack.eu";
       admin = "admin@vhack.eu";
+      initialAdminPassword = "$6$k/JGlODSgRyb6dG2$KV78QYipkS423WQQoQIcEWNFZdq4uuS5uIpJUNL1WLmXsD3b6KuLtt18TNU24Hnpup5TbMM5vtui/I.vGAybS/";
       security = {
         dkimKeys = let
           loadKey = name: {
@@ -84,19 +85,7 @@
         verificationMode = "strict";
       };
       openFirewall = true;
-      principals = [
-        {
-          class = "individual";
-          name = "soispha";
-          secret = "$2b$05$XX36sJuHNbTFvi8DFldscOeQBHahluSkiUqD9QGzQaET7NJusSuQW";
-          email = [
-            "soispha@vhack.eu"
-            "abuse@vhack.eu"
-            "postmaster@vhack.eu"
-            "admin@vhack.eu"
-          ];
-        }
-      ];
+      principals = null;
     };
     postgresql.enable = true;
     rust-motd.enable = true;
diff --git a/modules/by-name/co/constants/module.nix b/modules/by-name/co/constants/module.nix
index d6674bb..2115a37 100644
--- a/modules/by-name/co/constants/module.nix
+++ b/modules/by-name/co/constants/module.nix
@@ -25,6 +25,7 @@
   config.vhack.constants = {
     ids.uids = {
       # Keep this sorted with `!sort --numeric-sort --key=2 --field-separator="="`
+      systemd-coredump = 151; # GROUP
       opendkim = 221;
       mautrix-whatsapp = 222;
       etebase-server = 223;
@@ -43,16 +44,22 @@
       nscd = 330;
       sshd = 331;
       systemd-oom = 332;
+      resolvconf = 333; # GROUP
       nix-sync = 334;
       nextcloud = 335;
       redis-nextcloud = 336;
       taskchampion = 337;
+      stalwart-mail-certificates = 338; # GROUP
+      sharkey = 339;
+      redis-sharkey = 340;
 
       # As per the NixOS file, the uids should not be greater or equal to 400;
     };
     ids.gids = let
       inherit (config.vhack.constants.ids) uids;
     in {
+      # Please add your groups to the users and inherit them here.
+      # This avoids having an user/group id mismatch.
       inherit
         (uids)
         acme
@@ -76,12 +83,13 @@
         sshd
         stalwart-mail
         systemd-oom
+        sharkey
+        redis-sharkey
+        systemd-coredump # matches systemd-coredump user
+        resolvconf # This group is not matched to an user?
+        stalwart-mail-certificates # This group is used to connect nginx and stalwart-mail
         ;
 
-      # Keep this sorted with `!sort --numeric-sort --key=2 --field-separator="="`
-      systemd-coredump = 151; # matches systemd-coredump user
-      resolvconf = 333; # This group is not matched to an user?
-
       # The gid should match the uid. Thus should not be >= 400;
     };
   };
diff --git a/modules/by-name/ng/nginx/module.nix b/modules/by-name/ng/nginx/module.nix
index 1cb4e46..fa3337d 100644
--- a/modules/by-name/ng/nginx/module.nix
+++ b/modules/by-name/ng/nginx/module.nix
@@ -44,7 +44,10 @@ in {
     ];
 
     users = {
-      users.acme.uid = config.vhack.constants.ids.uids.acme;
+      users.acme = {
+        uid = config.vhack.constants.ids.uids.acme;
+        group = "acme";
+      };
       groups.acme.gid = config.vhack.constants.ids.gids.acme;
     };
 
diff --git a/modules/by-name/sh/sharkey/module.nix b/modules/by-name/sh/sharkey/module.nix
new file mode 100644
index 0000000..a2f5445
--- /dev/null
+++ b/modules/by-name/sh/sharkey/module.nix
@@ -0,0 +1,297 @@
+# Source: https://github.com/sodiboo/system/blob/b63c7b27f49043e8701b3ff5e1441cd27d5a2fff/sharkey/module.nix
+{
+  config,
+  lib,
+  pkgs,
+  vhackPackages,
+  ...
+}: let
+  cfg = config.vhack.sharkey;
+
+  createDB = cfg.database.host == "127.0.0.1" && cfg.database.createLocally;
+
+  settingsFormat = pkgs.formats.yaml {};
+  configFile = settingsFormat.generate "sharkey-config.yml" cfg.settings;
+in {
+  options.vhack.sharkey = {
+    enable = lib.mkEnableOption "sharkey";
+
+    fqdn = lib.mkOption {
+      description = "The fully qualified domain name of this instance.";
+      type = lib.types.str;
+      example = "sharkey.shonk.social";
+    };
+
+    package = lib.mkOption {
+      type = lib.types.package;
+      default = vhackPackages.sharkey;
+      defaultText = lib.literalExpression "vhackPackages.sharkey";
+      description = "Sharkey package to use.";
+    };
+
+    dataDirectory = lib.mkOption {
+      type = lib.types.path;
+      default = "/var/lib/sharkey";
+      description = "The directory where sharkey stores it's data.";
+
+      # This is already set in the package.
+      readOnly = true;
+    };
+
+    database = {
+      createLocally = lib.mkOption {
+        description = "Whether to enable local db creation.";
+        type = lib.types.bool;
+        default = true;
+      };
+
+      host = lib.mkOption {
+        type = lib.types.str;
+        default = "127.0.0.1";
+        description = "The database host.";
+      };
+
+      port = lib.mkOption {
+        type = lib.types.port;
+        default = 5432;
+        description = "The database port.";
+      };
+
+      name = lib.mkOption {
+        type = lib.types.str;
+        default = "sharkey";
+        description = "The database name in postgresql.";
+      };
+    };
+
+    settings = lib.mkOption {
+      inherit (settingsFormat) type;
+      default = {};
+      description = ''
+        Configuration for Sharkey, see
+        <link xlink:href="https://activitypub.software/TransFem-org/Sharkey/-/blob/develop/.config/example.yml"/>
+        for supported settings.
+      '';
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    environment.systemPackages = [cfg.package];
+
+    vhack = {
+      nginx.enable = true;
+
+      sharkey.settings = {
+        id = "aidx";
+
+        url = "https://${cfg.fqdn}/";
+        port = 5312;
+
+        db = {
+          inherit (cfg.database) host port;
+          db = cfg.database.name;
+          user = cfg.database.name;
+          pass = "sharkey-password";
+        };
+        redis = {
+          path = config.services.redis.servers."sharkey".unixSocket;
+        };
+      };
+
+      persist.directories = [
+        {
+          directory = "${config.services.redis.servers."sharkey".settings.dir}";
+          user = "sharkey";
+          group = "redis-sharey";
+          mode = "0770";
+        }
+        {
+          directory = "${cfg.dataDirectory}";
+          user = "sharkey";
+          group = "sharkey";
+          mode = "0770";
+        }
+      ];
+    };
+
+    services = {
+      nginx.virtualHosts."${cfg.fqdn}" = {
+        locations."/" = {
+          proxyPass = "http://127.0.0.1:${toString cfg.settings.port}";
+          proxyWebsockets = true;
+        };
+
+        # proxy_set_header Host $host;
+        # proxy_http_version 1.1;
+        # proxy_redirect off;
+        #
+        # # If it's behind another reverse proxy or CDN, remove the following.
+        # proxy_set_header X-Real-IP $remote_addr;
+        # proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+        # proxy_set_header X-Forwarded-Proto https;
+        #
+        # # For WebSocket
+        # proxy_set_header Upgrade $http_upgrade;
+        # proxy_set_header Connection $connection_upgrade;
+        #
+        # # Cache settings
+        # proxy_cache cache1;
+        # proxy_cache_lock on;
+        # proxy_cache_use_stale updating;
+        # proxy_force_ranges on;
+        # add_header X-Cache $upstream_cache_status;
+
+        enableACME = true;
+        forceSSL = true;
+      };
+
+      postgresql = lib.mkIf createDB {
+        enable = true;
+        settings.port = cfg.database.port;
+        ensureUsers = [
+          {
+            inherit (cfg.database) name;
+            ensureDBOwnership = true;
+          }
+        ];
+        ensureDatabases = [cfg.database.name];
+      };
+
+      redis = {
+        servers."sharkey" = {
+          enable = true;
+
+          user = "sharkey";
+
+          # Disable TCP listening. (We have a UNIX socket)
+          port = 0;
+          bind = null;
+
+          settings = {
+            protected-mode = true;
+            enable-protected-configs = false;
+            enable-debug-command = false;
+            enable-module-command = false;
+
+            supervised = "systemd";
+            stop-writes-on-bgsave-error = true;
+            sanitize-dump-payload = "clients";
+          };
+        };
+      };
+    };
+
+    systemd.services.postgresql.postStart = ''
+      $PSQL -tAc "ALTER ROLE ${cfg.database.name} WITH ENCRYPTED PASSWORD 'sharkey-password';"
+    '';
+
+    systemd.services.sharkey = {
+      requires =
+        [
+          "redis-sharkey.service"
+          "network-online.target"
+        ]
+        ++ lib.optionals createDB ["postgresql.service"];
+
+      after =
+        [
+          "redis-sharkey.service"
+          "network-online.target"
+        ]
+        ++ lib.optionals createDB ["postgresql.service"];
+
+      wantedBy = ["multi-user.target"];
+
+      environment = {
+        MISSKEY_CONFIG_YML = "${configFile}";
+        NODE_ENV = "production";
+      };
+
+      serviceConfig = {
+        Type = "simple";
+
+        StateDirectory = "sharkey";
+        StateDirectoryMode = "0700";
+        CacheDirectory = "sharkey";
+        RuntimeDirectory = "sharkey";
+        RuntimeDirectoryMode = "0700";
+        ExecStart = "${lib.getExe cfg.package} migrateandstart";
+
+        TimeoutSec = 60;
+        Restart = "no";
+
+        StandardOutput = "journal";
+        StandardError = "journal";
+        SyslogIdentifier = "sharkey";
+
+        User = "sharkey";
+        Group = "sharkey";
+
+        # Bind standard privileged ports
+        AmbientCapabilities = [];
+        CapabilityBoundingSet = [];
+
+        ReadWritePaths = [
+          "${cfg.dataDirectory}"
+        ];
+
+        # Hardening
+        DeviceAllow = [""];
+        LockPersonality = true;
+        # Probably needed for v8's JIT (crashes with it on).
+        MemoryDenyWriteExecute = false;
+        PrivateDevices = true;
+        PrivateUsers = true;
+        ProcSubset = "pid";
+        PrivateTmp = true;
+        ProtectClock = true;
+        ProtectControlGroups = true;
+        ProtectHome = true;
+        ProtectHostname = true;
+        ProtectKernelLogs = true;
+        ProtectKernelModules = true;
+        ProtectKernelTunables = true;
+        ProtectProc = "invisible";
+        ProtectSystem = "strict";
+        RestrictAddressFamilies = [
+          "AF_UNIX" # Local communication                        unix(7)
+          "AF_INET" # IPv4 Internet protocols                    ip(7)
+          "AF_INET6" # IPv6 Internet protocols                   ipv6(7)
+          # Needed for nodes `os.networkInterfaces()` function.
+          "AF_NETLINK" # Kernel user interface device            netlink(7)
+        ];
+        RestrictNamespaces = true;
+        RestrictRealtime = true;
+        RestrictSUIDSGID = true;
+        SystemCallArchitectures = "native";
+        SystemCallFilter = [
+          "@system-service"
+          "~@privileged"
+          "~@mount"
+        ];
+        UMask = "0077";
+      };
+    };
+
+    users = {
+      groups.sharkey = {
+        gid = config.vhack.constants.ids.gids.sharkey;
+      };
+      users.sharkey = {
+        isSystemUser = true;
+        group = "sharkey";
+        uid = config.vhack.constants.ids.uids.sharkey;
+        home = cfg.package;
+        packages = [cfg.package];
+      };
+
+      groups.redis-sharkey = {
+        gid = config.vhack.constants.ids.gids.redis-sharkey;
+      };
+      users.redis-sharkey = {
+        group = "redis-sharkey";
+        uid = config.vhack.constants.ids.uids.redis-sharkey;
+      };
+    };
+  };
+}
diff --git a/modules/by-name/st/stalwart-mail/module.nix b/modules/by-name/st/stalwart-mail/module.nix
index 1e39e81..396116d 100644
--- a/modules/by-name/st/stalwart-mail/module.nix
+++ b/modules/by-name/st/stalwart-mail/module.nix
@@ -10,6 +10,33 @@
 
   configFormat = pkgs.formats.toml {};
   configFile = configFormat.generate "stalwart-mail.toml" topCfg.settings;
+
+  hashedPassword = let
+    prefix = pre: lib.types.strMatching "^${lib.strings.escapeRegex pre}.*";
+  in
+    lib.types.oneOf [
+      (prefix "$argon2")
+      (prefix "$pbkdf2")
+      (prefix "$scrypt")
+      (prefix "$2") # bcrypt
+      (prefix "$6$") # sha-512
+      (prefix "$5$") # sha-256
+      (prefix "$sha1")
+      (prefix "$1") # md5
+      (prefix "_") # BSDi crypt
+      (prefix "{SHA}") # base64 sha
+      (prefix "{SSHA}") # base64 salted sha
+
+      # unix crypt
+      (prefix "{CRYPT}")
+      (prefix "{crypt}")
+
+      # Plain text
+      (prefix "{PLAIN}")
+      (prefix "{plain}")
+      (prefix "{CLEAR}")
+      (prefix "{clear}")
+    ];
 in {
   imports = [
     ./settings.nix
@@ -24,14 +51,20 @@ in {
       description = ''
         Email address to advertise as administrator. This is the address, where dkim, spv
         etc. refusal reports are sent to.
-
-        The format should be: `mailto:<name>@<domain>`
       '';
       type = lib.types.str;
-      example = "mailto:dmarc+rua@example.com";
+      example = "dmarc+rua@example.com";
       default = "";
     };
 
+    initialAdminPassword = lib.mkOption {
+      type = hashedPassword;
+      description = ''
+        The hash of the password for the admin account, used to bootstrap account
+        creation.
+      '';
+    };
+
     fqdn = lib.mkOption {
       type = lib.types.str;
       example = "mail.foss-syndicate.org";
@@ -61,7 +94,7 @@ in {
           };
 
           secret = lib.mkOption {
-            type = lib.types.str;
+            type = hashedPassword;
             description = ''
               Sets the password for the user account.
               Passwords can be stored hashed or in plain text (not recommended).
@@ -160,25 +193,16 @@ in {
         # However, this decision could obviously be reversed in the future. <2025-02-08>
         enable = false;
         inherit (cfg) package;
-        # dataDir = cfg.dataDirectory;
       };
 
-      # FIXME(@bpeetz): This is currently needed for a successful acme http-01 challenge.
-      # We could also use the DNS challenge. <2025-03-01>
       nginx.virtualHosts."${cfg.fqdn}" = {
-        enableACME = false;
-        extraConfig =
-          # This is copied directly from the nixos nginx module.
-          # Rule for legitimate ACME Challenge requests (like /.well-known/acme-challenge/xxxxxxxxx)
-          # We use ^~ here, so that we don't check any regexes (which could
-          # otherwise easily override this intended match accidentally).
-          ''
-            location ^~ /.well-known/acme-challenge/ {
-              root ${config.security.acme.certs.${cfg.fqdn}.webroot};
-              auth_basic off;
-              auth_request off;
-            }
-          '';
+        locations."/" = {
+          proxyPass = "http://${builtins.elemAt config.services.stalwart-mail.settings.server.listener.http.bind 0}";
+          recommendedProxySettings = true;
+        };
+
+        useACMEHost = "${cfg.fqdn}";
+        forceSSL = true;
       };
 
       redis = {
@@ -209,7 +233,7 @@ in {
     security.acme.certs = {
       "${cfg.fqdn}" = {
         domain = cfg.fqdn;
-        group = "stalwart-mail";
+        group = "stalwart-mail-certificates";
       };
     };
 
@@ -249,20 +273,31 @@ in {
     # service is restarted on a potentially large number of files.
     # That would cause unnecessary and unwanted delays.
     users = {
-      groups.stalwart-mail = {
-        gid = config.vhack.constants.ids.gids.stalwart-mail;
-      };
-      users.stalwart-mail = {
-        isSystemUser = true;
-        group = "stalwart-mail";
-        uid = config.vhack.constants.ids.uids.stalwart-mail;
-      };
-      groups.redis-stalwart-mail = {
-        gid = config.vhack.constants.ids.gids.redis-stalwart-mail;
+      groups = {
+        stalwart-mail = {
+          gid = config.vhack.constants.ids.gids.stalwart-mail;
+        };
+        stalwart-mail-certificates = {
+          gid = config.vhack.constants.ids.gids.stalwart-mail-certificates;
+        };
+        redis-stalwart-mail = {
+          gid = config.vhack.constants.ids.gids.redis-stalwart-mail;
+        };
       };
-      users.redis-stalwart-mail = {
-        group = "redis-stalwart-mail";
-        uid = config.vhack.constants.ids.uids.redis-stalwart-mail;
+      users = {
+        nginx = {
+          extraGroups = ["stalwart-mail-certificates"];
+        };
+        stalwart-mail = {
+          isSystemUser = true;
+          group = "stalwart-mail";
+          uid = config.vhack.constants.ids.uids.stalwart-mail;
+          extraGroups = ["stalwart-mail-certificates"];
+        };
+        redis-stalwart-mail = {
+          group = "redis-stalwart-mail";
+          uid = config.vhack.constants.ids.uids.redis-stalwart-mail;
+        };
       };
     };
 
@@ -321,8 +356,7 @@ in {
             ${lib.getExe cfg.package} --config="$CACHE_DIRECTORY/mutable_config_file.toml"
           '';
 
-          Restart = "on-failure";
-          RestartSec = 5;
+          Restart = "no";
 
           KillMode = "process";
           KillSignal = "SIGINT";
diff --git a/modules/by-name/st/stalwart-mail/settings.nix b/modules/by-name/st/stalwart-mail/settings.nix
index 17f045d..765d8db 100644
--- a/modules/by-name/st/stalwart-mail/settings.nix
+++ b/modules/by-name/st/stalwart-mail/settings.nix
@@ -100,7 +100,7 @@ in {
         from-name = "'TLS Report'";
         from-address = "'noreply-tls@${cfg.fqdn}'";
         org-name = "'Foss Syndicate Mail Handling'";
-        contact-info = "'${cfg.admin}'";
+        contact-info = "'mailto:${cfg.admin}'";
         send = "daily";
         max-size = 26214400; # 25 MiB
         sign = lib.mkIf (cfg.security != null) "'${cfg.fqdn}'";
@@ -110,7 +110,7 @@ in {
           from-name = "'DMARC Report'";
           from-address = "'noreply-dmarc@${cfg.fqdn}'";
           org-name = "'Foss Syndicate Mail Handling'";
-          contact-info = "'${cfg.admin}'";
+          contact-info = "'mailto:${cfg.admin}'";
           send = "weekly";
           max-size = 26214400; # 25MiB
           sign = lib.mkIf (cfg.security != null) "'${cfg.fqdn}'";
@@ -344,13 +344,13 @@ in {
       hostname = cfg.fqdn;
 
       listener = {
-        # TODO(@bpeetz): Add this <2025-02-08>
-        # # HTTP (used for jmap)
-        # "http" = {
-        #   bind = ["[::]:8080"];
-        #   protocol = "http";
-        #   tls.implicit = true;
-        # };
+        # HTTP (used for jmap)
+        "http" = {
+          bind = ["127.0.0.1:8112"];
+          protocol = "http";
+          # handled by ngnix
+          tls.implicit = false;
+        };
 
         # IMAP
         "imap" = {
@@ -406,11 +406,12 @@ in {
         certificate = "default";
       };
 
-      # TODO(@bpeetz): Configure that <2025-02-07>
-      # http = {
-      #   url = "";
-      #   allowed-endpoint = ["404"];
-      # };
+      http = {
+        url = "protocol + '://' + config_get('server.hostname') + ':' + local_port";
+
+        # We are behind a nginx proxy, and can thus trust this header.
+        use-x-forwarded = true;
+      };
 
       auto-ban = {
         # Ban if the same IP fails to login 10 times in a day
@@ -540,6 +541,11 @@ in {
       };
     };
 
+    authentication.fallback-admin = {
+      user = cfg.admin;
+      secret = cfg.initialAdminPassword;
+    };
+
     certificate = {
       "default" = {
         cert = "%{file:${config.security.acme.certs.${cfg.fqdn}.directory}/fullchain.pem}%";
diff --git a/modules/by-name/ta/taskchampion-sync/module.nix b/modules/by-name/ta/taskchampion-sync/module.nix
index 2fa0a3f..1870186 100644
--- a/modules/by-name/ta/taskchampion-sync/module.nix
+++ b/modules/by-name/ta/taskchampion-sync/module.nix
@@ -4,6 +4,7 @@
   ...
 }: let
   cfg = config.vhack.taskchampion-sync;
+  dataDirectory = "/var/lib/taskchampion-sync-server";
 in {
   options.vhack.taskchampion-sync = {
     enable = lib.mkEnableOption "taskchampion-sync";
@@ -16,13 +17,18 @@ in {
     };
 
     vhack.persist.directories = [
-      "/var/lib/taskchampion-sync-server"
+      {
+        directory = dataDirectory;
+        user = "taskchampion";
+        group = "taskchampion";
+        mode = "0700";
+      }
     ];
 
     services.taskchampion-sync-server = {
       enable = true;
       openFirewall = true;
-      dataDir = "/var/lib/taskchampion-sync-server";
+      dataDir = dataDirectory;
     };
   };
 }
diff --git a/pkgs/by-name/sh/sharkey/package.nix b/pkgs/by-name/sh/sharkey/package.nix
new file mode 100644
index 0000000..22261fa
--- /dev/null
+++ b/pkgs/by-name/sh/sharkey/package.nix
@@ -0,0 +1,4 @@
+{pkgsUnstable}:
+# NOTE(@bpeetz): The package was written for unstable, and I don't see a reason to migrate
+# it back, considering that 25.05 is right around the corner. <2025-04-22>
+pkgsUnstable.callPackage ./unstable_package.nix {}
diff --git a/pkgs/by-name/sh/sharkey/unstable_package.nix b/pkgs/by-name/sh/sharkey/unstable_package.nix
new file mode 100644
index 0000000..3922ca0
--- /dev/null
+++ b/pkgs/by-name/sh/sharkey/unstable_package.nix
@@ -0,0 +1,176 @@
+# Source: https://github.com/sodiboo/system/blob/b63c7b27f49043e8701b3ff5e1441cd27d5a2fff/sharkey/package.nix
+{
+  lib,
+  stdenv,
+  fetchFromGitLab,
+  # Build time
+  makeWrapper,
+  copyDesktopItems,
+  jq,
+  moreutils,
+  cacert,
+  python3,
+  pkg-config,
+  # Run time
+  bash,
+  jemalloc,
+  ffmpeg-headless,
+  nodejs,
+  pnpm_9,
+  glib,
+  vips,
+  pixman,
+  pango,
+  cairo,
+}:
+stdenv.mkDerivation (finalAttrs: {
+  pname = "sharkey";
+  version = "2025.2.2";
+
+  src = fetchFromGitLab {
+    owner = "TransFem-org";
+    repo = "Sharkey";
+    domain = "activitypub.software";
+    rev = finalAttrs.version;
+    hash = "sha256-KVr4KLtJ22LEk94GuxeTk8/GcFs7oU/gkoVTvrgbYBg=";
+    fetchSubmodules = true;
+  };
+
+  pnpmDeps = stdenv.mkDerivation {
+    pname = "${finalAttrs.pname}-pnpm-deps";
+    inherit (finalAttrs) src version;
+
+    nativeBuildInputs = [
+      jq
+      moreutils
+      pnpm_9
+      cacert
+    ];
+
+    # https://github.com/NixOS/nixpkgs/blob/763e59ffedb5c25774387bf99bc725df5df82d10/pkgs/applications/misc/pot/default.nix#L56
+    installPhase = ''
+      export HOME=$(mktemp --directory)
+
+      pnpm config set store-dir $out
+      pnpm config set side-effects-cache false
+      pnpm install --force --frozen-lockfile --ignore-scripts
+    '';
+
+    fixupPhase = ''
+      rm --recursive --force $out/v3/tmp
+      for f in $(find $out -name "*.json"); do
+        sed --in-place --regexp-extended --expression='s/"checkedAt":[0-9]+,//g' "$f"
+        jq --sort-keys . "$f" | sponge "$f"
+      done
+    '';
+
+    dontBuild = true;
+    outputHashMode = "recursive";
+    outputHash = "sha256-XWcDchvrYSJr0s/DMb8FIEK7MdE6aC2bAbrW88Ig4ug=";
+  };
+
+  nativeBuildInputs = [
+    copyDesktopItems
+    pnpm_9
+    nodejs
+    makeWrapper
+    python3
+    pkg-config
+  ];
+
+  buildInputs = [
+    glib
+    vips
+
+    pixman
+    pango
+    cairo
+  ];
+
+  configurePhase = ''
+    runHook preConfigure
+
+    export HOME=$(mktemp --directory)
+    export STORE_PATH=$(mktemp --directory)
+
+    export npm_config_nodedir=${nodejs}
+
+    cp --no-target-directory --recursive "$pnpmDeps" "$STORE_PATH"
+    chmod --recursive +w "$STORE_PATH"
+
+    pnpm config set store-dir "$STORE_PATH"
+    pnpm install --offline --frozen-lockfile --ignore-scripts
+
+    (
+      cd node_modules/.pnpm/node_modules/v-code-diff
+      pnpm run postinstall
+    )
+    (
+      cd node_modules/.pnpm/node_modules/re2
+      pnpm run rebuild
+    )
+    (
+      cd node_modules/.pnpm/node_modules/sharp
+      pnpm run install
+    )
+    (
+      cd node_modules/.pnpm/node_modules/canvas
+      pnpm run install
+    )
+
+    runHook postConfigure
+  '';
+
+  buildPhase = ''
+    runHook preBuild
+
+    pnpm build
+
+    runHook postBuild
+  '';
+
+  installPhase = let
+    libPath = lib.makeLibraryPath [
+      jemalloc
+      ffmpeg-headless
+      stdenv.cc.cc.lib
+    ];
+
+    binPath = lib.makeBinPath [
+      bash
+      pnpm_9
+      nodejs
+    ];
+  in
+    # bash
+    ''
+      runHook preInstall
+
+      mkdir --parents $out/Sharkey
+
+      ln --symbolic /var/lib/sharkey $out/Sharkey/files
+      ln --symbolic /run/sharkey $out/Sharkey/.config
+      cp --recursive * $out/Sharkey
+
+      # We cannot `--set` the PATH, because sharkey runs shellscripts at start (and maybe
+      # at other times), which need these things.
+      makeWrapper ${lib.getExe pnpm_9} $out/bin/sharkey \
+        --chdir $out/Sharkey \
+        --prefix PATH : ${binPath} \
+        --prefix LD_LIBRARY_PATH : ${libPath}
+
+      runHook postInstall
+    '';
+
+  passthru = {
+    inherit (finalAttrs) pnpmDeps;
+  };
+
+  meta = {
+    description = "🌎 A Sharkish microblogging platform 🚀";
+    homepage = "https://joinsharkey.org";
+    license = lib.licenses.gpl3Only;
+    platforms = ["x86_64-linux" "aarch64-linux"];
+    mainProgram = "sharkey";
+  };
+})
diff --git a/pkgs/by-name/st/stalwart-mail-free/package.nix b/pkgs/by-name/st/stalwart-mail-free/package.nix
index bb2c1db..19405c7 100644
--- a/pkgs/by-name/st/stalwart-mail-free/package.nix
+++ b/pkgs/by-name/st/stalwart-mail-free/package.nix
@@ -24,7 +24,7 @@ in
               } "stalwart-mail passthru";
 
               useFetchCargoVendor = true;
-              cargoHash = "sha256-Qg01QXP/ImRCUw3aXcZbnM1hysHUwozCdQ7LecjUa0o=";
+              cargoHash = "sha256-FCRsmH9ZGYaf9ss3ECjZQkknIxAaDj9cedW32dWmBGY=";
 
               # The tests should check if this works.
               # And this shaves of around 50% of the build time.
@@ -40,7 +40,7 @@ in
                 (prev.postUnpack or "")
                 + ''
                   cp --recursive "${mail-send}" ./source/crates/mail-send
-                  chmod -R +w "./source/crates/mail-send"
+                  chmod --recursive +w "./source/crates/mail-send"
                 '';
 
               cargoPatches =
diff --git a/tests/by-name/em/email-dns/nodes/mail_server.nix b/tests/by-name/em/email-dns/nodes/mail_server.nix
index a8c528a..279d289 100644
--- a/tests/by-name/em/email-dns/nodes/mail_server.nix
+++ b/tests/by-name/em/email-dns/nodes/mail_server.nix
@@ -13,7 +13,8 @@
       extraModules
       ++ [
         ../../../../../modules
-        ./acme/client.nix
+        ../../../../common/acme/client.nix
+        ../../../../common/dns/client.nix
       ];
 
     environment.systemPackages = [
@@ -21,12 +22,7 @@
       pkgs.openssl
     ];
 
-    networking.nameservers = lib.mkForce [
-      nodes.name_server.networking.primaryIPAddress
-      nodes.name_server.networking.primaryIPv6Address
-    ];
-
-    age.identityPaths = ["${../secrets/hostKey}"];
+    age.identityPaths = ["${../../../../common/email/hostKey}"];
 
     vhack = {
       stalwart-mail = {
@@ -36,8 +32,8 @@
         security = {
           dkimKeys = let
             loadKey = name: {
-              dkimPublicKey = builtins.readFile (../secrets/dkim + "/${name}/public");
-              dkimPrivateKeyPath = ../secrets/dkim + "/${name}/private.age";
+              dkimPublicKey = builtins.readFile (../../../../common/email/dkim + "/${name}/public");
+              dkimPrivateKeyPath = ../../../../common/email/dkim + "/${name}/private.age";
               keyAlgorithm = "ed25519-sha256";
             };
           in {
diff --git a/tests/by-name/em/email-dns/nodes/name_server.nix b/tests/by-name/em/email-dns/nodes/name_server.nix
index ef657f4..d9d3617 100644
--- a/tests/by-name/em/email-dns/nodes/name_server.nix
+++ b/tests/by-name/em/email-dns/nodes/name_server.nix
@@ -139,14 +139,10 @@ in {
     extraModules
     ++ [
       ../../../../../modules
-      ./acme/client.nix
+      ../../../../common/acme/client.nix
+      ../../../../common/dns/server.nix
     ];
 
-  networking.nameservers = lib.mkForce [
-    nodes.name_server.networking.primaryIPAddress
-    nodes.name_server.networking.primaryIPv6Address
-  ];
-
   services.nginx = {
     logError = "stderr debug";
     virtualHosts = let
@@ -175,145 +171,121 @@ in {
     nginx = {
       enable = true;
     };
-    dns = {
-      enable = true;
-      openFirewall = true;
-      interfaces = [
-        nodes.name_server.networking.primaryIPAddress
-        nodes.name_server.networking.primaryIPv6Address
-      ];
-
-      zones = let
-        stsZone = {
-          SOA = {
-            nameServer = "ns";
-            adminEmail = "admin@server.com";
-            serial = 2025012301;
-          };
+    dns.zones = let
+      stsZone = {
+        SOA = {
+          nameServer = "ns";
+          adminEmail = "admin@server.com";
+          serial = 2025012301;
+        };
 
-          useOrigin = false;
+        useOrigin = false;
 
-          A = [
-            nodes.name_server.networking.primaryIPAddress
-          ];
-          AAAA = [
-            nodes.name_server.networking.primaryIPv6Address
-          ];
+        A = [
+          nodes.name_server.networking.primaryIPAddress
+        ];
+        AAAA = [
+          nodes.name_server.networking.primaryIPv6Address
+        ];
+      };
+    in {
+      "arpa" = {
+        SOA = {
+          nameServer = "ns";
+          adminEmail = "admin@server.com";
+          serial = 2025012301;
         };
-      in {
-        "arpa" = {
-          SOA = {
-            nameServer = "ns";
-            adminEmail = "admin@server.com";
-            serial = 2025012301;
-          };
-          useOrigin = false;
+        useOrigin = false;
 
-          PTR = [
-            {
-              name = "acme.test";
-              ip.v4 = nodes.acme.networking.primaryIPAddress;
-            }
-            {
-              name = "acme.test";
-              ip.v6 = nodes.acme.networking.primaryIPv6Address;
-            }
+        PTR = [
+          {
+            name = "acme.test";
+            ip.v4 = nodes.acme.networking.primaryIPAddress;
+          }
+          {
+            name = "acme.test";
+            ip.v6 = nodes.acme.networking.primaryIPv6Address;
+          }
 
-            {
-              name = "alice.com";
-              ip.v4 = nodes.alice.networking.primaryIPAddress;
-            }
-            {
-              name = "alice.com";
-              ip.v6 = nodes.alice.networking.primaryIPv6Address;
-            }
+          {
+            name = "alice.com";
+            ip.v4 = nodes.alice.networking.primaryIPAddress;
+          }
+          {
+            name = "alice.com";
+            ip.v6 = nodes.alice.networking.primaryIPv6Address;
+          }
 
-            {
-              name = "bob";
-              ip.v4 = nodes.bob.networking.primaryIPAddress;
-            }
-            {
-              name = "bob";
-              ip.v6 = nodes.bob.networking.primaryIPv6Address;
-            }
+          {
+            name = "bob";
+            ip.v4 = nodes.bob.networking.primaryIPAddress;
+          }
+          {
+            name = "bob";
+            ip.v6 = nodes.bob.networking.primaryIPv6Address;
+          }
 
-            {
-              name = "mail1.server.com";
-              ip.v4 = nodes.mail1_server.networking.primaryIPAddress;
-            }
-            {
-              name = "mail1.server.com";
-              ip.v6 = nodes.mail1_server.networking.primaryIPv6Address;
-            }
+          {
+            name = "mail1.server.com";
+            ip.v4 = nodes.mail1_server.networking.primaryIPAddress;
+          }
+          {
+            name = "mail1.server.com";
+            ip.v6 = nodes.mail1_server.networking.primaryIPv6Address;
+          }
 
-            {
-              name = "mail2.server.com";
-              ip.v4 = nodes.mail2_server.networking.primaryIPAddress;
-            }
-            {
-              name = "mail2.server.com";
-              ip.v6 = nodes.mail2_server.networking.primaryIPv6Address;
-            }
+          {
+            name = "mail2.server.com";
+            ip.v4 = nodes.mail2_server.networking.primaryIPAddress;
+          }
+          {
+            name = "mail2.server.com";
+            ip.v6 = nodes.mail2_server.networking.primaryIPv6Address;
+          }
 
-            {
-              name = "ns.server.com";
-              ip.v4 = nodes.name_server.networking.primaryIPAddress;
-            }
-            {
-              name = "ns.server.com";
-              ip.v6 = nodes.name_server.networking.primaryIPv6Address;
-            }
-          ];
-        };
-
-        "alice.com" = mkZone "alice" nodes lib nodes.mail2_server.vhack.stalwart-mail;
-        "mta-sts.alice.com" = stsZone;
-        "bob.com" = mkZone "bob" nodes lib nodes.mail1_server.vhack.stalwart-mail;
-        "mta-sts.bob.com" = stsZone;
-        "mail1.server.com" = mkServerZone "mail1" nodes lib;
-        "mail2.server.com" = mkServerZone "mail2" nodes lib;
-        "ns.server.com" = {
-          SOA = {
-            nameServer = "ns";
-            adminEmail = "admin@server.com";
-            serial = 2025012301;
-          };
-          useOrigin = false;
+          {
+            name = "ns.server.com";
+            ip.v4 = nodes.name_server.networking.primaryIPAddress;
+          }
+          {
+            name = "ns.server.com";
+            ip.v6 = nodes.name_server.networking.primaryIPv6Address;
+          }
+        ];
+      };
 
-          A = [
-            nodes.name_server.networking.primaryIPAddress
-          ];
-          AAAA = [
-            nodes.name_server.networking.primaryIPv6Address
-          ];
+      "alice.com" = mkZone "alice" nodes lib nodes.mail2_server.vhack.stalwart-mail;
+      "mta-sts.alice.com" = stsZone;
+      "bob.com" = mkZone "bob" nodes lib nodes.mail1_server.vhack.stalwart-mail;
+      "mta-sts.bob.com" = stsZone;
+      "mail1.server.com" = mkServerZone "mail1" nodes lib;
+      "mail2.server.com" = mkServerZone "mail2" nodes lib;
+      "ns.server.com" = {
+        SOA = {
+          nameServer = "ns";
+          adminEmail = "admin@server.com";
+          serial = 2025012301;
         };
-        "acme.test" = {
-          SOA = {
-            nameServer = "ns";
-            adminEmail = "admin@server.com";
-            serial = 2025012301;
-          };
-          useOrigin = false;
+        useOrigin = false;
 
-          A = [
-            nodes.acme.networking.primaryIPAddress
-          ];
-          AAAA = [
-            nodes.acme.networking.primaryIPv6Address
-          ];
+        A = [
+          nodes.name_server.networking.primaryIPAddress
+        ];
+        AAAA = [
+          nodes.name_server.networking.primaryIPv6Address
+        ];
+      };
+      "server.com" = {
+        SOA = {
+          nameServer = "ns";
+          adminEmail = "admin@server.com";
+          serial = 2025012301;
         };
-        "server.com" = {
-          SOA = {
-            nameServer = "ns";
-            adminEmail = "admin@server.com";
-            serial = 2025012301;
-          };
 
-          useOrigin = false;
-          NS = [
-            "ns.server.com."
-          ];
-        };
+        useOrigin = false;
+        NS = [
+          "ns.server.com."
+        ];
       };
     };
   };
diff --git a/tests/by-name/em/email-dns/nodes/user.nix b/tests/by-name/em/email-dns/nodes/user.nix
index e4db347..fba02ce 100644
--- a/tests/by-name/em/email-dns/nodes/user.nix
+++ b/tests/by-name/em/email-dns/nodes/user.nix
@@ -8,7 +8,8 @@
     ...
   }: {
     imports = [
-      ./acme/client.nix
+      ../../../../common/acme/client.nix
+      ../../../../common/dns/client.nix
     ];
 
     environment.systemPackages = [
@@ -20,11 +21,6 @@
       pkgs.openssl
     ];
 
-    networking.nameservers = lib.mkForce [
-      nodes.name_server.networking.primaryIPAddress
-      nodes.name_server.networking.primaryIPv6Address
-    ];
-
     users.users."${user}" = {isNormalUser = true;};
 
     systemd.tmpfiles.rules = [
diff --git a/tests/by-name/em/email-dns/test.nix b/tests/by-name/em/email-dns/test.nix
index 32447ae..f0399a5 100644
--- a/tests/by-name/em/email-dns/test.nix
+++ b/tests/by-name/em/email-dns/test.nix
@@ -31,9 +31,9 @@ in
         lib,
         ...
       }: {
-        imports = [./nodes/acme];
-        networking.nameservers = lib.mkForce [
-          nodes.name_server.networking.primaryIPAddress
+        imports = [
+          ../../../common/acme/server.nix
+          ../../../common/dns/client.nix
         ];
       };
 
@@ -44,7 +44,7 @@ in
         {
           class = "individual";
           name = "bob";
-          secret = "bob-password";
+          secret = "{PLAIN}bob-password";
           email = ["bob@bob.com"];
         };
 
@@ -53,7 +53,7 @@ in
         {
           class = "individual";
           name = "alice";
-          secret = "alice-password";
+          secret = "{PLAIN}alice-password";
           email = ["alice@alice.com"];
         };
 
@@ -89,7 +89,8 @@ in
           exit 1
         }
       '';
-      inherit (pkgs) lib;
+
+      acme_scripts = import ../../../common/acme/scripts.nix {inherit pkgs;};
     in
       /*
       python
@@ -121,30 +122,7 @@ in
 
         with subtest("Add pebble ca key to all services"):
           for node in [name_server, mail1_server, mail2_server, alice, bob]:
-            node.succeed("${pkgs.writeShellScript "fetch-and-set-ca" ''
-          set -xe
-
-          # Fetch the randomly generated ca certificate
-          curl https://acme.test:15000/roots/0 > /tmp/ca.crt
-          curl https://acme.test:15000/intermediates/0 >> /tmp/ca.crt
-
-          # Append it to the various system stores
-          # The file paths are from <nixpgks>/modules/security/ca.nix
-          for cert_path in "ssl/certs/ca-certificates.crt" "ssl/certs/ca-bundle.crt" "pki/tls/certs/ca-bundle.crt"; do
-            cert_path="/etc/$cert_path"
-
-            mv "$cert_path" "$cert_path.old"
-            cat "$cert_path.old" > "$cert_path"
-            cat /tmp/ca.crt >> "$cert_path"
-          done
-
-          export NIX_SSL_CERT_FILE=/tmp/ca.crt
-          export SSL_CERT_FILE=/tmp/ca.crt
-
-          # TODO
-          # # P11-Kit trust source.
-          # environment.etc."ssl/trust-source".source = "$${cacertPackage.p11kit}/etc/ssl/trust-source";
-        ''}")
+            node.succeed("${acme_scripts.add_pebble_acme_ca}")
 
         with subtest("Both mailserver successfully started all services"):
           import json
diff --git a/tests/by-name/em/email-http/nodes/mail_server.nix b/tests/by-name/em/email-http/nodes/mail_server.nix
new file mode 100644
index 0000000..e94c4e9
--- /dev/null
+++ b/tests/by-name/em/email-http/nodes/mail_server.nix
@@ -0,0 +1,57 @@
+{
+  extraModules,
+  pkgs,
+  vhackPackages,
+}: {
+  mkMailServer = serverName: principal: {
+    config,
+    lib,
+    nodes,
+    ...
+  }: {
+    imports =
+      extraModules
+      ++ [
+        ../../../../../modules
+        ../../../../common/acme/client.nix
+      ];
+
+    environment.systemPackages = [
+      pkgs.bind
+      pkgs.openssl
+    ];
+
+    networking.nameservers = lib.mkForce [
+      nodes.name_server.networking.primaryIPAddress
+      nodes.name_server.networking.primaryIPv6Address
+    ];
+
+    age.identityPaths = ["${../../../../common/email/hostKey}"];
+
+    vhack = {
+      stalwart-mail = {
+        enable = true;
+        fqdn = "${serverName}.server.com";
+        admin = "admin@${serverName}.server.com";
+        security = {
+          dkimKeys = let
+            loadKey = name: {
+              dkimPublicKey = builtins.readFile (../../../../common/email/dkim + "/${name}/public");
+              dkimPrivateKeyPath = ../../../../common/email/dkim + "/${name}/private.age";
+              keyAlgorithm = "ed25519-sha256";
+            };
+          in {
+            "mail.server.com" = loadKey "mail1.server.com";
+            "bob.com" = loadKey "bob.com";
+          };
+          verificationMode = "strict";
+        };
+        openFirewall = true;
+        principals =
+          if principal == null
+          then null
+          else [principal];
+      };
+    };
+  };
+}
diff --git a/tests/by-name/em/email-http/nodes/user.nix b/tests/by-name/em/email-http/nodes/user.nix
new file mode 100644
index 0000000..73b9ff7
--- /dev/null
+++ b/tests/by-name/em/email-http/nodes/user.nix
@@ -0,0 +1,26 @@
+{
+  pkgs,
+  vhackPackages,
+}: {
+  mkUser = user: serverName: {
+    nodes,
+    lib,
+    ...
+  }: {
+    imports = [
+      ../../../../common/acme/client.nix
+    ];
+
+    environment.systemPackages = [
+      pkgs.bind
+      pkgs.openssl
+    ];
+
+    networking.nameservers = lib.mkForce [
+      nodes.name_server.networking.primaryIPAddress
+      nodes.name_server.networking.primaryIPv6Address
+    ];
+
+    users.users."${user}" = {isNormalUser = true;};
+  };
+}
diff --git a/tests/by-name/em/email-http/test.nix b/tests/by-name/em/email-http/test.nix
new file mode 100644
index 0000000..f508b9f
--- /dev/null
+++ b/tests/by-name/em/email-http/test.nix
@@ -0,0 +1,115 @@
+{
+  nixos-lib,
+  pkgsUnstable,
+  nixpkgs-unstable,
+  vhackPackages,
+  pkgs,
+  extraModules,
+  nixLib,
+  ...
+}: let
+  mail_server = import ./nodes/mail_server.nix {inherit extraModules pkgs vhackPackages;};
+  inherit (mail_server) mkMailServer;
+  user = import ./nodes/user.nix {inherit pkgs vhackPackages;};
+  inherit (user) mkUser;
+in
+  nixos-lib.runTest {
+    hostPkgs = pkgs; # the Nixpkgs package set used outside the VMs
+
+    name = "email-http";
+
+    node = {
+      specialArgs = {inherit pkgsUnstable vhackPackages nixpkgs-unstable nixLib;};
+
+      # Use the nixpkgs as constructed by the `nixpkgs.*` options
+      pkgs = null;
+    };
+
+    nodes = {
+      acme = {
+        nodes,
+        lib,
+        ...
+      }: {
+        imports = [
+          ../../../common/acme/server.nix
+          ../../../common/dns/client.nix
+        ];
+      };
+
+      name_server = {nodes, ...}: {
+        imports =
+          extraModules
+          ++ [
+            ../../../common/acme/client.nix
+            ../../../common/dns/server.nix
+          ];
+
+        vhack.dns.zones = {
+          "mail.server.com" = {
+            SOA = {
+              nameServer = "ns";
+              adminEmail = "admin@server.com";
+              serial = 2025012301;
+            };
+            useOrigin = false;
+
+            A = [
+              nodes.mail_server.networking.primaryIPAddress
+            ];
+            AAAA = [
+              nodes.mail_server.networking.primaryIPv6Address
+            ];
+          };
+        };
+      };
+
+      mail_server = mkMailServer "mail" null;
+
+      bob = mkUser "bob" "mail";
+    };
+
+    # TODO(@bpeetz): This test should also test the http JMAP features of stalwart-mail. <2025-04-12>
+    testScript = _: let
+      acme_scripts = import ../../../common/acme/scripts.nix {inherit pkgs;};
+    in
+      /*
+      python
+      */
+      ''
+        # Start dependencies for the other services
+        acme.start()
+        acme.wait_for_unit("pebble.service")
+        name_server.start()
+        name_server.wait_for_unit("nsd.service")
+
+        # Start the actual testing machines
+        start_all()
+
+        mail_server.wait_for_unit("stalwart-mail.service")
+        mail_server.wait_for_open_port(993) # imap
+        mail_server.wait_for_open_port(465) # smtp
+
+        bob.wait_for_unit("multi-user.target")
+
+        with subtest("Add pebble ca key to all services"):
+          for node in [name_server, mail_server, bob]:
+            node.wait_for_unit("network-online.target")
+            node.succeed("${acme_scripts.add_pebble_acme_ca}")
+
+        with subtest("The mailserver successfully started all services"):
+          import json
+          def all_services_running(host):
+            (status, output) = host.systemctl("list-units --state=failed --plain --no-pager --output=json")
+            host_failed = json.loads(output)
+            assert len(host_failed) == 0, f"Expected zero failing services, but found: {json.dumps(host_failed, indent=4)}"
+          all_services_running(mail_server)
+
+        with subtest("Bob can use the self-service interface"):
+          bob.succeed("${pkgs.writeShellScript "check-self-service" ''
+          curl mail.server.com --location --output /home/bob/output.html;
+        ''}")
+
+        bob.copy_from_vm("/home/bob", "")
+      '';
+  }
diff --git a/tests/by-name/em/email-ip/test.nix b/tests/by-name/em/email-ip/test.nix
index 688cd8f..dabc404 100644
--- a/tests/by-name/em/email-ip/test.nix
+++ b/tests/by-name/em/email-ip/test.nix
@@ -113,13 +113,13 @@ in
               {
                 class = "individual";
                 name = "alice";
-                secret = "alice-password";
+                secret = "{PLAIN}alice-password";
                 email = ["alice@${domain}"];
               }
               {
                 class = "individual";
                 name = "bob";
-                secret = "bob-password";
+                secret = "{PLAIN}bob-password";
                 email = ["bob@${domain}"];
               }
             ];
diff --git a/tests/by-name/sh/sharkey/test.nix b/tests/by-name/sh/sharkey/test.nix
new file mode 100644
index 0000000..40efe17
--- /dev/null
+++ b/tests/by-name/sh/sharkey/test.nix
@@ -0,0 +1,118 @@
+{
+  nixos-lib,
+  pkgsUnstable,
+  nixpkgs-unstable,
+  vhackPackages,
+  pkgs,
+  extraModules,
+  nixLib,
+  ...
+}:
+nixos-lib.runTest {
+  hostPkgs = pkgs; # the Nixpkgs package set used outside the VMs
+
+  name = "sharkey";
+
+  node = {
+    specialArgs = {inherit pkgsUnstable extraModules vhackPackages nixpkgs-unstable nixLib;};
+
+    # Use the nixpkgs as constructed by the `nixpkgs.*` options
+    pkgs = null;
+  };
+
+  nodes = {
+    acme = {...}: {
+      imports = [
+        ../../../common/acme/server.nix
+        ../../../common/dns/client.nix
+      ];
+    };
+    name_server = {nodes, ...}: {
+      imports =
+        extraModules
+        ++ [
+          ../../../common/acme/client.nix
+          ../../../common/dns/server.nix
+        ];
+
+      vhack.dns.zones = {
+        "sharkey.server" = {
+          SOA = {
+            nameServer = "ns";
+            adminEmail = "admin@server.com";
+            serial = 2025012301;
+          };
+          useOrigin = false;
+
+          A = [
+            nodes.server.networking.primaryIPAddress
+          ];
+          AAAA = [
+            nodes.server.networking.primaryIPv6Address
+          ];
+        };
+      };
+    };
+
+    server = {config, ...}: {
+      imports =
+        extraModules
+        ++ [
+          ../../../../modules
+          ../../../common/acme/client.nix
+          ../../../common/dns/client.nix
+        ];
+
+      vhack = {
+        persist.enable = true;
+        nginx.enable = true;
+        sharkey = {
+          enable = true;
+          fqdn = "sharkey.server";
+        };
+      };
+    };
+
+    client = {...}: {
+      imports = [
+        ../../../common/acme/client.nix
+        ../../../common/dns/client.nix
+      ];
+    };
+  };
+
+  testScript = {nodes, ...}: let
+    acme_scripts = import ../../../common/acme/scripts.nix {inherit pkgs;};
+  in
+    /*
+    python
+    */
+    ''
+      # Start dependencies for the other services
+      acme.start()
+      acme.wait_for_unit("pebble.service")
+      name_server.start()
+      name_server.wait_for_unit("nsd.service")
+
+      # Start the actual testing machines
+      start_all()
+
+
+      with subtest("Add pebble ca key to all services"):
+        for node in [name_server, server, client]:
+          node.wait_for_unit("network-online.target")
+          node.succeed("${acme_scripts.add_pebble_acme_ca}")
+
+      server.wait_for_unit("sharkey.service")
+
+      with subtest("All services running"):
+        import json
+        def all_services_running(host):
+          (status, output) = host.systemctl("list-units --state=failed --plain --no-pager --output=json")
+          host_failed = json.loads(output)
+          assert len(host_failed) == 0, f"Expected zero failing services, but found: {json.dumps(host_failed, indent=4)}"
+        all_services_running(server)
+
+      client.wait_until_succeeds("curl --silent https://sharkey.server | grep 'Thank you for using Sharkey!'")
+    '';
+}
diff --git a/tests/by-name/em/email-dns/nodes/acme/certs/generate b/tests/common/acme/certs/generate
index 0d6258e..0d6258e 100755
--- a/tests/by-name/em/email-dns/nodes/acme/certs/generate
+++ b/tests/common/acme/certs/generate
diff --git a/tests/by-name/em/email-dns/nodes/acme/certs/generate.ca b/tests/common/acme/certs/generate.ca
index 92832c5..92832c5 100755
--- a/tests/by-name/em/email-dns/nodes/acme/certs/generate.ca
+++ b/tests/common/acme/certs/generate.ca
diff --git a/tests/by-name/em/email-dns/nodes/acme/certs/generate.client b/tests/common/acme/certs/generate.client
index 5930298..5930298 100755
--- a/tests/by-name/em/email-dns/nodes/acme/certs/generate.client
+++ b/tests/common/acme/certs/generate.client
diff --git a/tests/by-name/em/email-dns/nodes/acme/certs/output/acme.test.cert.pem b/tests/common/acme/certs/output/acme.test.cert.pem
index 687101d..687101d 100644
--- a/tests/by-name/em/email-dns/nodes/acme/certs/output/acme.test.cert.pem
+++ b/tests/common/acme/certs/output/acme.test.cert.pem
diff --git a/tests/by-name/em/email-dns/nodes/acme/certs/output/acme.test.key.pem b/tests/common/acme/certs/output/acme.test.key.pem
index 06195b8..06195b8 100644
--- a/tests/by-name/em/email-dns/nodes/acme/certs/output/acme.test.key.pem
+++ b/tests/common/acme/certs/output/acme.test.key.pem
diff --git a/tests/by-name/em/email-dns/nodes/acme/certs/output/acme.test.template b/tests/common/acme/certs/output/acme.test.template
index 320a170..320a170 100644
--- a/tests/by-name/em/email-dns/nodes/acme/certs/output/acme.test.template
+++ b/tests/common/acme/certs/output/acme.test.template
diff --git a/tests/by-name/em/email-dns/nodes/acme/certs/output/ca.cert.pem b/tests/common/acme/certs/output/ca.cert.pem
index 0fa9d14..0fa9d14 100644
--- a/tests/by-name/em/email-dns/nodes/acme/certs/output/ca.cert.pem
+++ b/tests/common/acme/certs/output/ca.cert.pem
diff --git a/tests/by-name/em/email-dns/nodes/acme/certs/output/ca.key.pem b/tests/common/acme/certs/output/ca.key.pem
index 64263bc..64263bc 100644
--- a/tests/by-name/em/email-dns/nodes/acme/certs/output/ca.key.pem
+++ b/tests/common/acme/certs/output/ca.key.pem
diff --git a/tests/by-name/em/email-dns/nodes/acme/certs/output/ca.template b/tests/common/acme/certs/output/ca.template
index a2295d8..a2295d8 100644
--- a/tests/by-name/em/email-dns/nodes/acme/certs/output/ca.template
+++ b/tests/common/acme/certs/output/ca.template
diff --git a/tests/by-name/em/email-dns/nodes/acme/certs/snakeoil-certs.nix b/tests/common/acme/certs/snakeoil-certs.nix
index aeb6dfc..aeb6dfc 100644
--- a/tests/by-name/em/email-dns/nodes/acme/certs/snakeoil-certs.nix
+++ b/tests/common/acme/certs/snakeoil-certs.nix
diff --git a/tests/by-name/em/email-dns/nodes/acme/client.nix b/tests/common/acme/client.nix
index 2b870e8..2b870e8 100644
--- a/tests/by-name/em/email-dns/nodes/acme/client.nix
+++ b/tests/common/acme/client.nix
diff --git a/tests/common/acme/scripts.nix b/tests/common/acme/scripts.nix
new file mode 100644
index 0000000..2228823
--- /dev/null
+++ b/tests/common/acme/scripts.nix
@@ -0,0 +1,30 @@
+{pkgs}:
+/*
+* Extra functions useful for the test script.
+*/
+{
+  add_pebble_acme_ca = pkgs.writeShellScript "fetch-and-set-ca" ''
+    set -xe
+
+    # Fetch the randomly generated ca certificate
+    curl https://acme.test:15000/roots/0 > /tmp/ca.crt
+    curl https://acme.test:15000/intermediates/0 >> /tmp/ca.crt
+
+    # Append it to the various system stores
+    # The file paths are from <nixpgks>/modules/security/ca.nix
+    for cert_path in "ssl/certs/ca-certificates.crt" "ssl/certs/ca-bundle.crt" "pki/tls/certs/ca-bundle.crt"; do
+      cert_path="/etc/$cert_path"
+
+      mv "$cert_path" "$cert_path.old"
+      cat "$cert_path.old" > "$cert_path"
+      cat /tmp/ca.crt >> "$cert_path"
+    done
+
+    export NIX_SSL_CERT_FILE=/tmp/ca.crt
+    export SSL_CERT_FILE=/tmp/ca.crt
+
+    # TODO
+    # # P11-Kit trust source.
+    # environment.etc."ssl/trust-source".source = "$${cacertPackage.p11kit}/etc/ssl/trust-source";
+  '';
+}
diff --git a/tests/by-name/em/email-dns/nodes/acme/default.nix b/tests/common/acme/server.nix
index 236ba6a..997c944 100644
--- a/tests/by-name/em/email-dns/nodes/acme/default.nix
+++ b/tests/common/acme/server.nix
@@ -1,28 +1,5 @@
-# The certificate for the ACME service is exported as:
-#
-#   config.test-support.acme.caCert
-#
-# This value can be used inside the configuration of other test nodes to inject
-# the test certificate into security.pki.certificateFiles or into package
-# overlays.
-#
-# {
-#   acme = { nodes, lib, ... }: {
-#     imports = [ ./common/acme/server ];
-#     networking.nameservers = lib.mkForce [
-#       nodes.mydnsresolver.networking.primaryIPAddress
-#     ];
-#   };
-#
-#   dnsmyresolver = ...;
-# }
-#
-# Keep in mind, that currently only _one_ resolver is supported, if you have
-# more than one resolver in networking.nameservers only the first one will be
-# used.
-#
-# Also make sure that whenever you use a resolver from a different test node
-# that it has to be started _before_ the ACME service.
+# Add this node as acme server.
+# This also needs a DNS server.
 {
   config,
   pkgs,
diff --git a/tests/common/dns/client.nix b/tests/common/dns/client.nix
new file mode 100644
index 0000000..52f3267
--- /dev/null
+++ b/tests/common/dns/client.nix
@@ -0,0 +1,10 @@
+{
+  lib,
+  nodes,
+  ...
+}: {
+  networking.nameservers = lib.mkForce [
+    nodes.name_server.networking.primaryIPAddress
+    nodes.name_server.networking.primaryIPv6Address
+  ];
+}
diff --git a/tests/common/dns/server.nix b/tests/common/dns/server.nix
new file mode 100644
index 0000000..0c8d72c
--- /dev/null
+++ b/tests/common/dns/server.nix
@@ -0,0 +1,43 @@
+{
+  lib,
+  nodes,
+  ...
+}: {
+  imports = [
+    ../../../modules
+  ];
+
+  networking.nameservers = lib.mkForce [
+    nodes.name_server.networking.primaryIPAddress
+    nodes.name_server.networking.primaryIPv6Address
+  ];
+
+  vhack = {
+    dns = {
+      enable = true;
+      openFirewall = true;
+      interfaces = [
+        nodes.name_server.networking.primaryIPAddress
+        nodes.name_server.networking.primaryIPv6Address
+      ];
+
+      zones = {
+        "acme.test" = {
+          SOA = {
+            nameServer = "ns";
+            adminEmail = "admin@server.com";
+            serial = 2025012301;
+          };
+          useOrigin = false;
+
+          A = [
+            nodes.acme.networking.primaryIPAddress
+          ];
+          AAAA = [
+            nodes.acme.networking.primaryIPv6Address
+          ];
+        };
+      };
+    };
+  };
+}
diff --git a/tests/by-name/em/email-dns/secrets/dkim/alice.com/private.age b/tests/common/email/dkim/alice.com/private.age
index 5415fdc..5415fdc 100644
--- a/tests/by-name/em/email-dns/secrets/dkim/alice.com/private.age
+++ b/tests/common/email/dkim/alice.com/private.age
diff --git a/tests/by-name/em/email-dns/secrets/dkim/alice.com/public b/tests/common/email/dkim/alice.com/public
index 0f3c3b2..0f3c3b2 100644
--- a/tests/by-name/em/email-dns/secrets/dkim/alice.com/public
+++ b/tests/common/email/dkim/alice.com/public
diff --git a/tests/by-name/em/email-dns/secrets/dkim/bob.com/private.age b/tests/common/email/dkim/bob.com/private.age
index c07c997..c07c997 100644
--- a/tests/by-name/em/email-dns/secrets/dkim/bob.com/private.age
+++ b/tests/common/email/dkim/bob.com/private.age
diff --git a/tests/by-name/em/email-dns/secrets/dkim/bob.com/public b/tests/common/email/dkim/bob.com/public
index ddea670..ddea670 100644
--- a/tests/by-name/em/email-dns/secrets/dkim/bob.com/public
+++ b/tests/common/email/dkim/bob.com/public
diff --git a/tests/by-name/em/email-dns/secrets/dkim/gen_key.sh b/tests/common/email/dkim/gen_key.sh
index 48b4434..48b4434 100755
--- a/tests/by-name/em/email-dns/secrets/dkim/gen_key.sh
+++ b/tests/common/email/dkim/gen_key.sh
diff --git a/tests/by-name/em/email-dns/secrets/dkim/mail1.server.com/private.age b/tests/common/email/dkim/mail1.server.com/private.age
index 8c5d3c3..8c5d3c3 100644
--- a/tests/by-name/em/email-dns/secrets/dkim/mail1.server.com/private.age
+++ b/tests/common/email/dkim/mail1.server.com/private.age
diff --git a/tests/by-name/em/email-dns/secrets/dkim/mail1.server.com/public b/tests/common/email/dkim/mail1.server.com/public
index 4941b85..4941b85 100644
--- a/tests/by-name/em/email-dns/secrets/dkim/mail1.server.com/public
+++ b/tests/common/email/dkim/mail1.server.com/public
diff --git a/tests/by-name/em/email-dns/secrets/dkim/mail2.server.com/private.age b/tests/common/email/dkim/mail2.server.com/private.age
index d39631a..d39631a 100644
--- a/tests/by-name/em/email-dns/secrets/dkim/mail2.server.com/private.age
+++ b/tests/common/email/dkim/mail2.server.com/private.age
diff --git a/tests/by-name/em/email-dns/secrets/dkim/mail2.server.com/public b/tests/common/email/dkim/mail2.server.com/public
index 5c4406d..5c4406d 100644
--- a/tests/by-name/em/email-dns/secrets/dkim/mail2.server.com/public
+++ b/tests/common/email/dkim/mail2.server.com/public
diff --git a/tests/by-name/em/email-dns/secrets/hostKey b/tests/common/email/hostKey
index 79c9d6c..79c9d6c 100644
--- a/tests/by-name/em/email-dns/secrets/hostKey
+++ b/tests/common/email/hostKey
diff --git a/update.sh b/update.sh
index c43b80b..e22f4ea 100755
--- a/update.sh
+++ b/update.sh
@@ -3,12 +3,13 @@ __update_sh_run() {
     __update_sh_command="$1"
     shift 1
 
-    printf "\033[35;1m> \033[0m\033[35;1m%s\033[0m\n" "Running '$(basename "$__update_sh_command")' .."
+    printf "\033[35;1m> \033[0m\033[35;1m%s\033[0m\n" "Running '$(basename "$__update_sh_command") $*' .."
 
     "$__update_sh_command" "$@"
 
     unset __update_sh_command
 }
 
+__update_sh_run nix flake update
 __update_sh_run ./pkgs/update_pkgs.sh "$@"
 # vim: ft=sh
diff --git a/zones/vhack.eu/zone.nix b/zones/vhack.eu/zone.nix
index d647174..696d1df 100644
--- a/zones/vhack.eu/zone.nix
+++ b/zones/vhack.eu/zone.nix
@@ -38,7 +38,7 @@ in {
     adminEmail = "dns-admin@foss-syndicate.org";
     # NOTE(@bpeetz): ALWAYS change the serial number, when you change something in the
     # zone file! <2025-04-01>
-    serial = mkSerial 2025 04 11 01;
+    serial = mkSerial 2025 04 23 01;
   };
   useOrigin = false;
 
@@ -157,6 +157,7 @@ in {
     redlib.CNAME = ["server2.vhack.eu."];
 
     nextcloud.CNAME = ["server2.vhack.eu."];
+    sharkey.CNAME = ["server2.vhack.eu."];
 
     mastodon.CNAME = ["server3.vhack.eu."];
     matrix.CNAME = ["server3.vhack.eu."];