diff --git a/ci.nix b/ci.nix
index 16c866e76..212114538 100644
--- a/ci.nix
+++ b/ci.nix
@@ -8,6 +8,9 @@ let
         imports = [
           ./krebs
           ./krebs/2configs
+          ({ config, ... }: {
+            krebs.build.host = config.krebs.hosts.test-all-krebs-modules;
+          })
         ];
       }];
     }
diff --git a/krebs/3modules/default.nix b/krebs/3modules/default.nix
index 2772d8d37..f76d3c536 100644
--- a/krebs/3modules/default.nix
+++ b/krebs/3modules/default.nix
@@ -50,6 +50,7 @@ let
       ./shadow.nix
       ./ssl.nix
       ./sync-containers.nix
+      ./systemd.nix
       ./tinc.nix
       ./tinc_graphs.nix
       ./upstream
diff --git a/krebs/3modules/git.nix b/krebs/3modules/git.nix
index 1bfd58e31..c038fd4c6 100644
--- a/krebs/3modules/git.nix
+++ b/krebs/3modules/git.nix
@@ -36,7 +36,7 @@ let
               type = types.user;
               default = {
                 name = "fcgiwrap";
-                home = toString pkgs.empty;
+                home = toString pkgs.emptyDirectory;
               };
             };
           };
@@ -111,7 +111,7 @@ let
       type = types.user;
       default = {
         name = "git";
-        home = toString pkgs.empty;
+        home = toString pkgs.emptyDirectory;
       };
     };
   };
diff --git a/krebs/3modules/systemd.nix b/krebs/3modules/systemd.nix
new file mode 100644
index 000000000..6b0fe9672
--- /dev/null
+++ b/krebs/3modules/systemd.nix
@@ -0,0 +1,67 @@
+{ config, pkgs, ... }: let {
+  lib = import ../../lib;
+
+  body.options.krebs.systemd.services = lib.mkOption {
+    default = {};
+    type = lib.types.attrsOf (lib.types.submodule {
+      options = {
+        ifCredentialsChange = lib.mkOption {
+          default = "restart";
+          description = ''
+            Whether to reload or restart the service whenever any its
+            credentials change.  Only credentials with an absolute path in
+            LoadCredential= are supported.
+          '';
+          type = lib.types.enum [
+            "reload"
+            "restart"
+            null
+          ];
+        };
+        serviceConfig.LoadCredential = lib.mkOption {
+          apply = lib.toList;
+          type =
+            lib.types.either lib.types.str (lib.types.listOf lib.types.str);
+        };
+      };
+    });
+  };
+
+  body.config.systemd =
+    lib.mkMerge
+      (lib.flatten
+        (lib.mapAttrsToList (serviceName: cfg: let
+          paths =
+            lib.filter
+              lib.types.absolute-pathname.check
+              (map
+                (lib.compose [ lib.maybeHead (lib.match "[^:]*:(.*)") ])
+                cfg.serviceConfig.LoadCredential);
+        in
+          lib.singleton {
+            services.${serviceName} = {
+              serviceConfig = {
+                LoadCredential = cfg.serviceConfig.LoadCredential;
+              };
+            };
+          }
+          ++
+          lib.optionals (cfg.ifCredentialsChange != null) (map (path: let
+            triggerName = "trigger-${lib.systemd.encodeName path}";
+          in {
+            paths.${triggerName} = {
+              wantedBy = ["multi-user.target"];
+              pathConfig.PathChanged = path;
+            };
+            services.${triggerName} = {
+              serviceConfig = {
+                Type = "oneshot";
+                ExecStart = lib.singleton (toString [
+                  "${pkgs.systemd}/bin/systemctl ${cfg.ifCredentialsChange}"
+                  (lib.shell.escape serviceName)
+                ]);
+              };
+            };
+          }) paths)
+        ) config.krebs.systemd.services));
+}
diff --git a/krebs/3modules/tinc.nix b/krebs/3modules/tinc.nix
index 3d0cc8fb4..dca764f63 100644
--- a/krebs/3modules/tinc.nix
+++ b/krebs/3modules/tinc.nix
@@ -1,12 +1,6 @@
 with import <stockholm/lib>;
-{ config, pkgs, ... }:
-let
-  out = {
-    options.krebs.tinc = api;
-    config = imp;
-  };
-
-  api = mkOption {
+{ config, pkgs, ... }: {
+  options.krebs.tinc = mkOption {
     default = {};
     description = ''
       define a tinc network
@@ -28,10 +22,6 @@ let
                 Interface = ${netname}
                 Broadcast = no
                 ${concatMapStrings (c: "ConnectTo = ${c}\n") tinc.config.connectTo}
-                ${optionalString (tinc.config.privkey_ed25519 != null)
-                  "Ed25519PrivateKeyFile = ${tinc.config.privkey_ed25519.path}"
-                }
-                PrivateKeyFile = ${tinc.config.privkey.path}
                 Port = ${toString tinc.config.host.nets.${netname}.tinc.port}
                 ${tinc.config.extraConfig}
               '';
@@ -169,25 +159,17 @@ let
         };
 
         privkey = mkOption {
-          type = types.secret-file;
-          default = {
-            name = "${tinc.config.netname}.rsa_key.priv";
-            path = "${tinc.config.user.home}/tinc.rsa_key.priv";
-            owner = tinc.config.user;
-            source-path = toString <secrets> + "/${tinc.config.netname}.rsa_key.priv";
-          };
+          type = types.absolute-pathname;
+          default = toString <secrets> + "/${tinc.config.netname}.rsa_key.priv";
           defaultText = "‹secrets/‹netname›.rsa_key.priv›";
         };
 
         privkey_ed25519 = mkOption {
-          type = types.nullOr types.secret-file;
+          type = types.nullOr types.absolute-pathname;
           default =
-            if config.krebs.hosts.${tinc.config.host.name}.nets.${tinc.config.netname}.tinc.pubkey_ed25519 == null then null else {
-              name = "${tinc.config.netname}.ed25519_key.priv";
-              path = "${tinc.config.user.home}/tinc.ed25519_key.priv";
-              owner = tinc.config.user;
-              source-path = toString <secrets> + "/${tinc.config.netname}.ed25519_key.priv";
-            };
+            if tinc.config.host.nets.${netname}.tinc.pubkey_ed25519 == null
+              then null
+              else toString <secrets> + "/${tinc.config.netname}.ed25519_key.priv";
           defaultText = "‹secrets/‹netname›.ed25519_key.priv›";
         };
 
@@ -226,28 +208,7 @@ let
     }));
   };
 
-  imp = {
-    # TODO `environment.systemPackages = [ cfg.tincPackage cfg.iproutePackage ]` for each network,
-    # avoid conflicts in environment if the packages differ
-
-    krebs.secret.files =
-      let
-        ed25519_keys =
-          filterAttrs
-            (_: key: key != null)
-            (mapAttrs'
-              (netname: cfg:
-                nameValuePair "${netname}.ed25519_key.priv" cfg.privkey_ed25519
-              )
-              config.krebs.tinc);
-
-        rsa_keys =
-          mapAttrs'
-            (netname: cfg: nameValuePair "${netname}.rsa_key.priv" cfg.privkey)
-            config.krebs.tinc;
-      in
-        ed25519_keys // rsa_keys;
-
+  config = {
     users.users = mapAttrs' (netname: cfg:
       nameValuePair "${netname}" {
         inherit (cfg.user) home name uid;
@@ -267,34 +228,42 @@ let
       }
     ) config.krebs.tinc;
 
-    systemd.services = mapAttrs (netname: cfg:
-      let
-        tinc = cfg.tincPackage;
-        iproute = cfg.iproutePackage;
-      in {
-        description = "Tinc daemon for ${netname}";
-        after = [
-          "network.target"
-          config.krebs.secret.files."${netname}.rsa_key.priv".service
-        ] ++ optionals (cfg.privkey_ed25519 != null) [
-          config.krebs.secret.files."${netname}.ed25519_key.priv".service
+    krebs.systemd.services = mapAttrs (netname: cfg: {
+      serviceConfig.LoadCredential = filter (x: x != "") [
+        (optionalString (cfg.privkey_ed25519 != null)
+          "ed25519_key:${cfg.privkey_ed25519}"
+        )
+        "rsa_key:${cfg.privkey}"
+      ];
+    }) config.krebs.tinc;
+
+    systemd.services = mapAttrs (netname: cfg: {
+      description = "Tinc daemon for ${netname}";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      path = [
+        cfg.iproutePackage
+        cfg.tincPackage
+      ];
+      reloadIfChanged = true;
+      restartTriggers = [ cfg.confDir ];
+      serviceConfig = {
+        Restart = "always";
+        ExecStart = toString [
+          "${cfg.tincPackage}/sbin/tincd"
+          "-D"
+          "-U ${cfg.user.name}"
+          "-c /etc/tinc/${netname}"
+          "-d 0"
+          (optionalString (cfg.privkey_ed25519 != null)
+            "-o Ed25519PrivateKeyFile=\${CREDENTIALS_DIRECTORY}/ed25519_key"
+          )
+          "-o PrivateKeyFile=\${CREDENTIALS_DIRECTORY}/rsa_key"
+          "--pidfile=/var/run/tinc.${netname}.pid"
         ];
-        partOf = [
-          config.krebs.secret.files."${netname}.rsa_key.priv".service
-        ] ++ optionals (cfg.privkey_ed25519 != null) [
-          config.krebs.secret.files."${netname}.ed25519_key.priv".service
-        ];
-        wantedBy = [ "multi-user.target" ];
-        path = [ tinc iproute ];
-        reloadIfChanged = true;
-        restartTriggers = [ cfg.confDir ];
-        serviceConfig = rec {
-          Restart = "always";
-          ExecStart = "${tinc}/sbin/tincd -c /etc/tinc/${netname} -d 0 -U ${cfg.user.name} -D --pidfile=/var/run/tinc.${SyslogIdentifier}.pid";
-          ExecReload = "${tinc}/sbin/tinc -n ${netname} reload";
-          SyslogIdentifier = netname;
-        };
-      }
-    ) config.krebs.tinc;
+        ExecReload = "${cfg.tincPackage}/sbin/tinc -n ${netname} reload";
+        SyslogIdentifier = netname;
+      };
+    }) config.krebs.tinc;
   };
-in out
+}
diff --git a/krebs/5pkgs/simple/empty.nix b/krebs/5pkgs/simple/empty.nix
deleted file mode 100644
index a45723b65..000000000
--- a/krebs/5pkgs/simple/empty.nix
+++ /dev/null
@@ -1,2 +0,0 @@
-{ pkgs }:
-pkgs.runCommand "empty-1.0.0" {} "mkdir $out"
diff --git a/lib/default.nix b/lib/default.nix
index 738e52186..574713e48 100644
--- a/lib/default.nix
+++ b/lib/default.nix
@@ -39,6 +39,8 @@ let
       listToAttrs (map (name: nameValuePair name set.${name})
                        (filter (flip hasAttr set) names));
 
+    maybeHead = x: if isList x && length x > 0 then head x else null;
+
     packageName = pkg:
       pkg.pname or (parseDrvName pkg.name).name;