about summary refs log tree commit diff stats
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--flake.lock193
-rw-r--r--flake.nix23
-rw-r--r--hosts/by-name/server2/configuration.nix6
-rw-r--r--hosts/by-name/server3/configuration.nix5
-rw-r--r--hosts/by-name/server3/secrets/rocie/login.age16
-rw-r--r--hosts/by-name/server3/websites.nix8
-rw-r--r--modules/by-name/co/constants/module.nix2
-rw-r--r--modules/by-name/gi/git-server/module.nix6
-rw-r--r--modules/by-name/ro/rocie/module.nix59
-rw-r--r--modules/by-name/sy/system-info/module.nix1
-rwxr-xr-xpkgs/by-name/st/stalwart-mail-patched/update.sh2
-rwxr-xr-xscripts/ping_hosts.sh12
-rw-r--r--scripts/update_hosts.remote41
-rwxr-xr-xscripts/update_hosts.sh24
-rw-r--r--secrets.nix1
-rw-r--r--tests/by-name/gi/git-server/test.nix2
-rw-r--r--tests/by-name/ro/rocie/secrets/login.age16
-rw-r--r--tests/by-name/ro/rocie/test.nix106
-rwxr-xr-xupdate.sh5
-rw-r--r--zones/vhack.eu/zone.nix2
20 files changed, 447 insertions, 83 deletions
diff --git a/flake.lock b/flake.lock
index 887b4fd..5be31d9 100644
--- a/flake.lock
+++ b/flake.lock
@@ -3,7 +3,9 @@
     "agenix": {
       "inputs": {
         "darwin": "darwin",
-        "home-manager": "home-manager",
+        "home-manager": [
+          "home-manager"
+        ],
         "nixpkgs": [
           "nixpkgs"
         ],
@@ -73,11 +75,11 @@
     },
     "crane": {
       "locked": {
-        "lastModified": 1771121070,
-        "narHash": "sha256-aIlv7FRXF9q70DNJPI237dEDAznSKaXmL5lfK/Id/bI=",
+        "lastModified": 1773857772,
+        "narHash": "sha256-5xsK26KRHf0WytBtsBnQYC/lTWDhQuT57HJ7SzuqZcM=",
         "owner": "ipetkov",
         "repo": "crane",
-        "rev": "a2812c19f1ed2e5ed5ce2ef7109798b575c180e1",
+        "rev": "b556d7bbae5ff86e378451511873dfd07e4504cd",
         "type": "github"
       },
       "original": {
@@ -141,11 +143,11 @@
         ]
       },
       "locked": {
-        "lastModified": 1769524058,
-        "narHash": "sha256-zygdD6X1PcVNR2PsyK4ptzrVEiAdbMqLos7utrMDEWE=",
+        "lastModified": 1773889306,
+        "narHash": "sha256-PAqwnsBSI9SVC2QugvQ3xeYCB0otOwCacB1ueQj2tgw=",
         "owner": "nix-community",
         "repo": "disko",
-        "rev": "71a3fc97d80881e91710fe721f1158d3b96ae14d",
+        "rev": "5ad85c82cc52264f4beddc934ba57f3789f28347",
         "type": "github"
       },
       "original": {
@@ -203,11 +205,11 @@
         ]
       },
       "locked": {
-        "lastModified": 1763319842,
-        "narHash": "sha256-YG19IyrTdnVn0l3DvcUYm85u3PaqBt6tI6VvolcuHnA=",
+        "lastModified": 1772893680,
+        "narHash": "sha256-JDqZMgxUTCq85ObSaFw0HhE+lvdOre1lx9iI6vYyOEs=",
         "owner": "cachix",
         "repo": "git-hooks.nix",
-        "rev": "7275fa67fbbb75891c16d9dee7d88e58aea2d761",
+        "rev": "8baab586afc9c9b57645a734c820e4ac0a604af9",
         "type": "github"
       },
       "original": {
@@ -241,51 +243,34 @@
     "home-manager": {
       "inputs": {
         "nixpkgs": [
-          "agenix",
           "nixpkgs"
         ]
       },
       "locked": {
-        "lastModified": 1745494811,
-        "narHash": "sha256-YZCh2o9Ua1n9uCvrvi5pRxtuVNml8X2a03qIFfRKpFs=",
+        "lastModified": 1773963144,
+        "narHash": "sha256-WzBOBfSay3GYilUfKaUa1Mbf8/jtuAiJIedx7fWuIX4=",
         "owner": "nix-community",
         "repo": "home-manager",
-        "rev": "abfad3d2958c9e6300a883bd443512c55dfeb1be",
+        "rev": "a91b3ea73a765614d90360580b689c48102d1d33",
         "type": "github"
       },
       "original": {
         "owner": "nix-community",
+        "ref": "release-25.11",
         "repo": "home-manager",
         "type": "github"
       }
     },
-    "home-manager_2": {
+    "impermanence": {
       "inputs": {
+        "home-manager": [
+          "home-manager"
+        ],
         "nixpkgs": [
-          "impermanence",
           "nixpkgs"
         ]
       },
       "locked": {
-        "lastModified": 1768598210,
-        "narHash": "sha256-kkgA32s/f4jaa4UG+2f8C225Qvclxnqs76mf8zvTVPg=",
-        "owner": "nix-community",
-        "repo": "home-manager",
-        "rev": "c47b2cc64a629f8e075de52e4742de688f930dc6",
-        "type": "github"
-      },
-      "original": {
-        "owner": "nix-community",
-        "repo": "home-manager",
-        "type": "github"
-      }
-    },
-    "impermanence": {
-      "inputs": {
-        "home-manager": "home-manager_2",
-        "nixpkgs": "nixpkgs"
-      },
-      "locked": {
         "lastModified": 1769548169,
         "narHash": "sha256-03+JxvzmfwRu+5JafM0DLbxgHttOQZkUtDWBmeUkN8Y=",
         "owner": "nix-community",
@@ -317,27 +302,27 @@
     },
     "nixpkgs": {
       "locked": {
-        "lastModified": 1768564909,
-        "narHash": "sha256-Kell/SpJYVkHWMvnhqJz/8DqQg2b6PguxVWOuadbHCc=",
-        "owner": "nixos",
+        "lastModified": 1774091411,
+        "narHash": "sha256-P+9bwtvc75FdmXQpg9GLiA5a7xXddhwOam6AQZMRGn8=",
+        "owner": "NixOS",
         "repo": "nixpkgs",
-        "rev": "e4bae1bd10c9c57b2cf517953ab70060a828ee6f",
+        "rev": "339e6f7841d6a785c1ca468e02cd0be88db5604e",
         "type": "github"
       },
       "original": {
-        "owner": "nixos",
-        "ref": "nixos-unstable",
+        "owner": "NixOS",
+        "ref": "nixos-25.11-small",
         "repo": "nixpkgs",
         "type": "github"
       }
     },
     "nixpkgs-unstable": {
       "locked": {
-        "lastModified": 1771129128,
-        "narHash": "sha256-zi224RqZ0dlo44oOMJTzQiRdNa5sJ2UjGiDgpK4xJeM=",
+        "lastModified": 1774106199,
+        "narHash": "sha256-US5Tda2sKmjrg2lNHQL3jRQ6p96cgfWh3J1QBliQ8Ws=",
         "owner": "NixOS",
         "repo": "nixpkgs",
-        "rev": "626a1db9776eb9db61b1e1f394d928505162cf69",
+        "rev": "6c9a78c09ff4d6c21d0319114873508a6ec01655",
         "type": "github"
       },
       "original": {
@@ -347,20 +332,96 @@
         "type": "github"
       }
     },
-    "nixpkgs_2": {
+    "rocie": {
+      "inputs": {
+        "crane": [
+          "crane"
+        ],
+        "nixpkgs": [
+          "nixpkgs"
+        ],
+        "rocie-mobile": "rocie-mobile",
+        "rocie-server": "rocie-server",
+        "rust-overlay": [
+          "rust-overlay"
+        ],
+        "treefmt-nix": [
+          "treefmt-nix"
+        ]
+      },
       "locked": {
-        "lastModified": 1771151655,
-        "narHash": "sha256-vU2GXgAXkVwC5wbmPvt9iCf2MnMqr3P2un0euDAvGJ0=",
-        "owner": "NixOS",
-        "repo": "nixpkgs",
-        "rev": "2594032e2ed39e048f30c7b16d623a545fac4b05",
-        "type": "github"
+        "lastModified": 1774191391,
+        "narHash": "sha256-uxAuBw9qb5pQpgreV9l0RD0CfRWuh/2QzM2xBApXCaQ=",
+        "ref": "prime",
+        "rev": "69e667daff09bf3f44089b25e73fb7c391eeb5fa",
+        "revCount": 9,
+        "type": "git",
+        "url": "https://git.foss-syndicate.org/bpeetz/rocie/nix"
       },
       "original": {
-        "owner": "NixOS",
-        "ref": "nixos-25.11-small",
-        "repo": "nixpkgs",
-        "type": "github"
+        "ref": "prime",
+        "type": "git",
+        "url": "https://git.foss-syndicate.org/bpeetz/rocie/nix"
+      }
+    },
+    "rocie-mobile": {
+      "inputs": {
+        "crane": [
+          "rocie",
+          "crane"
+        ],
+        "nixpkgs": [
+          "rocie",
+          "nixpkgs"
+        ],
+        "rust-overlay": [
+          "rocie",
+          "rust-overlay"
+        ],
+        "treefmt-nix": [
+          "rocie",
+          "treefmt-nix"
+        ]
+      },
+      "locked": {
+        "lastModified": 1774118765,
+        "narHash": "sha256-YHCNeo9amdHBD/sk+SYEdNFYajy+6I6IIyFN37bwNk8=",
+        "ref": "prime",
+        "rev": "c0b29e4be7f0fff59934ef6e7c52dea2ee31e0b3",
+        "revCount": 18,
+        "type": "git",
+        "url": "https://git.foss-syndicate.org/bpeetz/rocie/web-client"
+      },
+      "original": {
+        "ref": "prime",
+        "type": "git",
+        "url": "https://git.foss-syndicate.org/bpeetz/rocie/web-client"
+      }
+    },
+    "rocie-server": {
+      "inputs": {
+        "nixpkgs": [
+          "rocie",
+          "nixpkgs"
+        ],
+        "treefmt-nix": [
+          "rocie",
+          "treefmt-nix"
+        ]
+      },
+      "locked": {
+        "lastModified": 1774188691,
+        "narHash": "sha256-Cng2OK/7EJyfxCACqa09J4ioZyCyGUM3bKqH/NwUu3s=",
+        "ref": "prime",
+        "rev": "70017ad6ab922bf5bfabca4377739863817e244e",
+        "revCount": 52,
+        "type": "git",
+        "url": "https://git.foss-syndicate.org/bpeetz/rocie/server"
+      },
+      "original": {
+        "ref": "prime",
+        "type": "git",
+        "url": "https://git.foss-syndicate.org/bpeetz/rocie/server"
       }
     },
     "root": {
@@ -372,10 +433,12 @@
         "disko": "disko",
         "flake-compat": "flake-compat",
         "flake-utils": "flake-utils",
+        "home-manager": "home-manager",
         "impermanence": "impermanence",
         "library": "library",
-        "nixpkgs": "nixpkgs_2",
+        "nixpkgs": "nixpkgs",
         "nixpkgs-unstable": "nixpkgs-unstable",
+        "rocie": "rocie",
         "rust-overlay": "rust-overlay",
         "simple-nixos-mailserver": "simple-nixos-mailserver",
         "systems": "systems",
@@ -389,11 +452,11 @@
         ]
       },
       "locked": {
-        "lastModified": 1771125043,
-        "narHash": "sha256-ldf/s49n6rOAxl7pYLJGGS1N/assoHkCOWdEdLyNZkc=",
+        "lastModified": 1774062094,
+        "narHash": "sha256-ba3c+hS7KzEiwtZRGHagIAYdcmdY3rCSWVCyn64rx7s=",
         "owner": "oxalica",
         "repo": "rust-overlay",
-        "rev": "4912f951a26dc8142b176be2c2ad834319dc06e8",
+        "rev": "c807e83cc2e32adc35f51138b3bdef722c0812ab",
         "type": "github"
       },
       "original": {
@@ -414,11 +477,11 @@
         ]
       },
       "locked": {
-        "lastModified": 1766537863,
-        "narHash": "sha256-HEt+wbazRgJYeY+lgj65bxhPyVc4x7NEB2bs5NU6DF8=",
+        "lastModified": 1773912645,
+        "narHash": "sha256-QHzRqq6gh+t3F/QU9DkP7X63dDDcuIQmaDz12p7ANTg=",
         "owner": "simple-nixos-mailserver",
         "repo": "nixos-mailserver",
-        "rev": "23f0a53ca6e58e61e1ea2b86791c69b79c91656d",
+        "rev": "25e6dbb8fca3b6e779c5a46fd03bd760b2165bb5",
         "type": "gitlab"
       },
       "original": {
@@ -450,11 +513,11 @@
         ]
       },
       "locked": {
-        "lastModified": 1770228511,
-        "narHash": "sha256-wQ6NJSuFqAEmIg2VMnLdCnUc0b7vslUohqqGGD+Fyxk=",
+        "lastModified": 1773297127,
+        "narHash": "sha256-6E/yhXP7Oy/NbXtf1ktzmU8SdVqJQ09HC/48ebEGBpk=",
         "owner": "numtide",
         "repo": "treefmt-nix",
-        "rev": "337a4fe074be1042a35086f15481d763b8ddc0e7",
+        "rev": "71b125cd05fbfd78cab3e070b73544abe24c5016",
         "type": "github"
       },
       "original": {
diff --git a/flake.nix b/flake.nix
index 4c847b6..23a7d3e 100644
--- a/flake.nix
+++ b/flake.nix
@@ -4,6 +4,12 @@
   inputs = {
     nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.11-small";
     nixpkgs-unstable.url = "github:NixOS/nixpkgs/nixos-unstable-small";
+    home-manager = {
+      url = "github:nix-community/home-manager/release-25.11";
+      inputs = {
+        nixpkgs.follows = "nixpkgs";
+      };
+    };
 
     library.url = "git+https://git.foss-syndicate.org/vhack.eu/nix-library?ref=prime";
 
@@ -53,6 +59,7 @@
       inputs = {
         nixpkgs.follows = "nixpkgs";
         systems.follows = "systems";
+        home-manager.follows = "home-manager";
       };
     };
     back = {
@@ -72,7 +79,19 @@
     };
     impermanence = {
       url = "github:nix-community/impermanence";
-      inputs = {};
+      inputs = {
+        nixpkgs.follows = "nixpkgs";
+        home-manager.follows = "home-manager";
+      };
+    };
+    rocie = {
+      url = "git+https://git.foss-syndicate.org/bpeetz/rocie/nix?ref=prime";
+      inputs = {
+        nixpkgs.follows = "nixpkgs";
+        treefmt-nix.follows = "treefmt-nix";
+        crane.follows = "crane";
+        rust-overlay.follows = "rust-overlay";
+      };
     };
     simple-nixos-mailserver = {
       url = "gitlab:simple-nixos-mailserver/nixos-mailserver/nixos-25.11";
@@ -96,6 +115,7 @@
     disko,
     impermanence,
     simple-nixos-mailserver,
+    rocie,
     ...
   } @ attrs: let
     system = "x86_64-linux";
@@ -129,6 +149,7 @@
       disko.nixosModules.default
       impermanence.nixosModules.impermanence
       simple-nixos-mailserver.nixosModule
+      rocie.nixosModules.default
     ];
 
     tests = import ./tests {inherit pkgs specialArgs nixLib;};
diff --git a/hosts/by-name/server2/configuration.nix b/hosts/by-name/server2/configuration.nix
index 65e3b24..5b85e69 100644
--- a/hosts/by-name/server2/configuration.nix
+++ b/hosts/by-name/server2/configuration.nix
@@ -1,8 +1,4 @@
-{
-  config,
-  lib,
-  ...
-}: {
+{lib, ...}: {
   imports = [
     ./networking.nix # network configuration that just works
     ./hardware.nix
diff --git a/hosts/by-name/server3/configuration.nix b/hosts/by-name/server3/configuration.nix
index 1736a32..a7cdc1d 100644
--- a/hosts/by-name/server3/configuration.nix
+++ b/hosts/by-name/server3/configuration.nix
@@ -21,9 +21,10 @@
       zones = import ../../../zones {inherit lib;};
     };
     fail2ban.enable = true;
-    grocy = {
+    rocie = {
       enable = true;
-      domain = "grocy.vhack.eu";
+      domain = "rocie.vhack.eu";
+      loginSecret = ./secrets/rocie/login.age;
     };
     nix-sync = {
       enable = true;
diff --git a/hosts/by-name/server3/secrets/rocie/login.age b/hosts/by-name/server3/secrets/rocie/login.age
new file mode 100644
index 0000000..0a6b8d3
--- /dev/null
+++ b/hosts/by-name/server3/secrets/rocie/login.age
@@ -0,0 +1,16 @@
+-----BEGIN AGE ENCRYPTED FILE-----
+YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBWN3ZFdjMyNjBvQXQ1YVYz
+OTBHMWYvbk5xK1ArVDYvMmZEUFF5dHRSajFNCkFXSCtPV2VVdW50amVVMVYxYXBK
+SHd4bXRXSEw0ZnJ3TTMvbmZGTStFbEUKLT4gWDI1NTE5IDZYMFJKWVBPVmc5QmFK
+M0xGNGNJZHNnRWxBcytaZXU3VWlqMzQvdDdhbm8KcVlIZTlRYVBZNEx1djZJbURD
+Qy9NUTEwYVN0VjhZbkc3TTNuUWd0QTFjcwotPiBzc2gtZWQyNTUxOSBweXU5Ymcg
+SnZ0czg2OUszeFJIbmNpU2RDV2tnMVRlUUVvMk5BTWcwbFFibDVzQXZUbwpJbkVC
+bFFqYzJZQTFQWDM4Zk42ZzE4Q2kzQlh4aTRtMHUyRlgrWlc1WUtjCi0+IFFELWdy
+ZWFzZQpCMDVUczNTa01KS2hFREhpM0k5L3ZHT0NDUEtVck1iZE13cnVkSi9Kdk80
+WXU1M3A0V1pZbGxZaVNGanZRUnE4CmYwUlo4QlVFTjc4YlFaQjZyU3BybTAwaTVP
+WHc2K1FyYzduT0RBYWtEa0xSREEKLS0tIExFSThrd2xGT3lEU1p6VXE0eWpZUG5z
+ZlVXMWJFS0NxczhycXl1VWc1bmsK4Dq740IgQP9CnGtkUrwWxM4QUOnQTgWe1YbI
+LtC471ykB4YlPIbMiqUdnBuFZQ9cBAOT2YuicxbI+/2Rb/mSRV0l8dGKpc2fqOVy
+zLLcX8A48NfmcbPeUtlv/HnVfu6Fe7X91V0bw6eMjWSvvcpQXsaIAd7TEPTpexCs
+gfAWd+brZMq6VzwslklxO4S5ZIu0jUSz4q6covAuxfEW3hdSbIg=
+-----END AGE ENCRYPTED FILE-----
diff --git a/hosts/by-name/server3/websites.nix b/hosts/by-name/server3/websites.nix
index 466f1e9..ece6247 100644
--- a/hosts/by-name/server3/websites.nix
+++ b/hosts/by-name/server3/websites.nix
@@ -1,7 +1,7 @@
 {...}: let
   mkWkd = domain: {
     domain = "openpgpkey.${domain}";
-    repositoryUrl = "https://git.foss-syndicate.org/vhack.eu/pgp-wkd.git";
+    repositoryUrl = "https://git.foss-syndicate.org/vhack.eu/pgp-wkd";
     extraSettings = {
       locations."/.well-known/openpgpkey/".extraConfig = ''
         default_type application/octet-stream;
@@ -15,17 +15,17 @@
 in [
   {
     domain = "vhack.eu";
-    repositoryUrl = "https://codeberg.org/vhack.eu/website.git";
+    repositoryUrl = "https://codeberg.org/vhack.eu/website";
   }
   {
     domain = "b-peetz.de";
-    repositoryUrl = "https://git.foss-syndicate.org/bpeetz/b-peetz.de.git";
+    repositoryUrl = "https://git.foss-syndicate.org/bpeetz/b-peetz.de";
   }
 
   # Trinitrix
   {
     domain = "trinitrix.vhack.eu";
-    repositoryUrl = "https://codeberg.org/trinitrix/website.git";
+    repositoryUrl = "https://codeberg.org/trinitrix/website";
   }
 
   # WKD
diff --git a/modules/by-name/co/constants/module.nix b/modules/by-name/co/constants/module.nix
index 3de9608..b94020b 100644
--- a/modules/by-name/co/constants/module.nix
+++ b/modules/by-name/co/constants/module.nix
@@ -55,6 +55,7 @@
       grocy = 341;
       anubis = 342;
       postfix-tlspol = 343;
+      rocie = 344;
 
       # As per the NixOS file, the uids should not be greater or equal to 400;
     };
@@ -94,6 +95,7 @@
         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
+        rocie
         ;
 
       # The gid should match the uid. Thus should not be >= 400;
diff --git a/modules/by-name/gi/git-server/module.nix b/modules/by-name/gi/git-server/module.nix
index db35897..4ddfca4 100644
--- a/modules/by-name/gi/git-server/module.nix
+++ b/modules/by-name/gi/git-server/module.nix
@@ -88,6 +88,12 @@ in {
         scanPath = "${config.services.gitolite.dataDir}/repositories";
         user = "git";
         group = "git";
+
+        # Don't bypass `cgit` when performing a http only clone.
+        # This is slightly slower, but we don't need to worry about the access
+        # restrictions also being by-passed.
+        gitHttpBackend.enable = false;
+
         settings = {
           branch-sort = "age";
 
diff --git a/modules/by-name/ro/rocie/module.nix b/modules/by-name/ro/rocie/module.nix
new file mode 100644
index 0000000..1e419b8
--- /dev/null
+++ b/modules/by-name/ro/rocie/module.nix
@@ -0,0 +1,59 @@
+{
+  config,
+  lib,
+  ...
+}: let
+  cfg = config.vhack.rocie;
+  data = "/var/lib/rocie";
+in {
+  options.vhack.rocie = {
+    enable = lib.mkEnableOption "Rocie integration into vhack.eu";
+
+    domain = lib.mkOption {
+      type = lib.types.str;
+      description = "The domain where to deploy rocie";
+    };
+
+    loginSecret = lib.mkOption {
+      type = lib.types.path;
+      description = "The age encrypted secret file for rocie, passed to agenix";
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    rocie = {
+      enable = true;
+      inherit (cfg) domain;
+
+      dbPath = "${data}/database.db";
+
+      secretKeyFile = config.age.secrets.rocie_secret.path;
+    };
+
+    vhack.persist.directories = [
+      {
+        directory = data;
+        user = "rocie";
+        group = "rocie";
+        mode = "0700";
+      }
+    ];
+
+    users = {
+      groups.rocie = {
+        gid = config.vhack.constants.ids.gids.rocie;
+      };
+      users.rocie = {
+        group = "rocie";
+        uid = config.vhack.constants.ids.uids.rocie;
+      };
+    };
+
+    age.secrets.rocie_secret = {
+      file = cfg.loginSecret;
+      mode = "700";
+      owner = "rocie";
+      group = "rocie";
+    };
+  };
+}
diff --git a/modules/by-name/sy/system-info/module.nix b/modules/by-name/sy/system-info/module.nix
index 8136ae5..21d901f 100644
--- a/modules/by-name/sy/system-info/module.nix
+++ b/modules/by-name/sy/system-info/module.nix
@@ -39,6 +39,7 @@
       # TODO(@bpeetz): Check which service opens these ports: <2025-01-28>
       "64738" = "???";
       "4190" = "???";
+      "8112" = "???";
     };
   in ''
     ${mode} ${builtins.toString port}: ${
diff --git a/pkgs/by-name/st/stalwart-mail-patched/update.sh b/pkgs/by-name/st/stalwart-mail-patched/update.sh
index 33d6e2b..a70ddc6 100755
--- a/pkgs/by-name/st/stalwart-mail-patched/update.sh
+++ b/pkgs/by-name/st/stalwart-mail-patched/update.sh
@@ -1,5 +1,5 @@
 #!/usr/bin/env sh
 
-nix-update --file ../../../standalone_pkgs.nix stalwart-mail-patched.passthru.spamfilter --option pure-eval false
+NIX_CONFIG="pure-eval = false" nix-update --file ../../../standalone_pkgs.nix stalwart-mail-patched.passthru.spamfilter --option pure-eval false
 
 # vim: ft=sh
diff --git a/scripts/ping_hosts.sh b/scripts/ping_hosts.sh
new file mode 100755
index 0000000..fba2490
--- /dev/null
+++ b/scripts/ping_hosts.sh
@@ -0,0 +1,12 @@
+#! /usr/bin/env sh
+
+user="${1-$USER}"
+hosts="${2-server2 server3}"
+
+for host in $hosts; do
+    echo "Checking status of '$user@$host.vhack.eu' ..."
+
+    ssh "$user@$host.vhack.eu" "set -x; systemctl --failed"
+done
+
+# vim: ft=sh
diff --git a/scripts/update_hosts.remote b/scripts/update_hosts.remote
new file mode 100644
index 0000000..7323a33
--- /dev/null
+++ b/scripts/update_hosts.remote
@@ -0,0 +1,41 @@
+#! /usr/bin/env sh
+
+# This is the remote side of `update_hosts.sh`, it will be copied to the remote host
+# and is responsible for performing the update.
+
+set -e
+
+PATH_add() {
+    nix_expr="$1"
+    what="$(nix build "nixpkgs#$nix_expr.out" --print-out-paths --no-link)"
+
+    printf "Adding '%s' (%s/bin) to PATH..\n" "$nix_expr" "$what"
+
+    PATH="$what/bin:$PATH"
+    export PATH
+}
+
+branch="$1"
+
+# We don't have access to git by default, so evaluate it here
+PATH_add git
+
+# By-default these systems use cppnix, which can't build our config. So let's switch to
+# lix.
+PATH_add lixPackageSets.latest.lix
+
+# We might or might not have python, and we need it, because we use the unwrapped
+# `nixos-update`.
+PATH_add python3
+PATH_add nixos-rebuild-ng
+
+set -x
+cd /etc/nixos
+
+sudo git fetch --all --prune
+sudo git switch "$branch"
+sudo git pull --rebase
+
+PYTHONNOUSERSITE='true' sudo --preserve-env=PATH --preserve-env=PYTHONNOUSERSITE ".nixos-rebuild-ng-wrapped" --no-reexec boot
+
+sudo reboot
diff --git a/scripts/update_hosts.sh b/scripts/update_hosts.sh
new file mode 100755
index 0000000..505f061
--- /dev/null
+++ b/scripts/update_hosts.sh
@@ -0,0 +1,24 @@
+#! /usr/bin/env sh
+set -e
+
+base_dir="$(git rev-parse --show-toplevel)"
+
+user="${1-$USER}"
+hosts="${2-server2 server3}"
+branch="${3-main}"
+
+for host in $hosts; do
+    echo "Updating '$user@$host.vhack.eu' ..."
+
+    new_system="$(nix build ".#nixosConfigurations.$host.config.system.build.toplevel" --no-link --print-out-paths)"
+
+    printf "Copying closure ..\n"
+    nix-copy-closure "$user@$host.vhack.eu" "$new_system"
+
+    printf "Deploying remote side script ..\n"
+    scp "$base_dir/scripts/update_hosts.remote" "$user@$host.vhack.eu:update_host.remote"
+
+    printf "Executing remote side script ..\n"
+    ssh -t "$user@$host.vhack.eu" "chmod +x update_host.remote; ./update_host.remote '$branch'"
+done
+# vim: ft=sh
diff --git a/secrets.nix b/secrets.nix
index 8d3ae92..3af0afa 100644
--- a/secrets.nix
+++ b/secrets.nix
@@ -70,4 +70,5 @@ in
     "./tests/by-name/em/email-dns/secrets/dkim/bob.com/private.age".publicKeys = [soispha sils testingKey];
     "./tests/by-name/em/email-dns/secrets/dkim/mail1.server.com/private.age".publicKeys = [soispha sils testingKey];
     "./tests/by-name/em/email-dns/secrets/dkim/mail2.server.com/private.age".publicKeys = [soispha sils testingKey];
+    "./tests/by-name/ro/rocie/secrets/login.age".publicKeys = [soispha sils testingKey];
   }
diff --git a/tests/by-name/gi/git-server/test.nix b/tests/by-name/gi/git-server/test.nix
index 524efcb..4e503b6 100644
--- a/tests/by-name/gi/git-server/test.nix
+++ b/tests/by-name/gi/git-server/test.nix
@@ -205,7 +205,7 @@ in
 
         cd ~bob
         # Disable ssl verification, as the certs are self-signed
-        git -c http.sslVerify=false clone https://server/alice/alice-project.git
+        git -c http.sslVerify=false clone https://server/alice/alice-project
       ''}")
 
       with subtest("Alice can change settings in her repo"):
diff --git a/tests/by-name/ro/rocie/secrets/login.age b/tests/by-name/ro/rocie/secrets/login.age
new file mode 100644
index 0000000..33d63be
--- /dev/null
+++ b/tests/by-name/ro/rocie/secrets/login.age
@@ -0,0 +1,16 @@
+-----BEGIN AGE ENCRYPTED FILE-----
+YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAzMWE5dTBiU0hDUC9jUi93
+Y1phYllHRk9YSHBzUGQ2YmF5ZC9ydXNGV0JrClRpTjZZUHZ5MEFFa0VrYVVhTkE2
+eCtSaEU1YVlhNjFNYlRRYzNCdjhYRWMKLT4gWDI1NTE5IDUrZWdDUmpQcFBOcE0x
+UE5QRDR5NXVXUHdQOVk3UGV1S3lCc0pUQmZIZ00KUldVSVF3TzB0cHFVaDZuNlZR
+b2FoT0lVSTZydHFTNHhnQ3U0NGdSR1k1MAotPiBzc2gtZWQyNTUxOSBSc2dXcFEg
+dldGZU15UXgrRTMxRkp2MEVKUllWQ3VFdnJDMnM4OS8wc202WW9lNW5BYwpPWjV0
+cmNuaDlPZndtUVVScm5TaGlvVUhHa0JiN1MvbDhCTTUxYzNhM3RRCi0+IDxXeEhv
+cC1ncmVhc2UKM3N5OHRLNTJEY1NIeGlWYm9yR096Y1NpSlVOM1lYQk9jOHkxU3N2
+K2c3QitDYnR6QTJOOWczV0xBa2dEUE1PTQpYU2Z1elZwRzU0Tm1RVDE2VWVqekUw
+bFROLzU0c2NNTXYwL2N5QkxTaGtXUWxKVVF6SE0KLS0tIGlYMHIvUkJpZUR0SHo4
+cldLSTdnbU90SGJTcGZGaHkyOTZON0hka3BLdlEKeP4nHmKWvJfqgEXuiLBMzldi
+n1qIsnlF3IU1EA0abJg/RK1BFwWlx4wBlLmViw6UTL+VEw8lv23PuZl2t7UtXVzQ
+smXDapW8nInNmTaElBPdwJ072/dD0Ly+KF95Qr0FDDv+jlKG/D/Mw+xD4jvuJHSo
+2HQnPF6MLTjCxpyPPggleWgKrBQggHBjm/pHtOKmPC5qfp+LAjmQoJXny/0X6cA=
+-----END AGE ENCRYPTED FILE-----
diff --git a/tests/by-name/ro/rocie/test.nix b/tests/by-name/ro/rocie/test.nix
new file mode 100644
index 0000000..c2ba97a
--- /dev/null
+++ b/tests/by-name/ro/rocie/test.nix
@@ -0,0 +1,106 @@
+{
+  nixos-lib,
+  pkgsUnstable,
+  nixpkgs-unstable,
+  vhackPackages,
+  pkgs,
+  extraModules,
+  nixLib,
+  ...
+}:
+nixos-lib.runTest {
+  hostPkgs = pkgs; # the Nixpkgs package set used outside the VMs
+
+  name = "rocie";
+
+  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 = {
+        "rocie.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
+        ];
+
+      age.identityPaths = ["${../../../common/email/hostKey}"];
+
+      vhack = {
+        persist.enable = true;
+        nginx.enable = true;
+        rocie = {
+          enable = true;
+          domain = "rocie.server";
+          loginSecret = ./secrets/login.age;
+        };
+      };
+    };
+
+    client = {...}: {
+      imports = [
+        ../../../common/acme/client.nix
+        ../../../common/dns/client.nix
+      ];
+    };
+  };
+
+  testScript = {nodes, ...}: let
+    acme = import ../../../common/acme {inherit pkgs;};
+  in
+    acme.prepare ["server" "client"]
+    # Python
+    ''
+      server.wait_for_unit("rocie.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 --verbose https://rocie.server/api/can-be-provisioned > out.file")
+      client.copy_from_vm("out.file")
+    '';
+}
diff --git a/update.sh b/update.sh
index 903b0ea..1b10dab 100755
--- a/update.sh
+++ b/update.sh
@@ -13,7 +13,6 @@ __update_sh_run() {
 __update_sh_run nix flake update
 __update_sh_run ./pkgs/update_pkgs.sh "$@"
 
-for host in "server2" "server3"; do
-    __update_sh_run nix build ".#nixosConfigurations.$host.config.system.build.toplevel" --print-out-paths --no-link --option max-jobs 1
-done
+__update_sh_run build.sh
+__update_sh_run check.sh
 # vim: ft=sh
diff --git a/zones/vhack.eu/zone.nix b/zones/vhack.eu/zone.nix
index 709940d..0e018e9 100644
--- a/zones/vhack.eu/zone.nix
+++ b/zones/vhack.eu/zone.nix
@@ -162,7 +162,7 @@ in {
 
     mastodon.CNAME = ["server3.vhack.eu."];
     matrix.CNAME = ["server3.vhack.eu."];
-    grocy.CNAME = ["server3.vhack.eu."];
+    rocie.CNAME = ["server3.vhack.eu."];
 
     miniflux.CNAME = ["server3.vhack.eu."];
     rss.CNAME = ["server3.vhack.eu."];