about summary refs log tree commit diff stats
path: root/modules/by-name
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--modules/by-name/at/atuin-sync/module.nix45
-rw-r--r--modules/by-name/ba/back/module.nix92
-rw-r--r--modules/by-name/co/constants/module.nix98
-rw-r--r--modules/by-name/gi/git-back/module.nix41
-rw-r--r--modules/by-name/ma/mastodon/module.nix27
-rw-r--r--modules/by-name/ma/matrix/module.nix78
-rw-r--r--modules/by-name/ne/nextcloud/module.nix82
-rw-r--r--modules/by-name/ng/nginx/module.nix5
-rw-r--r--modules/by-name/re/redlib/module.nix5
-rw-r--r--modules/by-name/ru/rust-motd/module.nix32
-rw-r--r--modules/by-name/sh/sharkey/module.nix144
-rw-r--r--modules/by-name/st/stalwart-mail/module.nix94
-rw-r--r--modules/by-name/st/stalwart-mail/settings.nix56
-rw-r--r--modules/by-name/sy/system-info/module.nix79
-rw-r--r--modules/by-name/ta/taskchampion-sync/module.nix53
-rw-r--r--modules/by-name/us/users/module.nix30
16 files changed, 707 insertions, 254 deletions
diff --git a/modules/by-name/at/atuin-sync/module.nix b/modules/by-name/at/atuin-sync/module.nix
new file mode 100644
index 0000000..0db2e29
--- /dev/null
+++ b/modules/by-name/at/atuin-sync/module.nix
@@ -0,0 +1,45 @@
+{
+  config,
+  lib,
+  vhackPackages,
+  ...
+}: let
+  cfg = config.vhack.atuin-sync;
+in {
+  options.vhack.atuin-sync = {
+    enable = lib.mkEnableOption "atuin sync server";
+
+    fqdn = lib.mkOption {
+      description = "The fully qualified domain name of this instance.";
+      type = lib.types.str;
+      example = "atuin-sync.atuin.sh";
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    vhack.nginx.enable = true;
+
+    services = {
+      nginx.virtualHosts."${cfg.fqdn}" = {
+        locations."/" = {
+          proxyPass = "http://127.0.0.1:${toString config.services.atuin.port}";
+          recommendedProxySettings = true;
+        };
+
+        enableACME = true;
+        forceSSL = true;
+      };
+
+      atuin = {
+        enable = true;
+        package = vhackPackages.atuin-server-only;
+        host = "127.0.0.1";
+
+        # Nobody knows about the fqdn and even if, they can only upload encrypted blobs.
+        openRegistration = true;
+
+        database.createLocally = true;
+      };
+    };
+  };
+}
diff --git a/modules/by-name/ba/back/module.nix b/modules/by-name/ba/back/module.nix
deleted file mode 100644
index d47ffce..0000000
--- a/modules/by-name/ba/back/module.nix
+++ /dev/null
@@ -1,92 +0,0 @@
-{
-  config,
-  lib,
-  vhackPackages,
-  pkgs,
-  ...
-}: let
-  cfg = config.vhack.back;
-in {
-  options.vhack.back = {
-    enable = lib.mkEnableOption "Back issue tracker (inspired by tvix's panettone)";
-
-    domain = lib.mkOption {
-      type = lib.types.str;
-      description = "The domain to host this `back` instance on.";
-    };
-
-    settings = {
-      scan_path = lib.mkOption {
-        type = lib.types.path;
-        description = "The path to the directory under which all the repositories reside";
-      };
-      project_list = lib.mkOption {
-        type = lib.types.path;
-        description = "The path to the `projects.list` file.";
-      };
-
-      source_code_repository_url = lib.mkOption {
-        description = "The url to the source code of this instance of back";
-        default = "https://git.foss-syndicate.org/vhack.eu/nixos-server/tree/pkgs/by-name/ba/back";
-        type = lib.types.str;
-      };
-
-      root_url = lib.mkOption {
-        type = lib.types.str;
-        description = "The url to this instance of back.";
-        default = "https://${cfg.domain}";
-      };
-    };
-  };
-
-  config = lib.mkIf cfg.enable {
-    systemd.services."back" = {
-      description = "Back issue tracking system.";
-      requires = ["network-online.target"];
-      after = ["network-online.target"];
-      wantedBy = ["default.target"];
-
-      serviceConfig = {
-        ExecStart = "${lib.getExe vhackPackages.back} ${(pkgs.formats.json {}).generate "config.json" cfg.settings}";
-
-        # Ensure that the service can read the repository
-        # FIXME(@bpeetz): This has the implied assumption, that all the exposed git
-        # repositories are readable for the git group. This should not be necessary. <2024-12-23>
-        User = "git";
-        Group = "git";
-
-        DynamicUser = true;
-        Restart = "always";
-
-        # Sandboxing
-        ProtectSystem = "strict";
-        ProtectHome = true;
-        PrivateTmp = true;
-        PrivateDevices = true;
-        ProtectHostname = true;
-        ProtectClock = true;
-        ProtectKernelTunables = true;
-        ProtectKernelModules = true;
-        ProtectKernelLogs = true;
-        ProtectControlGroups = true;
-        RestrictAddressFamilies = ["AF_UNIX" "AF_INET" "AF_INET6"];
-        RestrictNamespaces = true;
-        LockPersonality = true;
-        MemoryDenyWriteExecute = true;
-        RestrictRealtime = true;
-        RestrictSUIDSGID = true;
-        RemoveIPC = true;
-        PrivateMounts = true;
-        # System Call Filtering
-        SystemCallArchitectures = "native";
-        SystemCallFilter = ["~@cpu-emulation @debug @keyring @mount @obsolete @privileged @setuid"];
-      };
-    };
-    services.nginx.virtualHosts."${cfg.domain}" = {
-      locations."/".proxyPass = "http://127.0.0.1:8000";
-
-      enableACME = true;
-      forceSSL = true;
-    };
-  };
-}
diff --git a/modules/by-name/co/constants/module.nix b/modules/by-name/co/constants/module.nix
index fed14d3..2115a37 100644
--- a/modules/by-name/co/constants/module.nix
+++ b/modules/by-name/co/constants/module.nix
@@ -1,66 +1,94 @@
 # This file is inspired by the `nixos/modules/misc/ids.nix`
 # file in nixpkgs.
-{lib, ...}: {
+{
+  lib,
+  config,
+  ...
+}: {
   options.vhack.constants = {
     ids.uids = lib.mkOption {
       internal = true;
       description = ''
         The user IDs used in the vhack.eu nixos config.
       '';
-      type = lib.types.attrsOf lib.types.int;
+      type = lib.types.attrsOf (lib.types.ints.between 0 400);
     };
     ids.gids = lib.mkOption {
       internal = true;
       description = ''
         The group IDs used in the vhack.eu nixos config.
       '';
-      type = lib.types.attrsOf lib.types.int;
+      type = lib.types.attrsOf (lib.types.ints.between 0 400);
     };
   };
 
   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;
+      matrix-synapse = 224;
+      rspamd = 225;
+      knot-resolver = 226;
+      peertube = 231;
+      redis-mastodon = 232;
+      redis-peertube = 233;
+      redis-rspamd = 234;
+      redis-stalwart-mail = 235;
+      mastodon = 236;
+      stalwart-mail = 238;
       acme = 328;
       dhcpcd = 329;
       nscd = 330;
       sshd = 331;
       systemd-oom = 332;
+      resolvconf = 333; # GROUP
       nix-sync = 334;
-      redis-peertube = 990;
-      peertube = 992; # TODO Sort correctly
-      mastodon = 996;
-      redis-mastodon = 991;
-      matrix-synapse = 224;
-      mautrix-whatsapp = 225;
-      knot-resolver = 997;
-      redis-rspamd = 989;
-      rspamd = 225;
-      opendkim = 221;
-      virtualMail = 5000;
-      etebase-server = 998;
+      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 = {
-      acme = 328;
-      dhcpcd = 329;
-      nscd = 330;
-      sshd = 331;
-      systemd-oom = 332;
-      resolvconf = 333; # This group is not matched to an user?
-      nix-sync = 334;
-      systemd-coredump = 151; # matches systemd-coredump user
-      redis-peertube = 990;
-      peertube = 992;
-      mastodon = 996;
-      redis-mastodon = 991;
-      matrix-synapse = 224;
-      knot-resolver = 997;
-      redis-rspamd = 989;
-      rspamd = 225;
-      opendkim = 221;
-      virtualMail = 5000;
-      etebase-server = 998;
+    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
+        dhcpcd
+        etebase-server
+        knot-resolver
+        mastodon
+        matrix-synapse
+        mautrix-whatsapp
+        nextcloud
+        nix-sync
+        nscd
+        opendkim
+        peertube
+        redis-mastodon
+        redis-nextcloud
+        redis-peertube
+        redis-rspamd
+        redis-stalwart-mail
+        rspamd
+        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
+        ;
 
       # The gid should match the uid. Thus should not be >= 400;
     };
diff --git a/modules/by-name/gi/git-back/module.nix b/modules/by-name/gi/git-back/module.nix
new file mode 100644
index 0000000..96f4913
--- /dev/null
+++ b/modules/by-name/gi/git-back/module.nix
@@ -0,0 +1,41 @@
+{
+  config,
+  lib,
+  ...
+}: let
+  cfg = config.vhack.git-back;
+in {
+  options.vhack.git-back = {
+    enable = lib.mkEnableOption "Back integration into git-server";
+
+    domain = lib.mkOption {
+      type = lib.types.str;
+      description = "The domain where to deploy back";
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    vhack.back = {
+      enable = true;
+
+      user = "git";
+      group = "git";
+
+      settings = {
+        scan_path = "${config.services.gitolite.dataDir}/repositories";
+        project_list = "${config.services.gitolite.dataDir}/projects.list";
+        root_url = "https://${cfg.domain}";
+      };
+    };
+
+    services.nginx = {
+      enable = true;
+      virtualHosts."${cfg.domain}" = {
+        locations."/".proxyPass = "http://127.0.0.1:8000";
+
+        enableACME = true;
+        forceSSL = true;
+      };
+    };
+  };
+}
diff --git a/modules/by-name/ma/mastodon/module.nix b/modules/by-name/ma/mastodon/module.nix
index 895428d..84f3ec8 100644
--- a/modules/by-name/ma/mastodon/module.nix
+++ b/modules/by-name/ma/mastodon/module.nix
@@ -37,16 +37,22 @@ in {
       owner = "mastodon";
       group = "mastodon";
     };
-    vhack.persist.directories = [
-      {
-        directory = "/var/lib/mastodon";
-        user = "mastodon";
-        group = "mastodon";
-        mode = "0700";
-      }
-    ];
 
-    vhack.postgresql.enable = true;
+    vhack = {
+      persist.directories = [
+        {
+          directory = "/var/lib/mastodon";
+          user = "mastodon";
+          group = "mastodon";
+          mode = "0700";
+        }
+      ];
+
+      postgresql.enable = true;
+
+      nginx.enable = true;
+    };
+
     services.mastodon = {
       enable = true;
 
@@ -54,7 +60,7 @@ in {
 
       # Unstable Mastodon package, used if
       # security updates aren't backported.
-      #package = applyPatches pkgs-unstable.mastodon;
+      #package = applyPatches pkgsUnstable.mastodon;
 
       localDomain =
         if cfg.enableTLD
@@ -75,7 +81,6 @@ in {
       };
     };
 
-    vhack.nginx.enable = true;
     services.nginx = {
       enable = true;
       recommendedProxySettings = true; # required for redirections to work
diff --git a/modules/by-name/ma/matrix/module.nix b/modules/by-name/ma/matrix/module.nix
index 4b730da..ae3f04e 100644
--- a/modules/by-name/ma/matrix/module.nix
+++ b/modules/by-name/ma/matrix/module.nix
@@ -1,6 +1,5 @@
 {
   config,
-  pkgs,
   lib,
   ...
 }: let
@@ -29,6 +28,7 @@ in {
       description = "The age encrypted shared secret file for synapse, passed to agenix";
     };
   };
+
   config = lib.mkIf cfg.enable {
     age.secrets.matrix-synapse_registration_shared_secret = {
       file = cfg.sharedSecretFile;
@@ -38,45 +38,53 @@ in {
     };
     networking.firewall.allowedTCPPorts = [80 443];
 
-    vhack.persist.directories = [
-      {
-        directory = "/var/lib/matrix";
-        user = "matrix-synapse";
-        group = "matrix-synapse";
-        mode = "0700";
-      }
-      {
-        directory = "/var/lib/mautrix-whatsapp";
-        user = "mautrix-whatsapp";
-        group = "matrix-synapse";
-        mode = "0750";
-      }
-    ];
-    systemd.tmpfiles.rules = [
-      "d /etc/matrix 0755 matrix-synapse matrix-synapse"
-    ];
+    vhack = {
+      persist.directories = [
+        {
+          directory = "/var/lib/matrix";
+          user = "matrix-synapse";
+          group = "matrix-synapse";
+          mode = "0700";
+        }
+        {
+          directory = "/var/lib/mautrix-whatsapp";
+          user = "mautrix-whatsapp";
+          group = "matrix-synapse";
+          mode = "0750";
+        }
+      ];
 
-    vhack.postgresql.enable = true;
-    vhack.nginx.enable = true;
+      postgresql.enable = true;
+      nginx.enable = true;
+    };
+
+    systemd = {
+      tmpfiles.rules = [
+        "d /etc/matrix 0755 matrix-synapse matrix-synapse"
+      ];
+      services.postgresql.postStart = ''
+        $PSQL -tAc "ALTER ROLE \"matrix-synapse\" WITH PASSWORD 'synapse';"
+        $PSQL -tAc "ALTER ROLE \"mautrix-whatsapp\" WITH PASSWORD 'whatsapp';"
+      '';
+    };
 
     services = {
       postgresql = {
         enable = true;
-        initialScript = pkgs.writeText "synapse-init.sql" ''
-          --Matrix:
-          CREATE ROLE "matrix-synapse" WITH LOGIN PASSWORD 'synapse';
-          CREATE DATABASE "matrix-synapse" WITH OWNER "matrix-synapse"
-            TEMPLATE template0
-            LC_COLLATE = "C"
-            LC_CTYPE = "C";
-
-          --Whatsapp-bridge:
-          CREATE ROLE "mautrix-whatsapp" WITH LOGIN PASSWORD 'whatsapp';
-          CREATE DATABASE "mautrix-whatsapp" WITH OWNER "mautrix-whatsapp"
-            TEMPLATE template0
-            LC_COLLATE = "C"
-            LC_CTYPE = "C";
-        '';
+        ensureUsers = [
+          {
+            name = "matrix-synapse";
+            ensureDBOwnership = true;
+          }
+          {
+            name = "mautrix-whatsapp";
+            ensureDBOwnership = true;
+          }
+        ];
+        ensureDatabases = [
+          "matrix-synapse"
+          "mautrix-whatsapp"
+        ];
       };
 
       nginx = {
diff --git a/modules/by-name/ne/nextcloud/module.nix b/modules/by-name/ne/nextcloud/module.nix
new file mode 100644
index 0000000..e0d7cb3
--- /dev/null
+++ b/modules/by-name/ne/nextcloud/module.nix
@@ -0,0 +1,82 @@
+{
+  config,
+  pkgs,
+  lib,
+  ...
+}: let
+  cfg = config.vhack.nextcloud;
+in {
+  options.vhack.nextcloud = {
+    enable = lib.mkEnableOption "a sophisticated nextcloud setup";
+    package = lib.mkOption {
+      type = lib.types.package;
+      default = pkgs.nextcloud31;
+      description = "The nextcloud package to use";
+    };
+    hostname = lib.mkOption {
+      type = lib.types.str;
+      description = "The nextcloud hostname (fqdn)";
+    };
+    adminpassFile = lib.mkOption {
+      type = lib.types.path;
+      description = "The age encrypted admin password file";
+    };
+  };
+  config = lib.mkIf cfg.enable {
+    vhack = {
+      nginx.enable = true;
+      postgresql.enable = true;
+      persist.directories = [
+        "/var/lib/nextcloud"
+      ];
+    };
+    age.secrets = {
+      adminpassFile = {
+        file = cfg.adminpassFile;
+        mode = "0700";
+        owner = "nextcloud";
+        group = "nextcloud";
+      };
+    };
+
+    services = {
+      nextcloud = {
+        enable = true;
+        extraApps = {
+          inherit (cfg.package.packages.apps) calendar contacts tasks;
+        };
+        extraAppsEnable = true;
+        configureRedis = true;
+        config = {
+          adminuser = "admin";
+          adminpassFile = config.age.secrets.adminpassFile.path;
+          dbname = "nextcloud";
+          dbuser = "nextcloud";
+          dbtype = "pgsql";
+        };
+        database.createLocally = true;
+        hostName = cfg.hostname;
+        https = true;
+        maxUploadSize = "5G";
+        package = cfg.package;
+        settings = {
+          default_phone_region = "DE";
+        };
+      };
+      nginx.virtualHosts.${cfg.hostname} = {
+        forceSSL = true;
+        enableACME = true;
+      };
+    };
+    users = {
+      users = {
+        "nextcloud".uid = config.vhack.constants.ids.uids.nextcloud;
+        "redis-nextcloud".uid = config.vhack.constants.ids.uids.redis-nextcloud;
+      };
+      groups = {
+        "nextcloud".gid = config.vhack.constants.ids.gids.nextcloud;
+        "redis-nextcloud".gid = config.vhack.constants.ids.gids.redis-nextcloud;
+      };
+    };
+  };
+}
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/re/redlib/module.nix b/modules/by-name/re/redlib/module.nix
index eb5edba..909c9f1 100644
--- a/modules/by-name/re/redlib/module.nix
+++ b/modules/by-name/re/redlib/module.nix
@@ -32,10 +32,5 @@ in {
         forceSSL = true;
       };
     };
-
-    # TODO(@bpeetz): Remove this at some point. <2025-02-04>
-    vhack.nginx.redirects = {
-      "libreddit.vhack.eu" = "${domain}";
-    };
   };
 }
diff --git a/modules/by-name/ru/rust-motd/module.nix b/modules/by-name/ru/rust-motd/module.nix
index a6998f4..8d0939a 100644
--- a/modules/by-name/ru/rust-motd/module.nix
+++ b/modules/by-name/ru/rust-motd/module.nix
@@ -19,6 +19,13 @@
     || v.openssh.authorizedKeys.keyFiles != []
   );
   userList = builtins.mapAttrs (n: v: 2) (lib.filterAttrs pred config.users.users);
+
+  bannerFile =
+    pkgs.runCommandNoCCLocal "banner-file" {
+      nativeBuildInputs = [pkgs.figlet];
+    } ''
+      echo "${config.system.name}" | figlet -f slant > "$out"
+    '';
 in {
   options.vhack.rust-motd = {
     enable = lib.mkEnableOption "rust-motd";
@@ -49,25 +56,22 @@ in {
 
         banner = {
           color = "red";
-          command = "${pkgs.hostname}/bin/hostname | ${pkgs.figlet}/bin/figlet -f slant";
-          # if you don't want a dependency on figlet, you can generate your
-          # banner however you want, put it in a file, and then use something like:
-          # command = "cat banner.txt"
+          # Avoid some runtime dependencies.
+          command = "cat ${bannerFile}";
+        };
+
+        cg_stats = {
+          state_file = "/var/lib/rust-motd/cg_stats_state";
+          threshold = 0.02; # When to start generating output for a cgroup
+        };
+        load_avg = {
+          format = "Load (1, 5, 15 min.): {one:.02}, {five:.02}, {fifteen:.02}";
         };
 
         uptime = {
           prefix = "Uptime:";
         };
 
-        # ssl_certificates = {
-        #   sort_method = "manual";
-        #
-        #   certs = {
-        #     "server1.vhack.eu" = "/var/lib/acme/server1.vhack.eu/cert.pem";
-        #     "vhack.eu" = "/var/lib/acme/vhack.eu/cert.pem";
-        #   };
-        # };
-
         filesystems = {
           root = "/";
           persistent = "/srv";
@@ -79,7 +83,7 @@ in {
           swap_pos = "beside"; # or "below" or "none"
         };
 
-        fail2_ban = {
+        fail_2_ban = {
           jails = ["sshd"]; #, "anotherjail"]
         };
 
diff --git a/modules/by-name/sh/sharkey/module.nix b/modules/by-name/sh/sharkey/module.nix
new file mode 100644
index 0000000..a296edd
--- /dev/null
+++ b/modules/by-name/sh/sharkey/module.nix
@@ -0,0 +1,144 @@
+{
+  config,
+  lib,
+  pkgs,
+  pkgsUnstable,
+  nixpkgs-unstable,
+  ...
+}: let
+  cfg = config.vhack.sharkey;
+in {
+  imports = [
+    # TODO(@bpeetz): Remove this import once we update to NixOS 25.11 <2025-07-12>
+    "${nixpkgs-unstable}/nixos/modules/services/web-apps/sharkey.nix"
+  ];
+
+  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 = pkgsUnstable.sharkey;
+      defaultText = lib.literalExpression "vhackPackages.sharkey";
+      description = "Sharkey package to use.";
+    };
+
+    mediaDirectory = lib.mkOption {
+      type = lib.types.path;
+      default = "/var/lib/sharkey";
+      description = "The directory where sharkey stores it's data.";
+    };
+
+    settings = lib.mkOption {
+      inherit (pkgs.formats.yaml {}) type;
+      default = {};
+      description = ''
+        Extra Configuration for Sharkey, see
+        <link xlink:href="https://activitypub.software/TransFem-org/Sharkey/-/blob/develop/.config/example.yml"/>
+        for supported settings.
+
+        Note, that this is applied on-top of the neccessary config.
+      '';
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    services = {
+      sharkey = {
+        enable = true;
+
+        inherit (cfg) package;
+        openFirewall = false;
+        setupRedis = true;
+        setupPostgresql = true;
+
+        settings =
+          cfg.settings
+          // {
+            url = "https://${cfg.fqdn}/";
+            port = 5312;
+
+            inherit (cfg) mediaDirectory;
+            fulltextSearch.provider = "sqlLike";
+          };
+      };
+
+      nginx.virtualHosts."${cfg.fqdn}" = {
+        locations."/" = {
+          proxyPass = "http://127.0.0.1:${toString config.services.sharkey.settings.port}";
+          proxyWebsockets = true;
+        };
+
+        enableACME = true;
+        forceSSL = true;
+      };
+    };
+
+    systemd.services.sharkey = {
+      # TODO(@bpeetz): `postgresql.target` is only available in NixOS 25.11, as such we
+      # need to override this back to the postgresql.service. <2025-07-12>
+      after = lib.mkForce [
+        "postgresql.service"
+        "redis-sharkey.service"
+      ];
+      bindsTo = lib.mkForce [
+        "postgresql.service"
+        "redis-sharkey.service"
+      ];
+
+      serviceConfig = {
+        # The upstream service uses DynamicUsers, which currently poses issues to our
+        # directory persisting strategy.
+        User = "sharkey";
+        Group = "sharkey";
+        DynamicUser = lib.mkForce false;
+      };
+    };
+
+    vhack = {
+      nginx.enable = true;
+
+      persist.directories = [
+        {
+          directory = "${config.services.redis.servers."sharkey".settings.dir}";
+          user = "sharkey";
+          group = "redis-sharey";
+          mode = "0770";
+        }
+        {
+          directory = "${cfg.mediaDirectory}";
+          user = "sharkey";
+          group = "sharkey";
+          mode = "0700";
+        }
+      ];
+    };
+
+    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 6905005..4565bf4 100644
--- a/modules/by-name/st/stalwart-mail/module.nix
+++ b/modules/by-name/st/stalwart-mail/module.nix
@@ -18,7 +18,7 @@ in {
   options.vhack.stalwart-mail = {
     enable = lib.mkEnableOption "starwart-mail";
 
-    package = lib.mkPackageOption vhackPackages "stalwart-mail-free" {};
+    package = lib.mkPackageOption vhackPackages "stalwart-mail-patched" {};
 
     admin = lib.mkOption {
       description = ''
@@ -41,8 +41,8 @@ in {
     };
 
     principals = lib.mkOption {
-      default = [];
-      type = lib.types.listOf (lib.types.submodule {
+      default = null;
+      type = lib.types.nullOr (lib.types.listOf (lib.types.submodule {
         options = {
           name = lib.mkOption {
             type = lib.types.str;
@@ -61,7 +61,32 @@ in {
           };
 
           secret = lib.mkOption {
-            type = lib.types.str;
+            type = 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}")
+              ];
             description = ''
               Sets the password for the user account.
               Passwords can be stored hashed or in plain text (not recommended).
@@ -77,7 +102,7 @@ in {
             '';
           };
         };
-      });
+      }));
     };
 
     dataDirectory = lib.mkOption {
@@ -160,25 +185,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 +225,7 @@ in {
     security.acme.certs = {
       "${cfg.fqdn}" = {
         domain = cfg.fqdn;
-        group = "stalwart-mail";
+        group = "stalwart-mail-certificates";
       };
     };
 
@@ -239,7 +255,7 @@ in {
       {
         directory = "${config.services.redis.servers."stalwart-mail".settings.dir}";
         user = "stalwart-mail";
-        group = "redis";
+        group = "redis-stalwart-mail";
         mode = "0770";
       }
     ];
@@ -249,10 +265,31 @@ in {
     # service is restarted on a potentially large number of files.
     # That would cause unnecessary and unwanted delays.
     users = {
-      groups.stalwart-mail = {};
-      users.stalwart-mail = {
-        isSystemUser = true;
-        group = "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 = {
+        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;
+        };
       };
     };
 
@@ -311,8 +348,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 7032ae0..907cea9 100644
--- a/modules/by-name/st/stalwart-mail/settings.nix
+++ b/modules/by-name/st/stalwart-mail/settings.nix
@@ -18,6 +18,11 @@
     if cfg.security != null
     then cfg.security.verificationMode
     else "disable";
+
+  directory =
+    if cfg.principals == null
+    then "internal"
+    else "in-memory";
 in {
   config.services.stalwart-mail.settings = lib.mkIf cfg.enable {
     # https://www.rfc-editor.org/rfc/rfc6376.html#section-3.3
@@ -219,7 +224,7 @@ in {
         require = true;
       };
       rcpt = {
-        directory = "'in-memory'";
+        directory = "'${directory}'";
         catch-all = true;
         subaddressing = true;
       };
@@ -236,7 +241,7 @@ in {
         };
         auth = {
           mechanisms = ["LOGIN" "PLAIN"];
-          directory = "'in-memory'";
+          directory = "'${directory}'";
           require = true;
           must-match-sender = true;
           errors = {
@@ -339,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" = {
@@ -401,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
@@ -467,6 +473,14 @@ in {
         # Perform “maintenance” every day at 3 am local time.
         purge.frequency = "0 3 *";
       };
+      "rocksdb-directory" = lib.mkIf (cfg.principals == null) {
+        type = "rocksdb";
+        path = "${cfg.dataDirectory}/storage/directory";
+        compression = "lz4";
+
+        # Perform “maintenance” every day at 1 am local time.
+        purge.frequency = "0 1 *";
+      };
       "rocksdb-full-text-search" = {
         type = "rocksdb";
         path = "${cfg.dataDirectory}/storage/full-text-search";
@@ -505,7 +519,7 @@ in {
       full-text.default-language = "en";
       fts = "rocksdb-full-text-search";
 
-      directory = "in-memory";
+      directory = "${directory}";
 
       lookup = "redis";
 
@@ -516,9 +530,15 @@ in {
       encryption.enable = false;
     };
 
-    directory."in-memory" = {
-      type = "memory";
-      inherit (cfg) principals;
+    directory = {
+      "in-memory" = lib.mkIf (cfg.principals != null) {
+        type = "memory";
+        inherit (cfg) principals;
+      };
+      "internal" = lib.mkIf (cfg.principals == null) {
+        type = "internal";
+        store = "rocksdb-directory";
+      };
     };
 
     certificate = {
diff --git a/modules/by-name/sy/system-info/module.nix b/modules/by-name/sy/system-info/module.nix
new file mode 100644
index 0000000..8136ae5
--- /dev/null
+++ b/modules/by-name/sy/system-info/module.nix
@@ -0,0 +1,79 @@
+{
+  lib,
+  config,
+  pkgs,
+  ...
+}: let
+  mkVirtualHostDisplay = name: value: let
+    aliases =
+      if value.serverAliases != []
+      then
+        ": "
+        + builtins.concatStringsSep " " value.serverAliases
+      else "";
+  in ''
+    ${name}${aliases}
+  '';
+  vHosts = builtins.concatStringsSep "" (builtins.attrValues (builtins.mapAttrs mkVirtualHostDisplay config.services.nginx.virtualHosts));
+
+  mkOpenPortDisplay = mode: port: let
+    checkEnabled = service: name:
+      if config.vhack.${service}.enable
+      then name
+      else "<port is '${name}' but service 'vhack.${service}' is not enabled.>";
+    mappings = {
+      "22" = checkEnabled "openssh" "ssh";
+      "80" = checkEnabled "nginx" "http";
+      "443" = checkEnabled "nginx" "https";
+
+      "53" = checkEnabled "dns" "dns";
+
+      "24" = checkEnabled "mail" "mail-lmtp";
+      "465" = checkEnabled "mail" "mail-smtp-tls";
+      "25" = checkEnabled "mail" "mail-smtp";
+      "993" = checkEnabled "mail" "mail-imap-tls";
+      "995" = checkEnabled "mail" "mail-pop3-tls";
+
+      "10222" = checkEnabled "taskchampion-sync" "taskchampion-sync";
+
+      # TODO(@bpeetz): Check which service opens these ports: <2025-01-28>
+      "64738" = "???";
+      "4190" = "???";
+    };
+  in ''
+    ${mode} ${builtins.toString port}: ${
+      if (builtins.hasAttr "${builtins.toString port}" mappings)
+      then mappings.${builtins.toString port}
+      else
+        builtins.throw
+        "'${builtins.toString port}' is still missing from the system info port -> name map. Maybe add it?"
+    }
+  '';
+
+  # TODO(@bpeetz): This should probably also include the allowed TCP/UDP port ranges. <2025-01-28>
+  openTCPPorts = builtins.concatStringsSep "" (builtins.map (mkOpenPortDisplay "TCP") config.networking.firewall.allowedTCPPorts);
+  openUDPPorts = builtins.concatStringsSep "" (builtins.map (mkOpenPortDisplay "UDP") config.networking.firewall.allowedUDPPorts);
+
+  markdown = pkgs.writeText "${config.networking.hostName}-system-info.md" ''
+    ## Virtual Hosts
+    ${vHosts}
+    ## Open ports
+    ${openTCPPorts}
+    ${openUDPPorts}
+  '';
+in {
+  options.vhack.system-info = {
+    markdown = lib.mkOption {
+      type = lib.types.package;
+      description = ''
+        A derivation, that builds a markdown file, showing relevant system
+        information for this host.
+      '';
+      readOnly = true;
+    };
+  };
+
+  config.vhack.system-info = {
+    inherit markdown;
+  };
+}
diff --git a/modules/by-name/ta/taskchampion-sync/module.nix b/modules/by-name/ta/taskchampion-sync/module.nix
new file mode 100644
index 0000000..a722883
--- /dev/null
+++ b/modules/by-name/ta/taskchampion-sync/module.nix
@@ -0,0 +1,53 @@
+{
+  config,
+  lib,
+  ...
+}: let
+  cfg = config.vhack.taskchampion-sync;
+  dataDirectory = "/var/lib/taskchampion-sync-server";
+in {
+  options.vhack.taskchampion-sync = {
+    enable = lib.mkEnableOption "taskchampion-sync";
+
+    fqdn = lib.mkOption {
+      description = "The fully qualified domain name of this instance.";
+      type = lib.types.str;
+      example = "task-sync.tw.online";
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    users = {
+      users.taskchampion.uid = config.vhack.constants.ids.uids.taskchampion;
+      groups.taskchampion.gid = config.vhack.constants.ids.uids.taskchampion;
+    };
+
+    vhack = {
+      persist.directories = [
+        {
+          directory = dataDirectory;
+          user = "taskchampion";
+          group = "taskchampion";
+          mode = "0700";
+        }
+      ];
+      nginx.enable = true;
+    };
+
+    services = {
+      taskchampion-sync-server = {
+        enable = true;
+        dataDir = dataDirectory;
+      };
+
+      nginx.virtualHosts."${cfg.fqdn}" = {
+        locations."/" = {
+          proxyPass = "http://127.0.0.1:${toString config.services.taskchampion-sync-server.port}";
+          recommendedProxySettings = true;
+        };
+        enableACME = true;
+        forceSSL = true;
+      };
+    };
+  };
+}
diff --git a/modules/by-name/us/users/module.nix b/modules/by-name/us/users/module.nix
index a197b13..6011204 100644
--- a/modules/by-name/us/users/module.nix
+++ b/modules/by-name/us/users/module.nix
@@ -27,20 +27,22 @@
     };
   };
 
-  extraUsers = lib.listToAttrs (builtins.map mkUser [
-    {
-      name = "soispha";
-      password = "$y$jFT$3.8XmUyukZvpExMUxDZkI.$IVrJgm8ysNDF/0vDD2kF6w73ozXgr1LMVRNN4Bq7pv1";
-      sshKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIME4ZVa+IoZf6T3U08JG93i6QIAJ4amm7mkBzO14JSkz cardno:000F_18F83532";
-      uid = 1000;
-    }
-    {
-      name = "sils";
-      password = "$y$jFT$KpFnahVCE9JbE.5P3us8o.$ZzSxCusWqe3sL7b6DLgOXNNUf114tiiptM6T8lDxtKC";
-      sshKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAe4o1PM6VasT3KZNl5NYvgkkBrPOg36dqsywd10FztS openpgp:0x21D20D6A";
-      uid = 1001;
-    }
-  ]);
+  extraUsers = lib.listToAttrs (
+    builtins.map mkUser [
+      {
+        name = "soispha";
+        password = "$y$jFT$3.8XmUyukZvpExMUxDZkI.$IVrJgm8ysNDF/0vDD2kF6w73ozXgr1LMVRNN4Bq7pv1";
+        sshKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIME4ZVa+IoZf6T3U08JG93i6QIAJ4amm7mkBzO14JSkz cardno:000F_18F83532";
+        uid = 1000;
+      }
+      {
+        name = "sils";
+        password = "$y$jFT$KpFnahVCE9JbE.5P3us8o.$ZzSxCusWqe3sL7b6DLgOXNNUf114tiiptM6T8lDxtKC";
+        sshKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILn7Oumr5IYtTTIKRFvDnofGXXiDLBQE9jVF+7UE+4G5 vhack.eu";
+        uid = 1001;
+      }
+    ]
+  );
 in {
   options.vhack.users = {
     enable = lib.mkEnableOption "user setup";