about summary refs log tree commit diff stats
diff options
context:
space:
mode:
Diffstat (limited to '')
-rwxr-xr-xcheck.sh19
-rw-r--r--flake.lock60
-rw-r--r--hosts/by-name/server3/configuration.nix4
-rw-r--r--modules/by-name/co/constants/module.nix2
-rw-r--r--modules/by-name/gr/grocy/module.nix51
-rw-r--r--modules/by-name/ru/rust-motd/module.nix32
-rw-r--r--modules/by-name/sh/sharkey/module.nix313
-rw-r--r--modules/by-name/us/users/module.nix30
-rw-r--r--pkgs/by-name/sh/sharkey/package.nix176
-rw-r--r--pkgs/by-name/st/stalwart-mail-patched/package.nix7
-rw-r--r--pkgs/by-name/st/stalwart-mail-patched/spam-filter.nix4
-rw-r--r--tests/by-name/ru/rust-motd/test.nix63
-rw-r--r--tests/by-name/sh/sharkey-cpu/test.nix2
-rwxr-xr-xupdate.sh5
-rw-r--r--zones/vhack.eu/zone.nix3
15 files changed, 301 insertions, 470 deletions
diff --git a/check.sh b/check.sh
new file mode 100755
index 0000000..f3c4ecb
--- /dev/null
+++ b/check.sh
@@ -0,0 +1,19 @@
+#! /usr/bin/env sh
+
+nix build \
+    --option max-jobs 1 \
+    --print-out-paths --no-link \
+    .#checks.x86_64-linux.atuin-sync \
+    .#checks.x86_64-linux.back \
+    .#checks.x86_64-linux.deploy-activate \
+    .#checks.x86_64-linux.deploy-schema \
+    .#checks.x86_64-linux.dns \
+    .#checks.x86_64-linux.formatting \
+    .#checks.x86_64-linux.git-server \
+    .#checks.x86_64-linux.rust-motd \
+    .#checks.x86_64-linux.sharkey \
+    .#checks.x86_64-linux.sharkey-cpu \
+    .#checks.x86_64-linux.taskchampion-sync
+    # .#checks.x86_64-linux.email-dns \
+    # .#checks.x86_64-linux.email-http \
+    # .#checks.x86_64-linux.email-ip \
diff --git a/flake.lock b/flake.lock
index 0206d5f..8e09c5f 100644
--- a/flake.lock
+++ b/flake.lock
@@ -12,11 +12,11 @@
         ]
       },
       "locked": {
-        "lastModified": 1747575206,
-        "narHash": "sha256-NwmAFuDUO/PFcgaGGr4j3ozG9Pe5hZ/ogitWhY+D81k=",
+        "lastModified": 1761656077,
+        "narHash": "sha256-lsNWuj4Z+pE7s0bd2OKicOFq9bK86JE0ZGeKJbNqb94=",
         "owner": "ryantm",
         "repo": "agenix",
-        "rev": "4835b1dc898959d8547a871ef484930675cb47f1",
+        "rev": "9ba0d85de3eaa7afeab493fed622008b6e4924f5",
         "type": "github"
       },
       "original": {
@@ -73,11 +73,11 @@
     },
     "crane": {
       "locked": {
-        "lastModified": 1748970125,
-        "narHash": "sha256-UDyigbDGv8fvs9aS95yzFfOKkEjx1LO3PL3DsKopohA=",
+        "lastModified": 1760924934,
+        "narHash": "sha256-tuuqY5aU7cUkR71sO2TraVKK2boYrdW3gCSXUkF4i44=",
         "owner": "ipetkov",
         "repo": "crane",
-        "rev": "323b5746d89e04b22554b061522dfce9e4c49b18",
+        "rev": "c6b4d5308293d0d04fcfeee92705017537cad02f",
         "type": "github"
       },
       "original": {
@@ -121,11 +121,11 @@
         ]
       },
       "locked": {
-        "lastModified": 1749105467,
-        "narHash": "sha256-hXh76y/wDl15almBcqvjryB50B0BaiXJKk20f314RoE=",
+        "lastModified": 1756719547,
+        "narHash": "sha256-N9gBKUmjwRKPxAafXEk1EGadfk2qDZPBQp4vXWPHINQ=",
         "owner": "serokell",
         "repo": "deploy-rs",
-        "rev": "6bc76b872374845ba9d645a2f012b764fecd765f",
+        "rev": "125ae9e3ecf62fb2c0fd4f2d894eb971f1ecaed2",
         "type": "github"
       },
       "original": {
@@ -141,11 +141,11 @@
         ]
       },
       "locked": {
-        "lastModified": 1749200714,
-        "narHash": "sha256-W8KiJIrVwmf43JOPbbTu5lzq+cmdtRqaNbOsZigjioY=",
+        "lastModified": 1761899396,
+        "narHash": "sha256-XOpKBp6HLzzMCbzW50TEuXN35zN5WGQREC7n34DcNMM=",
         "owner": "nix-community",
         "repo": "disko",
-        "rev": "17d08c65c241b1d65b3ddf79e3fac1ddc870b0f6",
+        "rev": "6f4cf5abbe318e4cd1e879506f6eeafd83f7b998",
         "type": "github"
       },
       "original": {
@@ -157,11 +157,11 @@
     "flake-compat": {
       "flake": false,
       "locked": {
-        "lastModified": 1747046372,
-        "narHash": "sha256-CIVLLkVgvHYbgI2UpXvIIBJ12HWgX+fjA8Xf8PUmqCY=",
+        "lastModified": 1761588595,
+        "narHash": "sha256-XKUZz9zewJNUj46b4AJdiRZJAvSZ0Dqj2BNfXvFlJC4=",
         "owner": "edolstra",
         "repo": "flake-compat",
-        "rev": "9100a0f413b0c601e0533d1d94ffd501ce2e7885",
+        "rev": "f387cd2afec9419c8ee37694406ca490c3f34ee5",
         "type": "github"
       },
       "original": {
@@ -292,11 +292,11 @@
     },
     "nixpkgs": {
       "locked": {
-        "lastModified": 1749240875,
-        "narHash": "sha256-x+qVg8KwsHrhM0F6f1awVvVFNduyq2uhSjDyRzALXxI=",
+        "lastModified": 1761903212,
+        "narHash": "sha256-lSIrqFA4mnukm8IPTp8x4IYZ+/kXLv2tZsvT0yv9FZM=",
         "owner": "NixOS",
         "repo": "nixpkgs",
-        "rev": "73e167b46f2889fe0dacb9cb4f98054554ed107f",
+        "rev": "7e82f02937587358bce377bef87359f44e0fa07b",
         "type": "github"
       },
       "original": {
@@ -308,11 +308,11 @@
     },
     "nixpkgs-unstable": {
       "locked": {
-        "lastModified": 1749201760,
-        "narHash": "sha256-LEZbj+VD/AR/dWL5ns1gMwzMvp4mLlv4WalxmZTKy5Y=",
+        "lastModified": 1761907660,
+        "narHash": "sha256-kJ8lIZsiPOmbkJypG+B5sReDXSD1KGu2VEPNqhRa/ew=",
         "owner": "NixOS",
         "repo": "nixpkgs",
-        "rev": "ebd3748a6b97de45844aa62701b81df35c5c1269",
+        "rev": "2fb006b87f04c4d3bdf08cfdbc7fab9c13d94a15",
         "type": "github"
       },
       "original": {
@@ -348,11 +348,11 @@
         ]
       },
       "locked": {
-        "lastModified": 1749263796,
-        "narHash": "sha256-m52UsUrcNjAzgc0cwcg94INkiFyVPTn6KbFGr4x4cu8=",
+        "lastModified": 1761878277,
+        "narHash": "sha256-6fCtyVdTzoQejwoextAu7dCLoy5fyD3WVh+Qm7t2Nhg=",
         "owner": "oxalica",
         "repo": "rust-overlay",
-        "rev": "6e1d910306edfe6e4b718878f222c5672500d6b2",
+        "rev": "6604534e44090c917db714faa58d47861657690c",
         "type": "github"
       },
       "original": {
@@ -376,11 +376,11 @@
         ]
       },
       "locked": {
-        "lastModified": 1747965231,
-        "narHash": "sha256-BW3ktviEhfCN/z3+kEyzpDKAI8qFTwO7+S0NVA0C90o=",
+        "lastModified": 1755110674,
+        "narHash": "sha256-PigqTAGkdBYXVFWsJnqcirrLeFqRFN4PFigLA8FzxeI=",
         "owner": "simple-nixos-mailserver",
         "repo": "nixos-mailserver",
-        "rev": "53007af63fade28853408370c4c600a63dd97f41",
+        "rev": "f5936247dbdb8501221978562ab0b302dd75456c",
         "type": "gitlab"
       },
       "original": {
@@ -412,11 +412,11 @@
         ]
       },
       "locked": {
-        "lastModified": 1749194973,
-        "narHash": "sha256-eEy8cuS0mZ2j/r/FE0/LYBSBcIs/MKOIVakwHVuqTfk=",
+        "lastModified": 1761311587,
+        "narHash": "sha256-Msq86cR5SjozQGCnC6H8C+0cD4rnx91BPltZ9KK613Y=",
         "owner": "numtide",
         "repo": "treefmt-nix",
-        "rev": "a05be418a1af1198ca0f63facb13c985db4cb3c5",
+        "rev": "2eddae033e4e74bf581c2d1dfa101f9033dbd2dc",
         "type": "github"
       },
       "original": {
diff --git a/hosts/by-name/server3/configuration.nix b/hosts/by-name/server3/configuration.nix
index 6966e58..1736a32 100644
--- a/hosts/by-name/server3/configuration.nix
+++ b/hosts/by-name/server3/configuration.nix
@@ -21,6 +21,10 @@
       zones = import ../../../zones {inherit lib;};
     };
     fail2ban.enable = true;
+    grocy = {
+      enable = true;
+      domain = "grocy.vhack.eu";
+    };
     nix-sync = {
       enable = true;
       domains = import ./websites.nix {};
diff --git a/modules/by-name/co/constants/module.nix b/modules/by-name/co/constants/module.nix
index 2115a37..1513b0c 100644
--- a/modules/by-name/co/constants/module.nix
+++ b/modules/by-name/co/constants/module.nix
@@ -52,6 +52,7 @@
       stalwart-mail-certificates = 338; # GROUP
       sharkey = 339;
       redis-sharkey = 340;
+      grocy = 341;
 
       # As per the NixOS file, the uids should not be greater or equal to 400;
     };
@@ -85,6 +86,7 @@
         systemd-oom
         sharkey
         redis-sharkey
+        grocy
         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
diff --git a/modules/by-name/gr/grocy/module.nix b/modules/by-name/gr/grocy/module.nix
new file mode 100644
index 0000000..28107f2
--- /dev/null
+++ b/modules/by-name/gr/grocy/module.nix
@@ -0,0 +1,51 @@
+{
+  config,
+  lib,
+  ...
+}: let
+  cfg = config.vhack.grocy;
+  data = "/var/lib/grocy";
+in {
+  options.vhack.grocy = {
+    enable = lib.mkEnableOption "grocy";
+
+    domain = lib.mkOption {
+      type = lib.types.str;
+      description = "FQDN for the grocy instance.";
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    services.grocy = {
+      enable = true;
+
+      hostName = cfg.domain;
+      dataDir = data;
+
+      settings = {
+        currency = "EUR";
+        culture = "sv_SE";
+        calendar.firstDayOfWeek = 1;
+      };
+    };
+
+    vhack.persist.directories = [
+      {
+        directory = data;
+        user = "grocy";
+        group = "grocy";
+        mode = "0700";
+      }
+    ];
+
+    users = {
+      groups.grocy = {
+        gid = config.vhack.constants.ids.gids.grocy;
+      };
+      users.grocy = {
+        extraGroups = ["grocy"];
+        uid = config.vhack.constants.ids.uids.grocy;
+      };
+    };
+  };
+}
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
index 2b50cf0..155d658 100644
--- a/modules/by-name/sh/sharkey/module.nix
+++ b/modules/by-name/sh/sharkey/module.nix
@@ -1,277 +1,130 @@
-# Source: https://github.com/sodiboo/system/blob/b63c7b27f49043e8701b3ff5e1441cd27d5a2fff/sharkey/module.nix
 {
   config,
   lib,
   pkgs,
-  vhackPackages,
+  pkgsUnstable,
+  nixpkgs-unstable,
   ...
 }: 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;
+  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 = {
+    services.meilisearch.settings = lib.mkOption {
+      type = lib.types.attrsOf lib.type.anything;
+      default = {};
     };
 
-    database = {
-      createLocally = lib.mkOption {
-        description = "Whether to enable local db creation.";
-        type = lib.types.bool;
-        default = true;
-      };
+    vhack.sharkey = {
+      enable = lib.mkEnableOption "sharkey";
 
-      host = lib.mkOption {
+      fqdn = lib.mkOption {
+        description = "The fully qualified domain name of this instance.";
         type = lib.types.str;
-        default = "127.0.0.1";
-        description = "The database host.";
+        example = "sharkey.shonk.social";
       };
 
-      port = lib.mkOption {
-        type = lib.types.port;
-        default = 5432;
-        description = "The database port.";
+      package = lib.mkOption {
+        type = lib.types.package;
+        default = pkgsUnstable.sharkey;
+        defaultText = lib.literalExpression "vhackPackages.sharkey";
+        description = "Sharkey package to use.";
       };
 
-      name = lib.mkOption {
-        type = lib.types.str;
-        default = "sharkey";
-        description = "The database name in postgresql.";
+      mediaDirectory = lib.mkOption {
+        type = lib.types.path;
+        default = "/var/lib/sharkey";
+        description = "The directory where sharkey stores it's data.";
       };
-    };
 
-    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.
-      '';
+      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 {
-    environment.systemPackages = [cfg.package];
-
-    vhack = {
-      nginx.enable = true;
+    services = {
+      sharkey = {
+        enable = true;
 
-      sharkey.settings = {
-        id = "aidx";
+        inherit (cfg) package;
+        openFirewall = false;
+        setupRedis = true;
+        setupPostgresql = true;
 
-        url = "https://${cfg.fqdn}/";
-        port = 5312;
+        settings =
+          cfg.settings
+          // {
+            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;
-        };
+            inherit (cfg) mediaDirectory;
+            fulltextSearch.provider = "sqlLike";
+          };
       };
 
-      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}";
+          proxyPass = "http://127.0.0.1:${toString config.services.sharkey.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";
-      };
+      # 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 = {
-        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";
-
+        # The upstream service uses DynamicUsers, which currently poses issues to our
+        # directory persisting strategy.
         User = "sharkey";
         Group = "sharkey";
+        DynamicUser = lib.mkForce false;
+      };
+    };
 
-        # Bind standard privileged ports
-        AmbientCapabilities = [];
-        CapabilityBoundingSet = [];
-
-        ReadWritePaths = [
-          "${cfg.dataDirectory}"
-        ];
+    vhack = {
+      nginx.enable = true;
 
-        # Hardening
-        DeviceAllow = [""];
-        LockPersonality = true;
-        # Probably needed for v8's JIT (crashes with it on).
-        MemoryDenyWriteExecute = false;
-        PrivateDevices = true;
-        PrivateUsers = true;
-        # Sharkey needs access to the hosts CPUs
-        ProcSubset = "all";
-        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"
-          "@chown"
-          "~@mount"
-        ];
-        UMask = "0077";
-      };
+      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 = {
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";
diff --git a/pkgs/by-name/sh/sharkey/package.nix b/pkgs/by-name/sh/sharkey/package.nix
deleted file mode 100644
index a88b7df..0000000
--- a/pkgs/by-name/sh/sharkey/package.nix
+++ /dev/null
@@ -1,176 +0,0 @@
-# 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.3";
-
-  src = fetchFromGitLab {
-    owner = "TransFem-org";
-    repo = "Sharkey";
-    domain = "activitypub.software";
-    rev = finalAttrs.version;
-    hash = "sha256-VBfkJuoQzQ93sUmJNnr1JUjA2GQNgOIuX+j8nAz3bb4=";
-    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-ALstAaN8dr5qSnc/ly0hv+oaeKrYFQ3GhObYXOv4E6I=";
-  };
-
-  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-patched/package.nix b/pkgs/by-name/st/stalwart-mail-patched/package.nix
index 062ab2c..f9bcdaa 100644
--- a/pkgs/by-name/st/stalwart-mail-patched/package.nix
+++ b/pkgs/by-name/st/stalwart-mail-patched/package.nix
@@ -11,7 +11,9 @@ in
     rustPlatform =
       rustPlatform
       // {
-        buildRustPackage = prev:
+        buildRustPackage = prev_base: let
+          prev = lib.fix prev_base;
+        in
           rustPlatform.buildRustPackage (
             lib.attrsets.recursiveUpdate
             prev
@@ -19,7 +21,8 @@ in
               pname = "stalwart-mail-patched";
 
               passthru = nixLib.warnMerge (prev.passthru or {}) {
-                # Use a reproducible source for the spamfilter instead of fetching it at runtime from GitHub.
+                # Use a reproducible source for the spamfilter
+                # instead of fetching it at runtime from GitHub.
                 inherit spamfilter;
               } "stalwart-mail passthru";
 
diff --git a/pkgs/by-name/st/stalwart-mail-patched/spam-filter.nix b/pkgs/by-name/st/stalwart-mail-patched/spam-filter.nix
index 32f5e06..be8d32f 100644
--- a/pkgs/by-name/st/stalwart-mail-patched/spam-filter.nix
+++ b/pkgs/by-name/st/stalwart-mail-patched/spam-filter.nix
@@ -4,13 +4,13 @@
 }:
 stdenv.mkDerivation (finalAttrs: {
   pname = "spam-filter";
-  version = "2.0.3";
+  version = "2.0.4";
 
   src = fetchFromGitHub {
     owner = "stalwartlabs";
     repo = "spam-filter";
     tag = "v${finalAttrs.version}";
-    hash = "sha256-NhD/qUiGhgESwR2IOzAHfDATRlgWMcCktlktvVfDONk=";
+    hash = "sha256-unSRgmXE5T1QfE41E29BjJKpEAnMtYiAefcL2p7Cjak=";
   };
 
   buildPhase = ''
diff --git a/tests/by-name/ru/rust-motd/test.nix b/tests/by-name/ru/rust-motd/test.nix
new file mode 100644
index 0000000..6623c0c
--- /dev/null
+++ b/tests/by-name/ru/rust-motd/test.nix
@@ -0,0 +1,63 @@
+{
+  nixos-lib,
+  pkgsUnstable,
+  nixpkgs-unstable,
+  vhackPackages,
+  pkgs,
+  extraModules,
+  nixLib,
+  ...
+}:
+nixos-lib.runTest {
+  hostPkgs = pkgs;
+
+  name = "rust-motd";
+
+  node = {
+    specialArgs = {inherit pkgsUnstable extraModules vhackPackages nixpkgs-unstable nixLib;};
+
+    # Use the nixpkgs as constructed by the `nixpkgs.*` options
+    pkgs = null;
+  };
+
+  nodes = {
+    server = {config, ...}: {
+      imports =
+        extraModules
+        ++ [
+          ../../../../modules
+        ];
+
+      vhack = {
+        rust-motd.enable = true;
+      };
+    };
+  };
+
+  testScript = {nodes, ...}:
+  /*
+  python
+  */
+  ''
+    from time import sleep
+
+    start_all()
+
+    # Give the service time to run.
+    sleep(3)
+
+    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)
+
+    with subtest("Motd generated"):
+      sleep(1)
+      server.succeed("cat /var/lib/rust-motd/motd | tee /dev/stderr | grep --invert-match Error")
+
+    server.copy_from_vm("/var/lib/rust-motd/motd")
+  '';
+}
diff --git a/tests/by-name/sh/sharkey-cpu/test.nix b/tests/by-name/sh/sharkey-cpu/test.nix
index 438cfb3..6082806 100644
--- a/tests/by-name/sh/sharkey-cpu/test.nix
+++ b/tests/by-name/sh/sharkey-cpu/test.nix
@@ -38,7 +38,7 @@ nixos-lib.runTest {
       };
       systemd.services = {
         # Avoid an error from this service.
-        "acme-sharkey.server".serviceConfig.ExecStart = pkgs.lib.mkForce "${pkgs.lib.getExe' pkgs.coreutils "true"}";
+        "acme-sharkey.server".enable = false;
 
         # Test that sharkey's hardening still allows access to the CPUs.
         sharkey.serviceConfig.ExecStart = let
diff --git a/update.sh b/update.sh
index e22f4ea..669ab7a 100755
--- a/update.sh
+++ b/update.sh
@@ -12,4 +12,9 @@ __update_sh_run() {
 
 __update_sh_run nix flake update
 __update_sh_run ./pkgs/update_pkgs.sh "$@"
+
+
+for host in "server2" "server3"; do
+    nix build ".#nixosConfigurations.$host.config.system.build.toplevel" --print-out-paths --no-link --option max-jobs 1
+done
 # vim: ft=sh
diff --git a/zones/vhack.eu/zone.nix b/zones/vhack.eu/zone.nix
index 070b58a..709940d 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 06 11 01;
+    serial = mkSerial 2025 08 03 01;
   };
   useOrigin = false;
 
@@ -162,6 +162,7 @@ in {
 
     mastodon.CNAME = ["server3.vhack.eu."];
     matrix.CNAME = ["server3.vhack.eu."];
+    grocy.CNAME = ["server3.vhack.eu."];
 
     miniflux.CNAME = ["server3.vhack.eu."];
     rss.CNAME = ["server3.vhack.eu."];