about summary refs log tree commit diff
path: root/nixos
diff options
context:
space:
mode:
Diffstat (limited to 'nixos')
-rw-r--r--nixos/modules/services/misc/portunus.nix14
-rw-r--r--nixos/modules/tasks/filesystems/zfs.nix34
-rw-r--r--nixos/modules/testing/test-instrumentation.nix2
-rw-r--r--nixos/modules/virtualisation/incus.nix74
-rw-r--r--nixos/tests/incus/container.nix101
-rw-r--r--nixos/tests/incus/default.nix2
-rw-r--r--nixos/tests/incus/incusd-options.nix104
-rw-r--r--nixos/tests/incus/preseed.nix63
-rw-r--r--nixos/tests/incus/virtual-machine.nix6
-rw-r--r--nixos/tests/logrotate.nix90
10 files changed, 319 insertions, 171 deletions
diff --git a/nixos/modules/services/misc/portunus.nix b/nixos/modules/services/misc/portunus.nix
index ab78479c96cd3..bdb35da788e3a 100644
--- a/nixos/modules/services/misc/portunus.nix
+++ b/nixos/modules/services/misc/portunus.nix
@@ -231,12 +231,14 @@ in
     };
 
     systemd.services = {
-      dex.serviceConfig = mkIf cfg.dex.enable {
-        # `dex.service` is super locked down out of the box, but we need some
-        # place to write the SQLite database. This creates $STATE_DIRECTORY below
-        # /var/lib/private because DynamicUser=true, but it gets symlinked into
-        # /var/lib/dex inside the unit
-        StateDirectory = "dex";
+      dex = mkIf cfg.dex.enable {
+        serviceConfig = {
+          # `dex.service` is super locked down out of the box, but we need some
+          # place to write the SQLite database. This creates $STATE_DIRECTORY below
+          # /var/lib/private because DynamicUser=true, but it gets symlinked into
+          # /var/lib/dex inside the unit
+          StateDirectory = "dex";
+        };
       };
 
       portunus = {
diff --git a/nixos/modules/tasks/filesystems/zfs.nix b/nixos/modules/tasks/filesystems/zfs.nix
index 2c749d45d7a15..b75817a011cbd 100644
--- a/nixos/modules/tasks/filesystems/zfs.nix
+++ b/nixos/modules/tasks/filesystems/zfs.nix
@@ -441,21 +441,45 @@ in
           {manpage}`systemd.time(7)`.
         '';
       };
+
+      randomizedDelaySec = mkOption {
+        default = "6h";
+        type = types.str;
+        example = "12h";
+        description = ''
+          Add a randomized delay before each ZFS trim.
+          The delay will be chosen between zero and this value.
+          This value must be a time span in the format specified by
+          {manpage}`systemd.time(7)`
+        '';
+      };
     };
 
     services.zfs.autoScrub = {
       enable = mkEnableOption "periodic scrubbing of ZFS pools";
 
       interval = mkOption {
-        default = "Sun, 02:00";
+        default = "monthly";
         type = types.str;
-        example = "daily";
+        example = "quarterly";
         description = ''
           Systemd calendar expression when to scrub ZFS pools. See
           {manpage}`systemd.time(7)`.
         '';
       };
 
+      randomizedDelaySec = mkOption {
+        default = "6h";
+        type = types.str;
+        example = "12h";
+        description = ''
+          Add a randomized delay before each ZFS autoscrub.
+          The delay will be chosen between zero and this value.
+          This value must be a time span in the format specified by
+          {manpage}`systemd.time(7)`
+        '';
+      };
+
       pools = mkOption {
         default = [];
         type = types.listOf types.str;
@@ -862,6 +886,7 @@ in
         timerConfig = {
           OnCalendar = cfgScrub.interval;
           Persistent = "yes";
+          RandomizedDelaySec = cfgScrub.randomizedDelaySec;
         };
       };
     })
@@ -879,7 +904,10 @@ in
         serviceConfig.ExecStart = "${pkgs.runtimeShell} -c 'for pool in $(zpool list -H -o name); do zpool trim $pool;  done || true' ";
       };
 
-      systemd.timers.zpool-trim.timerConfig.Persistent = "yes";
+      systemd.timers.zpool-trim.timerConfig = {
+        Persistent = "yes";
+        RandomizedDelaySec = cfgTrim.randomizedDelaySec;
+      };
     })
   ];
 }
diff --git a/nixos/modules/testing/test-instrumentation.nix b/nixos/modules/testing/test-instrumentation.nix
index 28abbe66adafb..dd8816063c70c 100644
--- a/nixos/modules/testing/test-instrumentation.nix
+++ b/nixos/modules/testing/test-instrumentation.nix
@@ -218,6 +218,8 @@ in
 
     services.displayManager.logToJournal = true;
 
+    services.logrotate.enable = lib.mkDefault false;
+
     # Make sure we use the Guest Agent from the QEMU package for testing
     # to reduce the closure size required for the tests.
     services.qemuGuest.package = pkgs.qemu_test.ga;
diff --git a/nixos/modules/virtualisation/incus.nix b/nixos/modules/virtualisation/incus.nix
index 4d04853d20a56..87568390bd3b8 100644
--- a/nixos/modules/virtualisation/incus.nix
+++ b/nixos/modules/virtualisation/incus.nix
@@ -105,6 +105,37 @@ let
       path = "${pkgs.OVMFFull.fd}/FV/${ovmf-prefix}_VARS.fd";
     }
   ];
+
+  environment = lib.mkMerge [
+    {
+      INCUS_LXC_TEMPLATE_CONFIG = "${pkgs.lxcfs}/share/lxc/config";
+      INCUS_OVMF_PATH = ovmf;
+      INCUS_USBIDS_PATH = "${pkgs.hwdata}/share/hwdata/usb.ids";
+      PATH = lib.mkForce serverBinPath;
+    }
+    (lib.mkIf (cfg.ui.enable) { "INCUS_UI" = cfg.ui.package; })
+  ];
+
+  incus-startup = pkgs.writeShellScript "incus-startup" ''
+    case "$1" in
+        start)
+          systemctl is-active incus.service -q && exit 0
+          exec incusd activateifneeded
+        ;;
+
+        stop)
+          systemctl is-active incus.service -q || exit 0
+          exec incusd shutdown
+        ;;
+
+        *)
+          echo "unknown argument \`$1'" >&2
+          exit 1
+        ;;
+    esac
+
+    exit 0
+  '';
 in
 {
   meta = {
@@ -137,6 +168,14 @@ in
         description = "The incus client package to use. This package is added to PATH.";
       };
 
+      softDaemonRestart = lib.mkOption {
+        type = lib.types.bool;
+        default = true;
+        description = ''
+          Allow for incus.service to be stopped without affecting running instances.
+        '';
+      };
+
       preseed = lib.mkOption {
         type = lib.types.nullOr (lib.types.submodule { freeformType = preseedFormat.type; });
 
@@ -282,6 +321,8 @@ in
     systemd.services.incus = {
       description = "Incus Container and Virtual Machine Management Daemon";
 
+      inherit environment;
+
       wantedBy = lib.mkIf (!cfg.socketActivation) [ "multi-user.target" ];
       after = [
         "network-online.target"
@@ -296,20 +337,10 @@ in
 
       wants = [ "network-online.target" ];
 
-      environment = lib.mkMerge [
-        {
-          INCUS_LXC_TEMPLATE_CONFIG = "${pkgs.lxcfs}/share/lxc/config";
-          INCUS_OVMF_PATH = ovmf;
-          INCUS_USBIDS_PATH = "${pkgs.hwdata}/share/hwdata/usb.ids";
-          PATH = lib.mkForce serverBinPath;
-        }
-        (lib.mkIf (cfg.ui.enable) { "INCUS_UI" = cfg.ui.package; })
-      ];
-
       serviceConfig = {
         ExecStart = "${cfg.package}/bin/incusd --group incus-admin";
         ExecStartPost = "${cfg.package}/bin/incusd waitready --timeout=${cfg.startTimeout}";
-        ExecStop = "${cfg.package}/bin/incus admin shutdown";
+        ExecStop = lib.optionalString (!cfg.softDaemonRestart) "${cfg.package}/bin/incus admin shutdown";
 
         KillMode = "process"; # when stopping, leave the containers alone
         Delegate = "yes";
@@ -324,6 +355,27 @@ in
       };
     };
 
+    systemd.services.incus-startup = lib.mkIf cfg.softDaemonRestart {
+      description = "Incus Instances Startup/Shutdown";
+
+      inherit environment;
+
+      after = [
+        "incus.service"
+        "incus.socket"
+      ];
+      requires = [ "incus.socket" ];
+
+      serviceConfig = {
+        ExecStart = "${incus-startup} start";
+        ExecStop = "${incus-startup} stop";
+        RemainAfterExit = true;
+        TimeoutStartSec = "600s";
+        TimeoutStopSec = "600s";
+        Type = "oneshot";
+      };
+    };
+
     systemd.sockets.incus = {
       description = "Incus UNIX socket";
       wantedBy = [ "sockets.target" ];
diff --git a/nixos/tests/incus/container.nix b/nixos/tests/incus/container.nix
index a71c5355046a5..f6bc295448e28 100644
--- a/nixos/tests/incus/container.nix
+++ b/nixos/tests/incus/container.nix
@@ -70,51 +70,60 @@ in
         machine.succeed("incus exec container mount | grep 'lxcfs on /proc/cpuinfo type fuse.lxcfs'")
         machine.succeed("incus exec container mount | grep 'lxcfs on /proc/meminfo type fuse.lxcfs'")
 
-    with subtest("Container CPU limits can be managed"):
-        set_container("limits.cpu 1")
-        cpuinfo = machine.succeed("incus exec container grep -- -c ^processor /proc/cpuinfo").strip()
-        assert cpuinfo == "1", f"Wrong number of CPUs reported from /proc/cpuinfo, want: 1, got: {cpuinfo}"
-
-        set_container("limits.cpu 2")
-        cpuinfo = machine.succeed("incus exec container grep -- -c ^processor /proc/cpuinfo").strip()
-        assert cpuinfo == "2", f"Wrong number of CPUs reported from /proc/cpuinfo, want: 2, got: {cpuinfo}"
-
-    with subtest("Container memory limits can be managed"):
-        set_container("limits.memory 64MB")
-        meminfo = machine.succeed("incus exec container grep -- MemTotal /proc/meminfo").strip()
-        meminfo_bytes = " ".join(meminfo.split(' ')[-2:])
-        assert meminfo_bytes == "62500 kB", f"Wrong amount of memory reported from /proc/meminfo, want: '62500 kB', got: '{meminfo_bytes}'"
-
-        set_container("limits.memory 128MB")
-        meminfo = machine.succeed("incus exec container grep -- MemTotal /proc/meminfo").strip()
-        meminfo_bytes = " ".join(meminfo.split(' ')[-2:])
-        assert meminfo_bytes == "125000 kB", f"Wrong amount of memory reported from /proc/meminfo, want: '125000 kB', got: '{meminfo_bytes}'"
-
-    with subtest("lxc-container generator configures plain container"):
-        # reuse the existing container to save some time
-        machine.succeed("incus exec container test -- -e /run/systemd/system/service.d/zzz-lxc-service.conf")
-        check_sysctl("container")
-
-    with subtest("lxc-container generator configures nested container"):
-        machine.execute("incus delete --force container")
-        machine.succeed("incus launch nixos container --config security.nesting=true")
-        with machine.nested("Waiting for instance to start and be usable"):
-          retry(instance_is_up)
-
-        machine.fail("incus exec container test -- -e /run/systemd/system/service.d/zzz-lxc-service.conf")
-        target = machine.succeed("incus exec container readlink -- -f /run/systemd/system/systemd-binfmt.service").strip()
-        assert target == "/dev/null", "lxc generator did not correctly mask /run/systemd/system/systemd-binfmt.service"
-
-        check_sysctl("container")
-
-    with subtest("lxc-container generator configures privileged container"):
-        machine.execute("incus delete --force container")
-        machine.succeed("incus launch nixos container --config security.privileged=true")
-        with machine.nested("Waiting for instance to start and be usable"):
-          retry(instance_is_up)
-
-        machine.succeed("incus exec container test -- -e /run/systemd/system/service.d/zzz-lxc-service.conf")
-
-        check_sysctl("container")
+    with subtest("resource limits"):
+        with subtest("Container CPU limits can be managed"):
+            set_container("limits.cpu 1")
+            cpuinfo = machine.succeed("incus exec container grep -- -c ^processor /proc/cpuinfo").strip()
+            assert cpuinfo == "1", f"Wrong number of CPUs reported from /proc/cpuinfo, want: 1, got: {cpuinfo}"
+
+            set_container("limits.cpu 2")
+            cpuinfo = machine.succeed("incus exec container grep -- -c ^processor /proc/cpuinfo").strip()
+            assert cpuinfo == "2", f"Wrong number of CPUs reported from /proc/cpuinfo, want: 2, got: {cpuinfo}"
+
+        with subtest("Container memory limits can be managed"):
+            set_container("limits.memory 64MB")
+            meminfo = machine.succeed("incus exec container grep -- MemTotal /proc/meminfo").strip()
+            meminfo_bytes = " ".join(meminfo.split(' ')[-2:])
+            assert meminfo_bytes == "62500 kB", f"Wrong amount of memory reported from /proc/meminfo, want: '62500 kB', got: '{meminfo_bytes}'"
+
+            set_container("limits.memory 128MB")
+            meminfo = machine.succeed("incus exec container grep -- MemTotal /proc/meminfo").strip()
+            meminfo_bytes = " ".join(meminfo.split(' ')[-2:])
+            assert meminfo_bytes == "125000 kB", f"Wrong amount of memory reported from /proc/meminfo, want: '125000 kB', got: '{meminfo_bytes}'"
+
+    with subtest("lxc-generator"):
+        with subtest("lxc-container generator configures plain container"):
+            # reuse the existing container to save some time
+            machine.succeed("incus exec container test -- -e /run/systemd/system/service.d/zzz-lxc-service.conf")
+            check_sysctl("container")
+
+        with subtest("lxc-container generator configures nested container"):
+            machine.execute("incus delete --force container")
+            machine.succeed("incus launch nixos container --config security.nesting=true")
+            with machine.nested("Waiting for instance to start and be usable"):
+              retry(instance_is_up)
+
+            machine.fail("incus exec container test -- -e /run/systemd/system/service.d/zzz-lxc-service.conf")
+            target = machine.succeed("incus exec container readlink -- -f /run/systemd/system/systemd-binfmt.service").strip()
+            assert target == "/dev/null", "lxc generator did not correctly mask /run/systemd/system/systemd-binfmt.service"
+
+            check_sysctl("container")
+
+        with subtest("lxc-container generator configures privileged container"):
+            machine.execute("incus delete --force container")
+            machine.succeed("incus launch nixos container --config security.privileged=true")
+            with machine.nested("Waiting for instance to start and be usable"):
+              retry(instance_is_up)
+
+            machine.succeed("incus exec container test -- -e /run/systemd/system/service.d/zzz-lxc-service.conf")
+
+            check_sysctl("container")
+
+    with subtest("softDaemonRestart"):
+        with subtest("Instance remains running when softDaemonRestart is enabled and services is stopped"):
+            pid = machine.succeed("incus info container | grep 'PID'").split(":")[1].strip()
+            machine.succeed(f"ps {pid}")
+            machine.succeed("systemctl stop incus")
+            machine.succeed(f"ps {pid}")
   '';
 })
diff --git a/nixos/tests/incus/default.nix b/nixos/tests/incus/default.nix
index b850c4fba018d..d778928a3b9f5 100644
--- a/nixos/tests/incus/default.nix
+++ b/nixos/tests/incus/default.nix
@@ -16,9 +16,9 @@
       boot.initrd.systemd.enable = true;
     };
   };
+  incusd-options = import ./incusd-options.nix { inherit system pkgs; };
   lxd-to-incus = import ./lxd-to-incus.nix { inherit system pkgs; };
   openvswitch = import ./openvswitch.nix { inherit system pkgs; };
-  preseed = import ./preseed.nix { inherit system pkgs; };
   socket-activated = import ./socket-activated.nix { inherit system pkgs; };
   storage = import ./storage.nix { inherit system pkgs; };
   ui = import ./ui.nix { inherit system pkgs; };
diff --git a/nixos/tests/incus/incusd-options.nix b/nixos/tests/incus/incusd-options.nix
new file mode 100644
index 0000000000000..b1e7f497fe3e7
--- /dev/null
+++ b/nixos/tests/incus/incusd-options.nix
@@ -0,0 +1,104 @@
+# this is a set of tests for non-default options. typically the default options
+# will be handled by the other tests
+import ../make-test-python.nix (
+  { pkgs, lib, ... }:
+
+  let
+    releases = import ../../release.nix {
+      configuration = {
+        # Building documentation makes the test unnecessarily take a longer time:
+        documentation.enable = lib.mkForce false;
+      };
+    };
+
+    container-image-metadata = releases.lxdContainerMeta.${pkgs.stdenv.hostPlatform.system};
+    container-image-rootfs = releases.lxdContainerImage.${pkgs.stdenv.hostPlatform.system};
+  in
+  {
+    name = "incusd-options";
+
+    meta = {
+      maintainers = lib.teams.lxc.members;
+    };
+
+    nodes.machine = {
+      virtualisation = {
+        cores = 2;
+        memorySize = 1024;
+        diskSize = 4096;
+
+        incus = {
+          enable = true;
+          softDaemonRestart = false;
+
+          preseed = {
+            networks = [
+              {
+                name = "nixostestbr0";
+                type = "bridge";
+                config = {
+                  "ipv4.address" = "10.0.100.1/24";
+                  "ipv4.nat" = "true";
+                };
+              }
+            ];
+            profiles = [
+              {
+                name = "default";
+                devices = {
+                  eth0 = {
+                    name = "eth0";
+                    network = "nixostestbr0";
+                    type = "nic";
+                  };
+                  root = {
+                    path = "/";
+                    pool = "nixostest_pool";
+                    size = "35GiB";
+                    type = "disk";
+                  };
+                };
+              }
+            ];
+            storage_pools = [
+              {
+                name = "nixostest_pool";
+                driver = "dir";
+              }
+            ];
+          };
+        };
+      };
+      networking.nftables.enable = true;
+    };
+
+    testScript = ''
+      def instance_is_up(_) -> bool:
+          status, _ = machine.execute("incus exec container --disable-stdin --force-interactive /run/current-system/sw/bin/systemctl -- is-system-running")
+          return status == 0
+
+      machine.wait_for_unit("incus.service")
+      machine.wait_for_unit("incus-preseed.service")
+
+      with subtest("Container image can be imported"):
+          machine.succeed("incus image import ${container-image-metadata}/*/*.tar.xz ${container-image-rootfs}/*/*.tar.xz --alias nixos")
+
+      with subtest("Container can be launched and managed"):
+          machine.succeed("incus launch nixos container")
+          with machine.nested("Waiting for instance to start and be usable"):
+            retry(instance_is_up)
+          machine.succeed("echo true | incus exec container /run/current-system/sw/bin/bash -")
+
+      with subtest("Verify preseed resources created"):
+          machine.succeed("incus profile show default")
+          machine.succeed("incus network info nixostestbr0")
+          machine.succeed("incus storage show nixostest_pool")
+
+      with subtest("Instance is stopped when softDaemonRestart is disabled and services is stopped"):
+          pid = machine.succeed("incus info container | grep 'PID'").split(":")[1].strip()
+          machine.succeed(f"ps {pid}")
+          machine.succeed("systemctl stop incus")
+          machine.fail(f"ps {pid}")
+    '';
+  }
+)
diff --git a/nixos/tests/incus/preseed.nix b/nixos/tests/incus/preseed.nix
deleted file mode 100644
index f2d928115f3ec..0000000000000
--- a/nixos/tests/incus/preseed.nix
+++ /dev/null
@@ -1,63 +0,0 @@
-import ../make-test-python.nix ({ pkgs, lib, ... } :
-
-{
-  name = "incus-preseed";
-
-  meta = {
-    maintainers = lib.teams.lxc.members;
-  };
-
-  nodes.machine = { lib, ... }: {
-    virtualisation = {
-      incus.enable = true;
-
-      incus.preseed = {
-        networks = [
-          {
-            name = "nixostestbr0";
-            type = "bridge";
-            config = {
-              "ipv4.address" = "10.0.100.1/24";
-              "ipv4.nat" = "true";
-            };
-          }
-        ];
-        profiles = [
-          {
-            name = "nixostest_default";
-            devices = {
-              eth0 = {
-                name = "eth0";
-                network = "nixostestbr0";
-                type = "nic";
-              };
-              root = {
-                path = "/";
-                pool = "default";
-                size = "35GiB";
-                type = "disk";
-              };
-            };
-          }
-        ];
-        storage_pools = [
-          {
-            name = "nixostest_pool";
-            driver = "dir";
-          }
-        ];
-      };
-    };
-    networking.nftables.enable = true;
-  };
-
-  testScript = ''
-    machine.wait_for_unit("incus.service")
-    machine.wait_for_unit("incus-preseed.service")
-
-    with subtest("Verify preseed resources created"):
-      machine.succeed("incus profile show nixostest_default")
-      machine.succeed("incus network info nixostestbr0")
-      machine.succeed("incus storage show nixostest_pool")
-  '';
-})
diff --git a/nixos/tests/incus/virtual-machine.nix b/nixos/tests/incus/virtual-machine.nix
index eebbbd113ed16..c460447f7832d 100644
--- a/nixos/tests/incus/virtual-machine.nix
+++ b/nixos/tests/incus/virtual-machine.nix
@@ -75,5 +75,11 @@ in
         machine.succeed("incus config set ${instance-name} limits.cpu=2")
         count = int(machine.succeed("incus exec ${instance-name} -- nproc").strip())
         assert count == 2, f"Wrong number of CPUs reported, want: 2, got: {count}"
+
+    with subtest("Instance remains running when softDaemonRestart is enabled and services is stopped"):
+        pid = machine.succeed("incus info ${instance-name} | grep 'PID'").split(":")[1].strip()
+        machine.succeed(f"ps {pid}")
+        machine.succeed("systemctl stop incus")
+        machine.succeed(f"ps {pid}")
   '';
 })
diff --git a/nixos/tests/logrotate.nix b/nixos/tests/logrotate.nix
index bcbe89c259ae5..f9c5e90609709 100644
--- a/nixos/tests/logrotate.nix
+++ b/nixos/tests/logrotate.nix
@@ -16,52 +16,60 @@ import ./make-test-python.nix ({ pkgs, ... }: rec {
   };
 
   nodes = {
-    defaultMachine = { ... }: { };
+    defaultMachine = { ... }: {
+      services.logrotate.enable = true;
+    };
     failingMachine = { ... }: {
-      services.logrotate.configFile = pkgs.writeText "logrotate.conf" ''
-        # self-written config file
-        su notarealuser notagroupeither
-      '';
+      services.logrotate = {
+        enable = true;
+        configFile = pkgs.writeText "logrotate.conf" ''
+          # self-written config file
+          su notarealuser notagroupeither
+        '';
+      };
     };
     machine = { config, ... }: {
       imports = [ importTest ];
 
-      services.logrotate.settings = {
-        # remove default frequency header and add another
-        header = {
-          frequency = null;
-          delaycompress = true;
-        };
-        # extra global setting... affecting nothing
-        last_line = {
-          global = true;
-          priority = 2000;
-          shred = true;
-        };
-        # using mail somewhere should add --mail to logrotate invocation
-        sendmail = {
-          mail = "user@domain.tld";
-        };
-        # postrotate should be suffixed by 'endscript'
-        postrotate = {
-          postrotate = "touch /dev/null";
-        };
-        # check checkConfig works as expected: there is nothing to check here
-        # except that the file build passes
-        checkConf = {
-          su = "root utmp";
-          createolddir = "0750 root utmp";
-          create = "root utmp";
-          "create " = "0750 root utmp";
-        };
-        # multiple paths should be aggregated
-        multipath = {
-          files = [ "file1" "file2" ];
-        };
-        # overriding imported path should keep existing attributes
-        # (e.g. olddir is still set)
-        import = {
-          notifempty = true;
+      services.logrotate = {
+        enable = true;
+        settings = {
+          # remove default frequency header and add another
+          header = {
+            frequency = null;
+            delaycompress = true;
+          };
+          # extra global setting... affecting nothing
+          last_line = {
+            global = true;
+            priority = 2000;
+            shred = true;
+          };
+          # using mail somewhere should add --mail to logrotate invocation
+          sendmail = {
+            mail = "user@domain.tld";
+          };
+          # postrotate should be suffixed by 'endscript'
+          postrotate = {
+            postrotate = "touch /dev/null";
+          };
+          # check checkConfig works as expected: there is nothing to check here
+          # except that the file build passes
+          checkConf = {
+            su = "root utmp";
+            createolddir = "0750 root utmp";
+            create = "root utmp";
+            "create " = "0750 root utmp";
+          };
+          # multiple paths should be aggregated
+          multipath = {
+            files = [ "file1" "file2" ];
+          };
+          # overriding imported path should keep existing attributes
+          # (e.g. olddir is still set)
+          import = {
+            notifempty = true;
+          };
         };
       };
     };