about summary refs log tree commit diff
path: root/nixos
diff options
context:
space:
mode:
Diffstat (limited to 'nixos')
-rw-r--r--nixos/doc/manual/release-notes/rl-1909.xml2
-rw-r--r--nixos/doc/manual/release-notes/rl-2003.xml13
-rw-r--r--nixos/lib/make-iso9660-image.nix6
-rw-r--r--nixos/modules/config/system-environment.nix7
-rw-r--r--nixos/modules/module-list.nix1
-rw-r--r--nixos/modules/services/amqp/rabbitmq.nix10
-rw-r--r--nixos/modules/services/backup/borgbackup.nix25
-rw-r--r--nixos/modules/services/misc/matrix-synapse.nix40
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/blackbox.nix53
-rw-r--r--nixos/modules/services/networking/networkmanager.nix23
-rw-r--r--nixos/modules/services/networking/pppd.nix134
-rw-r--r--nixos/modules/services/x11/desktop-managers/gnome3.nix8
-rw-r--r--nixos/modules/tasks/filesystems/zfs.nix17
-rw-r--r--nixos/tests/all-tests.nix1
-rw-r--r--nixos/tests/pppd.nix62
15 files changed, 352 insertions, 50 deletions
diff --git a/nixos/doc/manual/release-notes/rl-1909.xml b/nixos/doc/manual/release-notes/rl-1909.xml
index 446597e74fe9..1b7ca76c2f05 100644
--- a/nixos/doc/manual/release-notes/rl-1909.xml
+++ b/nixos/doc/manual/release-notes/rl-1909.xml
@@ -532,6 +532,8 @@
        is set to <literal>/var/lib/gitlab/state</literal>, <literal>gitlab</literal> and all parent directories
        must be owned by either <literal>root</literal> or the user specified in <option>services.gitlab.user</option>.
      </para>
+   </listitem>
+   <listitem>
      <para>
       The <option>networking.useDHCP</option> option is unsupported in combination with
       <option>networking.useNetworkd</option> in anticipation of defaulting to it by default.
diff --git a/nixos/doc/manual/release-notes/rl-2003.xml b/nixos/doc/manual/release-notes/rl-2003.xml
index bdf56acd5451..ab0951e831ce 100644
--- a/nixos/doc/manual/release-notes/rl-2003.xml
+++ b/nixos/doc/manual/release-notes/rl-2003.xml
@@ -36,6 +36,19 @@
      quirk in the boot menu.
     </para>
    </listitem>
+   <listitem>
+     <para>
+       By default zfs pools will now be trimmed on a weekly basis.
+       Trimming is only done on supported devices (i.e. NVME or SSDs)
+       and should improve throughput and lifetime of these devices.
+       It is controlled by the <varname>services.zfs.trim.enable</varname> varname.
+       The zfs scrub service (<varname>services.zfs.autoScrub.enable</varname>)
+       and the zfs autosnapshot service (<varname>services.zfs.autoSnapshot.enable</varname>)
+       are now only enabled if zfs is set in <varname>config.boot.initrd.supportedFilesystems</varname> or
+       <varname>config.boot.supportedFilesystems</varname>. These lists will automatically contain
+       zfs as soon as any zfs mountpoint is configured in <varname>fileSystems</varname>.
+     </para>
+   </listitem>
   </itemizedlist>
  </section>
 
diff --git a/nixos/lib/make-iso9660-image.nix b/nixos/lib/make-iso9660-image.nix
index 8cd19b6e1874..0f3f2b5b5234 100644
--- a/nixos/lib/make-iso9660-image.nix
+++ b/nixos/lib/make-iso9660-image.nix
@@ -10,9 +10,9 @@
   contents
 
 , # In addition to `contents', the closure of the store paths listed
-  # in `packages' are also placed in the Nix store of the CD.  This is
-  # a list of attribute sets {object, symlink} where `object' if a
-  # store path whose closure will be copied, and `symlink' is a
+  # in `storeContents' are also placed in the Nix store of the CD.
+  # This is a list of attribute sets {object, symlink} where `object'
+  # is a store path whose closure will be copied, and `symlink' is a
   # symlink to `object' that will be added to the CD.
   storeContents ? []
 
diff --git a/nixos/modules/config/system-environment.nix b/nixos/modules/config/system-environment.nix
index 361c3cfc553d..4888740ba3d5 100644
--- a/nixos/modules/config/system-environment.nix
+++ b/nixos/modules/config/system-environment.nix
@@ -88,6 +88,13 @@ in
           (mapAttrsToList pamVariable
           (zipAttrsWith (n: concatLists)
             [
+              # Make sure security wrappers are prioritized without polluting
+              # shell environments with an extra entry. Sessions which depend on
+              # pam for its environment will otherwise have eg. broken sudo. In
+              # particular Gnome Shell sometimes fails to source a proper
+              # environment from a shell.
+              { PATH = [ config.security.wrapperDir ]; }
+
               (mapAttrs (n: toList) cfg.sessionVariables)
               suffixedVariables
             ]));
diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix
index 4d177ae9699e..5214126ff7ed 100644
--- a/nixos/modules/module-list.nix
+++ b/nixos/modules/module-list.nix
@@ -666,6 +666,7 @@
   ./services/networking/polipo.nix
   ./services/networking/powerdns.nix
   ./services/networking/pdns-recursor.nix
+  ./services/networking/pppd.nix
   ./services/networking/pptpd.nix
   ./services/networking/prayer.nix
   ./services/networking/privoxy.nix
diff --git a/nixos/modules/services/amqp/rabbitmq.nix b/nixos/modules/services/amqp/rabbitmq.nix
index 38d109234946..697732426ccf 100644
--- a/nixos/modules/services/amqp/rabbitmq.nix
+++ b/nixos/modules/services/amqp/rabbitmq.nix
@@ -80,10 +80,12 @@ in {
       configItems = mkOption {
         default = {};
         type = types.attrsOf types.str;
-        example = {
-          "auth_backends.1.authn" = "rabbit_auth_backend_ldap";
-          "auth_backends.1.authz" = "rabbit_auth_backend_internal";
-        };
+        example = literalExample ''
+          {
+            "auth_backends.1.authn" = "rabbit_auth_backend_ldap";
+            "auth_backends.1.authz" = "rabbit_auth_backend_internal";
+          }
+        '';
         description = ''
           Configuration options in RabbitMQ's new config file format,
           which is a simple key-value format that can not express nested
diff --git a/nixos/modules/services/backup/borgbackup.nix b/nixos/modules/services/backup/borgbackup.nix
index 2ad116a7872a..10d42325a6b1 100644
--- a/nixos/modules/services/backup/borgbackup.nix
+++ b/nixos/modules/services/backup/borgbackup.nix
@@ -8,7 +8,7 @@ let
     builtins.substring 0 1 x == "/"      # absolute path
     || builtins.substring 0 1 x == "."   # relative path
     || builtins.match "[.*:.*]" == null; # not machine:path
- 
+
   mkExcludeFile = cfg:
     # Write each exclude pattern to a new line
     pkgs.writeText "excludefile" (concatStringsSep "\n" cfg.exclude);
@@ -104,12 +104,12 @@ let
       install = "install -o ${cfg.user} -g ${cfg.group}";
     in
       nameValuePair "borgbackup-job-${name}" (stringAfter [ "users" ] (''
-        # Eensure that the home directory already exists
+        # Ensure that the home directory already exists
         # We can't assert createHome == true because that's not the case for root
-        cd "${config.users.users.${cfg.user}.home}"                                                                                                         
+        cd "${config.users.users.${cfg.user}.home}"
         ${install} -d .config/borg
         ${install} -d .cache/borg
-      '' + optionalString (isLocalPath cfg.repo) ''
+      '' + optionalString (isLocalPath cfg.repo && !cfg.removableDevice) ''
         ${install} -d ${escapeShellArg cfg.repo}
       ''));
 
@@ -163,6 +163,13 @@ let
       + " without at least one public key";
   };
 
+  mkRemovableDeviceAssertions = name: cfg: {
+    assertion = !(isLocalPath cfg.repo) -> !cfg.removableDevice;
+    message = ''
+      borgbackup.repos.${name}: repo isn't a local path, thus it can't be a removable device!
+    '';
+  };
+
 in {
   meta.maintainers = with maintainers; [ dotlambda ];
 
@@ -202,6 +209,12 @@ in {
             example = "user@machine:/path/to/repo";
           };
 
+          removableDevice = mkOption {
+            type = types.bool;
+            default = false;
+            description = "Whether the repo (which must be local) is a removable device.";
+          };
+
           archiveBaseName = mkOption {
             type = types.strMatching "[^/{}]+";
             default = "${globalConfig.networking.hostName}-${name}";
@@ -511,7 +524,6 @@ in {
     type = types.attrsOf (types.submodule (
       { ... }: {
         options = {
-          
           path = mkOption {
             type = types.path;
             description = ''
@@ -598,7 +610,8 @@ in {
     (with config.services.borgbackup; {
       assertions =
         mapAttrsToList mkPassAssertion jobs
-        ++ mapAttrsToList mkKeysAssertion repos;
+        ++ mapAttrsToList mkKeysAssertion repos
+        ++ mapAttrsToList mkRemovableDeviceAssertions jobs;
 
       system.activationScripts = mapAttrs' mkActivationScript jobs;
 
diff --git a/nixos/modules/services/misc/matrix-synapse.nix b/nixos/modules/services/misc/matrix-synapse.nix
index 018fac386163..0f4eb2ccfcad 100644
--- a/nixos/modules/services/misc/matrix-synapse.nix
+++ b/nixos/modules/services/misc/matrix-synapse.nix
@@ -79,7 +79,11 @@ turn_user_lifetime: "${cfg.turn_user_lifetime}"
 user_creation_max_duration: ${cfg.user_creation_max_duration}
 bcrypt_rounds: ${cfg.bcrypt_rounds}
 allow_guest_access: ${boolToString cfg.allow_guest_access}
-trusted_third_party_id_servers: ${builtins.toJSON cfg.trusted_third_party_id_servers}
+
+account_threepid_delegates:
+  ${optionalString (cfg.account_threepid_delegates.email != null) "email: ${cfg.account_threepid_delegates.email}"}
+  ${optionalString (cfg.account_threepid_delegates.msisdn != null) "msisdn: ${cfg.account_threepid_delegates.msisdn}"}
+
 room_invite_state_types: ${builtins.toJSON cfg.room_invite_state_types}
 ${optionalString (cfg.macaroon_secret_key != null) ''
   macaroon_secret_key: "${cfg.macaroon_secret_key}"
@@ -102,6 +106,7 @@ perspectives:
     '') cfg.servers)}
     }
   }
+redaction_retention_period: ${toString cfg.redaction_retention_period}
 app_service_config_files: ${builtins.toJSON cfg.app_service_config_files}
 
 ${cfg.extraConfig}
@@ -552,14 +557,18 @@ in {
           accessible to anonymous users.
         '';
       };
-      trusted_third_party_id_servers = mkOption {
-        type = types.listOf types.str;
-        default = [
-          "matrix.org"
-          "vector.im"
-        ];
+      account_threepid_delegates.email = mkOption {
+        type = types.nullOr types.str;
+        default = null;
         description = ''
-          The list of identity servers trusted to verify third party identifiers by this server.
+          Delegate email sending to https://example.org
+        '';
+      };
+      account_threepid_delegates.msisdn = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = ''
+          Delegate SMS sending to this local process (https://localhost:8090)
         '';
       };
       room_invite_state_types = mkOption {
@@ -600,6 +609,13 @@ in {
           A list of application service config file to use
         '';
       };
+      redaction_retention_period = mkOption {
+        type = types.int;
+        default = 7;
+        description = ''
+          How long to keep redacted events in unredacted form in the database.
+        '';
+      };
       extraConfig = mkOption {
         type = types.lines;
         default = "";
@@ -699,4 +715,12 @@ in {
       };
     };
   };
+
+  imports = [
+    (mkRemovedOptionModule [ "services" "matrix-synapse" "trusted_third_party_id_servers" ] ''
+      The `trusted_third_party_id_servers` option as been removed in `matrix-synapse` v1.4.0
+      as the behavior is now obsolete.
+    '')
+  ];
+
 }
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/blackbox.nix b/nixos/modules/services/monitoring/prometheus/exporters/blackbox.nix
index ca4366121e12..8a90afa99842 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/blackbox.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/blackbox.nix
@@ -3,16 +3,34 @@
 with lib;
 
 let
+  logPrefix = "services.prometheus.exporter.blackbox";
   cfg = config.services.prometheus.exporters.blackbox;
 
-  checkConfig = file: pkgs.runCommand "checked-blackbox-exporter.conf" {
-    preferLocalBuild = true;
-    buildInputs = [ pkgs.buildPackages.prometheus-blackbox-exporter ]; } ''
-    ln -s ${file} $out
-    blackbox_exporter --config.check --config.file $out
-  '';
-in
-{
+  # This ensures that we can deal with string paths, path types and
+  # store-path strings with context.
+  coerceConfigFile = file:
+    if (builtins.isPath file) || (lib.isStorePath file) then
+      file
+    else
+      (lib.warn ''
+        ${logPrefix}: configuration file "${file}" is being copied to the nix-store.
+        If you would like to avoid that, please set enableConfigCheck to false.
+      '' /. + file);
+  checkConfigLocation = file:
+    if lib.hasPrefix "/tmp/" file then
+      throw
+      "${logPrefix}: configuration file must not reside within /tmp - it won't be visible to the systemd service."
+    else
+      true;
+  checkConfig = file:
+    pkgs.runCommand "checked-blackbox-exporter.conf" {
+      preferLocalBuild = true;
+      buildInputs = [ pkgs.buildPackages.prometheus-blackbox-exporter ];
+    } ''
+      ln -s ${coerceConfigFile file} $out
+      blackbox_exporter --config.check --config.file $out
+    '';
+in {
   port = 9115;
   extraOpts = {
     configFile = mkOption {
@@ -21,14 +39,29 @@ in
         Path to configuration file.
       '';
     };
+    enableConfigCheck = mkOption {
+      type = types.bool;
+      default = true;
+      description = ''
+        Whether to run a correctness check for the configuration file. This depends
+        on the configuration file residing in the nix-store. Paths passed as string will
+        be copied to the store.
+      '';
+    };
   };
-  serviceOpts = {
+
+  serviceOpts = let
+    adjustedConfigFile = if cfg.enableConfigCheck then
+      checkConfig cfg.configFile
+    else
+      checkConfigLocation cfg.configFile;
+  in {
     serviceConfig = {
       AmbientCapabilities = [ "CAP_NET_RAW" ]; # for ping probes
       ExecStart = ''
         ${pkgs.prometheus-blackbox-exporter}/bin/blackbox_exporter \
           --web.listen-address ${cfg.listenAddress}:${toString cfg.port} \
-          --config.file ${checkConfig cfg.configFile} \
+          --config.file ${adjustedConfigFile} \
           ${concatStringsSep " \\\n  " cfg.extraFlags}
       '';
       ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
diff --git a/nixos/modules/services/networking/networkmanager.nix b/nixos/modules/services/networking/networkmanager.nix
index 3f1d0727d9bc..05a78d1c4486 100644
--- a/nixos/modules/services/networking/networkmanager.nix
+++ b/nixos/modules/services/networking/networkmanager.nix
@@ -24,9 +24,6 @@ let
 
   enableIwd = cfg.wifi.backend == "iwd";
 
-  # /var/lib/misc is for dnsmasq.leases.
-  stateDirs = "/var/lib/NetworkManager /var/lib/dhclient /var/lib/misc";
-
   configFile = pkgs.writeText "NetworkManager.conf" ''
     [main]
     plugins=keyfile
@@ -472,17 +469,25 @@ in {
 
     systemd.packages = cfg.packages;
 
+    systemd.tmpfiles.rules = [
+      "d /etc/NetworkManager/system-connections 0700 root root -"
+      "d /etc/ipsec.d 0700 root root -"
+      "d /var/lib/NetworkManager-fortisslvpn 0700 root root -"
+
+      "d /var/lib/dhclient 0755 root root -"
+      "d /var/lib/misc 0755 root root -" # for dnsmasq.leases
+    ];
+
     systemd.services.NetworkManager = {
       wantedBy = [ "network.target" ];
       restartTriggers = [ configFile ];
 
-      preStart = ''
-        mkdir -m 700 -p /etc/NetworkManager/system-connections
-        mkdir -m 700 -p /etc/ipsec.d
-        mkdir -m 755 -p ${stateDirs}
-      '';
-
       aliases = [ "dbus-org.freedesktop.NetworkManager.service" ];
+
+      serviceConfig = {
+        StateDirectory = "NetworkManager";
+        StateDirectoryMode = 755; # not sure if this really needs to be 755
+      };
     };
 
     systemd.services.NetworkManager-wait-online = {
diff --git a/nixos/modules/services/networking/pppd.nix b/nixos/modules/services/networking/pppd.nix
new file mode 100644
index 000000000000..e96c27bd84b4
--- /dev/null
+++ b/nixos/modules/services/networking/pppd.nix
@@ -0,0 +1,134 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.pppd;
+in
+{
+  meta = {
+    maintainers = with maintainers; [ danderson ];
+  };
+
+  options = {
+    services.pppd = {
+      enable = mkEnableOption "pppd";
+
+      package = mkOption {
+        default = pkgs.ppp;
+        defaultText = "pkgs.ppp";
+        type = types.package;
+        description = "pppd package to use.";
+      };
+
+      peers = mkOption {
+        default = {};
+        description = "pppd peers.";
+        type = types.attrsOf (types.submodule (
+          { name, ... }:
+          {
+            options = {
+              name = mkOption {
+                type = types.str;
+                default = name;
+                example = "dialup";
+                description = "Name of the PPP peer.";
+              };
+
+              enable = mkOption {
+                type = types.bool;
+                default = true;
+                example = false;
+                description = "Whether to enable this PPP peer.";
+              };
+
+              autostart = mkOption {
+                type = types.bool;
+                default = true;
+                example = false;
+                description = "Whether the PPP session is automatically started at boot time.";
+              };
+
+              config = mkOption {
+                type = types.lines;
+                default = "";
+                description = "pppd configuration for this peer, see the pppd(8) man page.";
+              };
+            };
+          }));
+      };
+    };
+  };
+
+  config = let
+    enabledConfigs = filter (f: f.enable) (attrValues cfg.peers);
+
+    mkEtc = peerCfg: {
+      "ppp/peers/${peerCfg.name}".text = peerCfg.config;
+    };
+
+    mkSystemd = peerCfg: {
+      "pppd-${peerCfg.name}" = {
+        restartTriggers = [ config.environment.etc."ppp/peers/${peerCfg.name}".source ];
+        before = [ "network.target" ];
+        wants = [ "network.target" ];
+        after = [ "network-pre.target" ];
+        environment = {
+          # pppd likes to write directly into /var/run. This is rude
+          # on a modern system, so we use libredirect to transparently
+          # move those files into /run/pppd.
+          LD_PRELOAD = "${pkgs.libredirect}/lib/libredirect.so";
+          NIX_REDIRECTS = "/var/run=/run/pppd";
+        };
+        serviceConfig = {
+          ExecStart = "${getBin cfg.package}/sbin/pppd call ${peerCfg.name} nodetach nolog";
+          Restart = "always";
+          RestartSec = 5;
+
+          AmbientCapabilities = "CAP_SYS_TTY_CONFIG CAP_NET_ADMIN CAP_NET_RAW CAP_SYS_ADMIN";
+          CapabilityBoundingSet = "CAP_SYS_TTY_CONFIG CAP_NET_ADMIN CAP_NET_RAW CAP_SYS_ADMIN";
+          KeyringMode = "private";
+          LockPersonality = true;
+          MemoryDenyWriteExecute = true;
+          NoNewPrivileges = true;
+          PrivateMounts = true;
+          PrivateTmp = true;
+          ProtectControlGroups = true;
+          ProtectHome = true;
+          ProtectHostname = true;
+          ProtectKernelModules = true;
+          # pppd can be configured to tweak kernel settings.
+          ProtectKernelTunables = false;
+          ProtectSystem = "strict";
+          RemoveIPC = true;
+          RestrictAddressFamilies = "AF_PACKET AF_UNIX AF_PPPOX AF_ATMPVC AF_ATMSVC AF_INET AF_INET6 AF_IPX";
+          RestrictNamespaces = true;
+          RestrictRealtime = true;
+          RestrictSUIDSGID = true;
+          SecureBits = "no-setuid-fixup-locked noroot-locked";
+          SystemCallFilter = "@system-service";
+          SystemCallArchitectures = "native";
+
+          # All pppd instances on a system must share a runtime
+          # directory in order for PPP multilink to work correctly. So
+          # we give all instances the same /run/pppd directory to store
+          # things in.
+          #
+          # For the same reason, we can't set PrivateUsers=true, because
+          # all instances need to run as the same user to access the
+          # multilink database.
+          RuntimeDirectory = "pppd";
+          RuntimeDirectoryPreserve = true;
+        };
+        wantedBy = mkIf peerCfg.autostart [ "multi-user.target" ];
+      };
+    };
+
+    etcFiles = map mkEtc enabledConfigs;
+    systemdConfigs = map mkSystemd enabledConfigs;
+
+  in mkIf cfg.enable {
+    environment.etc = mkMerge etcFiles;
+    systemd.services = mkMerge systemdConfigs;
+  };
+}
diff --git a/nixos/modules/services/x11/desktop-managers/gnome3.nix b/nixos/modules/services/x11/desktop-managers/gnome3.nix
index 20385c884b5e..9a402c74c052 100644
--- a/nixos/modules/services/x11/desktop-managers/gnome3.nix
+++ b/nixos/modules/services/x11/desktop-managers/gnome3.nix
@@ -30,6 +30,10 @@ let
 
      cp -f ${pkgs.gnome3.gnome-shell}/share/gsettings-schemas/*/glib-2.0/schemas/*.gschema.override $out/share/gsettings-schemas/nixos-gsettings-overrides/glib-2.0/schemas
 
+     ${optionalString flashbackEnabled ''
+       cp -f ${pkgs.gnome3.gnome-flashback}/share/gsettings-schemas/*/glib-2.0/schemas/*.gschema.override $out/share/gsettings-schemas/nixos-gsettings-overrides/glib-2.0/schemas
+     ''}
+
      chmod -R a+w $out/share/gsettings-schemas/nixos-gsettings-overrides
      cat - > $out/share/gsettings-schemas/nixos-gsettings-overrides/glib-2.0/schemas/nixos-defaults.gschema.override <<- EOF
        [org.gnome.desktop.background]
@@ -176,6 +180,10 @@ in
               wmCommand = "${pkgs.gnome3.metacity}/bin/metacity";
             } ++ cfg.flashback.customSessions);
 
+      systemd.packages = with pkgs.gnome3; [
+        gnome-flashback
+      ];
+
       security.pam.services.gnome-screensaver = {
         enableGnomeKeyring = true;
       };
diff --git a/nixos/modules/tasks/filesystems/zfs.nix b/nixos/modules/tasks/filesystems/zfs.nix
index cfdc0a31020b..fe11917c609c 100644
--- a/nixos/modules/tasks/filesystems/zfs.nix
+++ b/nixos/modules/tasks/filesystems/zfs.nix
@@ -16,9 +16,7 @@ let
   inInitrd = any (fs: fs == "zfs") config.boot.initrd.supportedFilesystems;
   inSystem = any (fs: fs == "zfs") config.boot.supportedFilesystems;
 
-  enableAutoSnapshots = cfgSnapshots.enable;
-  enableAutoScrub = cfgScrub.enable;
-  enableZfs = inInitrd || inSystem || enableAutoSnapshots || enableAutoScrub;
+  enableZfs = inInitrd || inSystem;
 
   kernel = config.boot.kernelPackages;
 
@@ -392,10 +390,11 @@ in
       };
 
       environment.etc."zfs/zed.d".source = "${packages.zfsUser}/etc/zfs/zed.d/";
+      environment.etc."zfs/zpool.d".source = "${packages.zfsUser}/etc/zfs/zpool.d/";
 
       system.fsPackages = [ packages.zfsUser ]; # XXX: needed? zfs doesn't have (need) a fsck
       environment.systemPackages = [ packages.zfsUser ]
-        ++ optional enableAutoSnapshots autosnapPkg; # so the user can run the command to see flags
+        ++ optional cfgSnapshots.enable autosnapPkg; # so the user can run the command to see flags
 
       services.udev.packages = [ packages.zfsUser ]; # to hook zvol naming, etc.
       systemd.packages = [ packages.zfsUser ];
@@ -487,7 +486,7 @@ in
       systemd.targets.zfs.wantedBy = [ "multi-user.target" ];
     })
 
-    (mkIf enableAutoSnapshots {
+    (mkIf (enableZfs && cfgSnapshots.enable) {
       systemd.services = let
                            descr = name: if name == "frequent" then "15 mins"
                                     else if name == "hourly" then "hour"
@@ -525,7 +524,7 @@ in
                             }) snapshotNames);
     })
 
-    (mkIf enableAutoScrub {
+    (mkIf (enableZfs && cfgScrub.enable) {
       systemd.services.zfs-scrub = {
         description = "ZFS pools scrubbing";
         after = [ "zfs-import.target" ];
@@ -552,15 +551,13 @@ in
       };
     })
 
-    (mkIf cfgTrim.enable {
+    (mkIf (enableZfs && cfgTrim.enable) {
       systemd.services.zpool-trim = {
         description = "ZFS pools trim";
         after = [ "zfs-import.target" ];
         path = [ packages.zfsUser ];
         startAt = cfgTrim.interval;
-        script = ''
-          zpool list -H -o name | xargs -n1 zpool trim
-        '';
+        serviceConfig.ExecStart = "${pkgs.runtimeShell} -c 'zpool list -H -o name | xargs --no-run-if-empty -n1 zpool trim'";
       };
     })
   ];
diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix
index 914b32f97c3a..e94c9712cbfa 100644
--- a/nixos/tests/all-tests.nix
+++ b/nixos/tests/all-tests.nix
@@ -227,6 +227,7 @@ in
   postgresql = handleTest ./postgresql.nix {};
   postgresql-wal-receiver = handleTest ./postgresql-wal-receiver.nix {};
   powerdns = handleTest ./powerdns.nix {};
+  pppd = handleTest ./pppd.nix {};
   predictable-interface-names = handleTest ./predictable-interface-names.nix {};
   printing = handleTest ./printing.nix {};
   prometheus = handleTest ./prometheus.nix {};
diff --git a/nixos/tests/pppd.nix b/nixos/tests/pppd.nix
new file mode 100644
index 000000000000..91f811859093
--- /dev/null
+++ b/nixos/tests/pppd.nix
@@ -0,0 +1,62 @@
+import ./make-test.nix (
+  let
+    chap-secrets = {
+      text = ''"flynn" * "reindeerflotilla" *'';
+      mode = "0640";
+    };
+  in {
+    nodes = {
+      server = {config, pkgs, ...}: {
+        config = {
+          # Run a PPPoE access concentrator server. It will spawn an
+          # appropriate PPP server process when a PPPoE client sets up a
+          # PPPoE session.
+          systemd.services.pppoe-server = {
+            restartTriggers = [
+              config.environment.etc."ppp/pppoe-server-options".source
+              config.environment.etc."ppp/chap-secrets".source
+            ];
+            after = ["network.target"];
+            serviceConfig = {
+              ExecStart = "${pkgs.rpPPPoE}/sbin/pppoe-server -F -O /etc/ppp/pppoe-server-options -q ${pkgs.ppp}/sbin/pppd -I eth1 -L 192.0.2.1 -R 192.0.2.2";
+            };
+            wantedBy = ["multi-user.target"];
+          };
+          environment.etc = {
+            "ppp/pppoe-server-options".text = ''
+              lcp-echo-interval 10
+              lcp-echo-failure 2
+              plugin rp-pppoe.so
+              require-chap
+              nobsdcomp
+              noccp
+              novj
+            '';
+            "ppp/chap-secrets" = chap-secrets;
+          };
+        };
+      };
+      client = {config, pkgs, ...}: {
+        services.pppd = {
+          enable = true;
+          peers.test = {
+            config = ''
+              plugin rp-pppoe.so eth1
+              name "flynn"
+              noipdefault
+              persist
+              noauth
+              debug
+            '';
+          };
+        };
+        environment.etc."ppp/chap-secrets" = chap-secrets;
+      };
+    };
+  
+    testScript = ''
+      startAll;
+      $client->waitUntilSucceeds("ping -c1 -W1 192.0.2.1");
+      $server->waitUntilSucceeds("ping -c1 -W1 192.0.2.2");
+    '';
+  })