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/from_md/release-notes/rl-2009.section.xml10
-rw-r--r--nixos/doc/manual/from_md/release-notes/rl-2111.section.xml22
-rw-r--r--nixos/doc/manual/man-nixos-rebuild.xml16
-rwxr-xr-xnixos/doc/manual/md-to-db.sh10
-rw-r--r--nixos/doc/manual/release-notes/rl-2009.section.md4
-rw-r--r--nixos/doc/manual/release-notes/rl-2111.section.md6
-rw-r--r--nixos/modules/config/update-users-groups.pl43
-rw-r--r--nixos/modules/config/users-groups.nix6
-rw-r--r--nixos/modules/services/audio/spotifyd.nix27
-rw-r--r--nixos/modules/services/backup/sanoid.nix9
-rw-r--r--nixos/modules/services/backup/znapzend.nix2
-rw-r--r--nixos/modules/services/monitoring/grafana.nix27
-rw-r--r--nixos/modules/services/monitoring/nagios.nix2
-rw-r--r--nixos/modules/services/network-filesystems/ipfs.nix20
-rw-r--r--nixos/modules/services/networking/firefox/sync-server.nix2
-rw-r--r--nixos/modules/system/activation/activation-script.nix100
-rw-r--r--nixos/modules/system/activation/switch-to-configuration.pl6
-rw-r--r--nixos/modules/system/activation/top-level.nix9
-rw-r--r--nixos/modules/tasks/lvm.nix26
-rw-r--r--nixos/tests/miniflux.nix2
-rw-r--r--nixos/tests/mutable-users.nix28
21 files changed, 289 insertions, 88 deletions
diff --git a/nixos/doc/manual/from_md/release-notes/rl-2009.section.xml b/nixos/doc/manual/from_md/release-notes/rl-2009.section.xml
index c74d850b2c62..edebd92b327a 100644
--- a/nixos/doc/manual/from_md/release-notes/rl-2009.section.xml
+++ b/nixos/doc/manual/from_md/release-notes/rl-2009.section.xml
@@ -1684,13 +1684,17 @@ CREATE ROLE postgres LOGIN SUPERUSER;
       </listitem>
       <listitem>
         <para>
-          The notmuch package move its emacs-related binaries and emacs
+          The notmuch package moves its emacs-related binaries and emacs
           lisp files to a separate output. They're not part of the
           default <literal>out</literal> output anymore - if you relied
           on the <literal>notmuch-emacs-mua</literal> binary or the
           emacs lisp files, access them via the
-          <literal>notmuch.emacs</literal> output. Device tree overlay
-          support was improved in
+          <literal>notmuch.emacs</literal> output.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          Device tree overlay support was improved in
           <link xlink:href="https://github.com/NixOS/nixpkgs/pull/79370">#79370</link>
           and now uses
           <link xlink:href="options.html#opt-hardware.deviceTree.kernelPackage">hardware.deviceTree.kernelPackage</link>
diff --git a/nixos/doc/manual/from_md/release-notes/rl-2111.section.xml b/nixos/doc/manual/from_md/release-notes/rl-2111.section.xml
index 1b0371a0179a..5554927b8b2a 100644
--- a/nixos/doc/manual/from_md/release-notes/rl-2111.section.xml
+++ b/nixos/doc/manual/from_md/release-notes/rl-2111.section.xml
@@ -37,6 +37,17 @@
           PostgreSQL now defaults to major version 13.
         </para>
       </listitem>
+      <listitem>
+        <para>
+          Activation scripts can now opt int to be run when running
+          <literal>nixos-rebuild dry-activate</literal> and detect the
+          dry activation by reading <literal>$NIXOS_ACTION</literal>.
+          This allows activation scripts to output what they would
+          change if the activation was really run. The users/modules
+          activation script supports this and outputs some of is
+          actions.
+        </para>
+      </listitem>
     </itemizedlist>
   </section>
   <section xml:id="sec-release-21.11-new-services">
@@ -1121,6 +1132,17 @@ Superuser created successfully.
           rofi’s changelog</link>.
         </para>
       </listitem>
+      <listitem>
+        <para>
+          ipfs now defaults to not listening on you local network. This
+          setting was change as server providers won’t accept port
+          scanning on their private network. If you have several ipfs
+          instances running on a network you own, feel free to change
+          the setting <literal>ipfs.localDiscovery = true;</literal>.
+          localDiscovery enables different instances to discover each
+          other and share data.
+        </para>
+      </listitem>
     </itemizedlist>
   </section>
 </section>
diff --git a/nixos/doc/manual/man-nixos-rebuild.xml b/nixos/doc/manual/man-nixos-rebuild.xml
index 8c34ea7458e6..0e0ea5d74b0b 100644
--- a/nixos/doc/manual/man-nixos-rebuild.xml
+++ b/nixos/doc/manual/man-nixos-rebuild.xml
@@ -553,6 +553,22 @@
 
    <varlistentry>
     <term>
+     <option>--use-substitutes</option>
+    </term>
+    <listitem>
+     <para>
+       When set, nixos-rebuild will add <option>--use-substitutes</option>
+       to each invocation of nix-copy-closure. This will only affect the
+       behavior of nixos-rebuild if <option>--target-host</option> or
+       <option>--build-host</option> is also set. This is useful when
+       the target-host connection to cache.nixos.org is faster than the
+       connection between hosts.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term>
      <option>--use-remote-sudo</option>
     </term>
     <listitem>
diff --git a/nixos/doc/manual/md-to-db.sh b/nixos/doc/manual/md-to-db.sh
index c7a328b2295d..6dd4b8c6e419 100755
--- a/nixos/doc/manual/md-to-db.sh
+++ b/nixos/doc/manual/md-to-db.sh
@@ -12,8 +12,14 @@ pushd $DIR
 # TODO: Remove raw-attribute when we can get rid of DocBook altogether.
 pandoc_commonmark_enabled_extensions=+attributes+fenced_divs+footnotes+bracketed_spans+definition_lists+pipe_tables+raw_attribute
 pandoc_flags=(
-  # media extraction and diagram-generator.lua not needed
-  "--lua-filter=$DIR/../../../doc/labelless-link-is-xref.lua"
+  # Not needed:
+  # - diagram-generator.lua (we do not support that in NixOS manual to limit dependencies)
+  # - media extraction (was only required for diagram generator)
+  # - docbook-reader/citerefentry-to-rst-role.lua (only relevant for DocBook → MarkDown/rST/MyST)
+  "--lua-filter=$DIR/../../../doc/build-aux/pandoc-filters/myst-reader/roles.lua"
+  "--lua-filter=$DIR/../../../doc/build-aux/pandoc-filters/link-unix-man-references.lua"
+  "--lua-filter=$DIR/../../../doc/build-aux/pandoc-filters/docbook-writer/rst-roles.lua"
+  "--lua-filter=$DIR/../../../doc/build-aux/pandoc-filters/docbook-writer/labelless-link-is-xref.lua"
   -f "commonmark${pandoc_commonmark_enabled_extensions}+smart"
   -t docbook
 )
diff --git a/nixos/doc/manual/release-notes/rl-2009.section.md b/nixos/doc/manual/release-notes/rl-2009.section.md
index 48059ab07f5e..79be2a56a54e 100644
--- a/nixos/doc/manual/release-notes/rl-2009.section.md
+++ b/nixos/doc/manual/release-notes/rl-2009.section.md
@@ -578,7 +578,9 @@ When upgrading from a previous release, please be aware of the following incompa
 
 - `services.journald.rateLimitBurst` was updated from `1000` to `10000` to follow the new upstream systemd default.
 
-- The notmuch package move its emacs-related binaries and emacs lisp files to a separate output. They\'re not part of the default `out` output anymore - if you relied on the `notmuch-emacs-mua` binary or the emacs lisp files, access them via the `notmuch.emacs` output. Device tree overlay support was improved in [\#79370](https://github.com/NixOS/nixpkgs/pull/79370) and now uses [hardware.deviceTree.kernelPackage](options.html#opt-hardware.deviceTree.kernelPackage) instead of `hardware.deviceTree.base`. [hardware.deviceTree.overlays](options.html#opt-hardware.deviceTree.overlays) configuration was extended to support `.dts` files with symbols. Device trees can now be filtered by setting [hardware.deviceTree.filter](options.html#opt-hardware.deviceTree.filter) option.
+- The notmuch package moves its emacs-related binaries and emacs lisp files to a separate output. They\'re not part of the default `out` output anymore - if you relied on the `notmuch-emacs-mua` binary or the emacs lisp files, access them via the `notmuch.emacs` output.
+
+- Device tree overlay support was improved in [\#79370](https://github.com/NixOS/nixpkgs/pull/79370) and now uses [hardware.deviceTree.kernelPackage](options.html#opt-hardware.deviceTree.kernelPackage) instead of `hardware.deviceTree.base`. [hardware.deviceTree.overlays](options.html#opt-hardware.deviceTree.overlays) configuration was extended to support `.dts` files with symbols. Device trees can now be filtered by setting [hardware.deviceTree.filter](options.html#opt-hardware.deviceTree.filter) option.
 
 - The default output of `buildGoPackage` is now `$out` instead of `$bin`.
 
diff --git a/nixos/doc/manual/release-notes/rl-2111.section.md b/nixos/doc/manual/release-notes/rl-2111.section.md
index 3df77d21d827..00844d529b77 100644
--- a/nixos/doc/manual/release-notes/rl-2111.section.md
+++ b/nixos/doc/manual/release-notes/rl-2111.section.md
@@ -14,6 +14,10 @@ In addition to numerous new and upgraded packages, this release has the followin
 
 - PostgreSQL now defaults to major version 13.
 
+- Activation scripts can now opt int to be run when running `nixos-rebuild dry-activate` and detect the dry activation by reading `$NIXOS_ACTION`.
+  This allows activation scripts to output what they would change if the activation was really run.
+  The users/modules activation script supports this and outputs some of is actions.
+
 ## New Services {#sec-release-21.11-new-services}
 
 - [btrbk](https://digint.ch/btrbk/index.html), a backup tool for btrfs subvolumes, taking advantage of btrfs specific capabilities to create atomic snapshots and transfer them incrementally to your backup locations. Available as [services.btrbk](options.html#opt-services.brtbk.instances).
@@ -320,3 +324,5 @@ To be able to access the web UI this port needs to be opened in the firewall.
 - GNOME desktop environment now enables `QGnomePlatform` as the Qt platform theme, which should avoid crashes when opening file chooser dialogs in Qt apps by using XDG desktop portal. Additionally, it will make the apps fit better visually.
 
 - `rofi` has been updated from '1.6.1' to '1.7.0', one important thing is the removal of the old xresources based configuration setup. Read more [in rofi's changelog](https://github.com/davatorium/rofi/blob/cb12e6fc058f4a0f4f/Changelog#L1).
+
+- ipfs now defaults to not listening on you local network. This setting was change as server providers won't accept port scanning on their private network. If you have several ipfs instances running on a network you own, feel free to change the setting `ipfs.localDiscovery = true;`. localDiscovery enables different instances to discover each other and share data.
diff --git a/nixos/modules/config/update-users-groups.pl b/nixos/modules/config/update-users-groups.pl
index bef08dc40207..232f886789d3 100644
--- a/nixos/modules/config/update-users-groups.pl
+++ b/nixos/modules/config/update-users-groups.pl
@@ -1,11 +1,10 @@
 use strict;
+use warnings;
 use File::Path qw(make_path);
 use File::Slurp;
+use Getopt::Long;
 use JSON;
 
-make_path("/var/lib/nixos", { mode => 0755 });
-
-
 # Keep track of deleted uids and gids.
 my $uidMapFile = "/var/lib/nixos/uid-map";
 my $uidMap = -e $uidMapFile ? decode_json(read_file($uidMapFile)) : {};
@@ -13,12 +12,19 @@ my $uidMap = -e $uidMapFile ? decode_json(read_file($uidMapFile)) : {};
 my $gidMapFile = "/var/lib/nixos/gid-map";
 my $gidMap = -e $gidMapFile ? decode_json(read_file($gidMapFile)) : {};
 
+my $is_dry = ($ENV{'NIXOS_ACTION'} // "") eq "dry-activate";
+GetOptions("dry-activate" => \$is_dry);
+make_path("/var/lib/nixos", { mode => 0755 }) unless $is_dry;
 
 sub updateFile {
     my ($path, $contents, $perms) = @_;
+    return if $is_dry;
     write_file($path, { atomic => 1, binmode => ':utf8', perms => $perms // 0644 }, $contents) or die;
 }
 
+sub nscdInvalidate {
+    system("nscd", "--invalidate", $_[0]) unless $is_dry;
+}
 
 sub hashPassword {
     my ($password) = @_;
@@ -28,6 +34,14 @@ sub hashPassword {
     return crypt($password, '$6$' . $salt . '$');
 }
 
+sub dry_print {
+    if ($is_dry) {
+        print STDERR ("$_[1] $_[2]\n")
+    } else {
+        print STDERR ("$_[0] $_[2]\n")
+    }
+}
+
 
 # Functions for allocating free GIDs/UIDs. FIXME: respect ID ranges in
 # /etc/login.defs.
@@ -51,7 +65,7 @@ sub allocGid {
     my ($name) = @_;
     my $prevGid = $gidMap->{$name};
     if (defined $prevGid && !defined $gidsUsed{$prevGid}) {
-        print STDERR "reviving group '$name' with GID $prevGid\n";
+        dry_print("reviving", "would revive", "group '$name' with GID $prevGid");
         $gidsUsed{$prevGid} = 1;
         return $prevGid;
     }
@@ -63,15 +77,14 @@ sub allocUid {
     my ($min, $max, $up) = $isSystemUser ? (400, 999, 0) : (1000, 29999, 1);
     my $prevUid = $uidMap->{$name};
     if (defined $prevUid && $prevUid >= $min && $prevUid <= $max && !defined $uidsUsed{$prevUid}) {
-        print STDERR "reviving user '$name' with UID $prevUid\n";
+        dry_print("reviving", "would revive", "user '$name' with UID $prevUid");
         $uidsUsed{$prevUid} = 1;
         return $prevUid;
     }
     return allocId(\%uidsUsed, \%uidsPrevUsed, $min, $max, $up, sub { my ($uid) = @_; getpwuid($uid) });
 }
 
-
-# Read the declared users/groups.
+# Read the declared users/groups
 my $spec = decode_json(read_file($ARGV[0]));
 
 # Don't allocate UIDs/GIDs that are manually assigned.
@@ -134,7 +147,7 @@ foreach my $g (@{$spec->{groups}}) {
     if (defined $existing) {
         $g->{gid} = $existing->{gid} if !defined $g->{gid};
         if ($g->{gid} != $existing->{gid}) {
-            warn "warning: not applying GID change of group ‘$name’ ($existing->{gid} -> $g->{gid})\n";
+            dry_print("warning: not applying", "warning: would not apply", "GID change of group ‘$name’ ($existing->{gid} -> $g->{gid})");
             $g->{gid} = $existing->{gid};
         }
         $g->{password} = $existing->{password}; # do we want this?
@@ -163,7 +176,7 @@ foreach my $name (keys %groupsCur) {
     my $g = $groupsCur{$name};
     next if defined $groupsOut{$name};
     if (!$spec->{mutableUsers} || defined $declGroups{$name}) {
-        print STDERR "removing group ‘$name’\n";
+        dry_print("removing group", "would remove group", "‘$name’");
     } else {
         $groupsOut{$name} = $g;
     }
@@ -175,7 +188,7 @@ my @lines = map { join(":", $_->{name}, $_->{password}, $_->{gid}, $_->{members}
     (sort { $a->{gid} <=> $b->{gid} } values(%groupsOut));
 updateFile($gidMapFile, to_json($gidMap));
 updateFile("/etc/group", \@lines);
-system("nscd --invalidate group");
+nscdInvalidate("group");
 
 # Generate a new /etc/passwd containing the declared users.
 my %usersOut;
@@ -196,7 +209,7 @@ foreach my $u (@{$spec->{users}}) {
     if (defined $existing) {
         $u->{uid} = $existing->{uid} if !defined $u->{uid};
         if ($u->{uid} != $existing->{uid}) {
-            warn "warning: not applying UID change of user ‘$name’ ($existing->{uid} -> $u->{uid})\n";
+            dry_print("warning: not applying", "warning: would not apply", "UID change of user ‘$name’ ($existing->{uid} -> $u->{uid})");
             $u->{uid} = $existing->{uid};
         }
     } else {
@@ -211,7 +224,7 @@ foreach my $u (@{$spec->{users}}) {
 
     # Ensure home directory incl. ownership and permissions.
     if ($u->{createHome}) {
-        make_path($u->{home}, { mode => 0700 }) if ! -e $u->{home};
+        make_path($u->{home}, { mode => 0700 }) if ! -e $u->{home} and ! $is_dry;
         chown $u->{uid}, $u->{gid}, $u->{home};
         chmod 0700, $u->{home};
     }
@@ -250,7 +263,7 @@ foreach my $name (keys %usersCur) {
     my $u = $usersCur{$name};
     next if defined $usersOut{$name};
     if (!$spec->{mutableUsers} || defined $declUsers{$name}) {
-        print STDERR "removing user ‘$name’\n";
+        dry_print("removing user", "would remove user", "‘$name’");
     } else {
         $usersOut{$name} = $u;
     }
@@ -261,7 +274,7 @@ foreach my $name (keys %usersCur) {
     (sort { $a->{uid} <=> $b->{uid} } (values %usersOut));
 updateFile($uidMapFile, to_json($uidMap));
 updateFile("/etc/passwd", \@lines);
-system("nscd --invalidate passwd");
+nscdInvalidate("passwd");
 
 
 # Rewrite /etc/shadow to add new accounts or remove dead ones.
@@ -293,7 +306,7 @@ updateFile("/etc/shadow", \@shadowNew, 0640);
     my $uid = getpwnam "root";
     my $gid = getgrnam "shadow";
     my $path = "/etc/shadow";
-    chown($uid, $gid, $path) || die "Failed to change ownership of $path: $!";
+    (chown($uid, $gid, $path) || die "Failed to change ownership of $path: $!") unless $is_dry;
 }
 
 # Rewrite /etc/subuid & /etc/subgid to include default container mappings
diff --git a/nixos/modules/config/users-groups.nix b/nixos/modules/config/users-groups.nix
index f86be3be2c65..d88162558e66 100644
--- a/nixos/modules/config/users-groups.nix
+++ b/nixos/modules/config/users-groups.nix
@@ -561,14 +561,16 @@ in {
       shadow.gid = ids.gids.shadow;
     };
 
-    system.activationScripts.users = stringAfter [ "stdio" ]
-      ''
+    system.activationScripts.users = {
+      supportsDryActivation = true;
+      text = ''
         install -m 0700 -d /root
         install -m 0755 -d /home
 
         ${pkgs.perl.withPackages (p: [ p.FileSlurp p.JSON ])}/bin/perl \
         -w ${./update-users-groups.pl} ${spec}
       '';
+    };
 
     # for backwards compatibility
     system.activationScripts.groups = stringAfter [ "users" ] "";
diff --git a/nixos/modules/services/audio/spotifyd.nix b/nixos/modules/services/audio/spotifyd.nix
index 9279a03aed4e..22848ed98000 100644
--- a/nixos/modules/services/audio/spotifyd.nix
+++ b/nixos/modules/services/audio/spotifyd.nix
@@ -4,7 +4,15 @@ with lib;
 
 let
   cfg = config.services.spotifyd;
-  spotifydConf = pkgs.writeText "spotifyd.conf" cfg.config;
+  toml = pkgs.formats.toml {};
+  warnConfig =
+    if cfg.config != ""
+    then lib.trace "Using the stringly typed .config attribute is discouraged. Use the TOML typed .settings attribute instead."
+    else id;
+  spotifydConf =
+    if cfg.settings != {}
+    then toml.generate "spotify.conf" cfg.settings
+    else warnConfig (pkgs.writeText "spotifyd.conf" cfg.config);
 in
 {
   options = {
@@ -15,6 +23,16 @@ in
         default = "";
         type = types.lines;
         description = ''
+          (Deprecated) Configuration for Spotifyd. For syntax and directives, see
+          <link xlink:href="https://github.com/Spotifyd/spotifyd#Configuration"/>.
+        '';
+      };
+
+      settings = mkOption {
+        default = {};
+        type = toml.type;
+        example = { global.bitrate = 320; };
+        description = ''
           Configuration for Spotifyd. For syntax and directives, see
           <link xlink:href="https://github.com/Spotifyd/spotifyd#Configuration"/>.
         '';
@@ -23,6 +41,13 @@ in
   };
 
   config = mkIf cfg.enable {
+    assertions = [
+      {
+        assertion = cfg.config == "" || cfg.settings == {};
+        message = "At most one of the .config attribute and the .settings attribute may be set";
+      }
+    ];
+
     systemd.services.spotifyd = {
       wantedBy = [ "multi-user.target" ];
       after = [ "network-online.target" "sound.target" ];
diff --git a/nixos/modules/services/backup/sanoid.nix b/nixos/modules/services/backup/sanoid.nix
index 41d0e2e1df68..e70063415ec0 100644
--- a/nixos/modules/services/backup/sanoid.nix
+++ b/nixos/modules/services/backup/sanoid.nix
@@ -57,8 +57,13 @@ let
     useTemplate = use_template;
 
     recursive = mkOption {
-      description = "Whether to recursively snapshot dataset children.";
-      type = types.bool;
+      description = ''
+        Whether to recursively snapshot dataset children.
+        You can also set this to <literal>"zfs"</literal> to handle datasets
+        recursively in an atomic way without the possibility to
+        override settings for child datasets.
+      '';
+      type = with types; oneOf [ bool (enum [ "zfs" ]) ];
       default = false;
     };
 
diff --git a/nixos/modules/services/backup/znapzend.nix b/nixos/modules/services/backup/znapzend.nix
index debb2a397050..1fccc7cd6076 100644
--- a/nixos/modules/services/backup/znapzend.nix
+++ b/nixos/modules/services/backup/znapzend.nix
@@ -324,7 +324,7 @@ in
       autoCreation = mkOption {
         type = bool;
         default = false;
-        description = "Automatically create the destination dataset if it does not exists.";
+        description = "Automatically create the destination dataset if it does not exist.";
       };
 
       zetup = mkOption {
diff --git a/nixos/modules/services/monitoring/grafana.nix b/nixos/modules/services/monitoring/grafana.nix
index fb67bbfb8420..d46e38e82af1 100644
--- a/nixos/modules/services/monitoring/grafana.nix
+++ b/nixos/modules/services/monitoring/grafana.nix
@@ -675,6 +675,33 @@ in {
         User = "grafana";
         RuntimeDirectory = "grafana";
         RuntimeDirectoryMode = "0755";
+        # Hardening
+        CapabilityBoundingSet = [ "" ];
+        DeviceAllow = [ "" ];
+        LockPersonality = true;
+        MemoryDenyWriteExecute = true;
+        NoNewPrivileges = true;
+        PrivateDevices = true;
+        PrivateTmp = true;
+        PrivateUsers = true;
+        ProcSubset = "pid";
+        ProtectClock = true;
+        ProtectControlGroups = true;
+        ProtectHome = true;
+        ProtectHostname = true;
+        ProtectKernelLogs = true;
+        ProtectKernelModules = true;
+        ProtectKernelTunables = true;
+        ProtectProc = "invisible";
+        ProtectSystem = "full";
+        RemoveIPC = true;
+        RestrictAddressFamilies = [ "AF_INET" "AF_INET6" "AF_UNIX" ];
+        RestrictNamespaces = true;
+        RestrictRealtime = true;
+        RestrictSUIDSGID = true;
+        SystemCallArchitectures = "native";
+        SystemCallFilter = [ "@system-service" "~@privileged" "~@resources" ];
+        UMask = "0027";
       };
       preStart = ''
         ln -fs ${cfg.package}/share/grafana/conf ${cfg.dataDir}
diff --git a/nixos/modules/services/monitoring/nagios.nix b/nixos/modules/services/monitoring/nagios.nix
index 0afaefe04e18..280a9a001b5b 100644
--- a/nixos/modules/services/monitoring/nagios.nix
+++ b/nixos/modules/services/monitoring/nagios.nix
@@ -41,7 +41,7 @@ let
     validated =  pkgs.runCommand "nagios-checked.cfg" {preferLocalBuild=true;} ''
       cp ${file} nagios.cfg
       # nagios checks the existence of /var/lib/nagios, but
-      # it does not exists in the build sandbox, so we fake it
+      # it does not exist in the build sandbox, so we fake it
       mkdir lib
       lib=$(readlink -f lib)
       sed -i s@=${nagiosState}@=$lib@ nagios.cfg
diff --git a/nixos/modules/services/network-filesystems/ipfs.nix b/nixos/modules/services/network-filesystems/ipfs.nix
index 57f5f6b006c8..faa515835b67 100644
--- a/nixos/modules/services/network-filesystems/ipfs.nix
+++ b/nixos/modules/services/network-filesystems/ipfs.nix
@@ -12,6 +12,11 @@ let
     (optionalString (cfg.defaultMode == "norouting") "--routing=none")
   ] ++ cfg.extraFlags);
 
+  profile =
+    if cfg.localDiscovery
+    then "local-discovery"
+    else "server";
+
   splitMulitaddr = addrRaw: lib.tail (lib.splitString "/" addrRaw);
 
   multiaddrToListenStream = addrRaw:
@@ -173,7 +178,7 @@ in
         description = ''Whether to enable local discovery for the ipfs daemon.
           This will allow ipfs to scan ports on your local network. Some hosting services will ban you if you do this.
         '';
-        default = true;
+        default = false;
       };
 
       serviceFdlimit = mkOption {
@@ -236,14 +241,13 @@ in
       environment.IPFS_PATH = cfg.dataDir;
 
       preStart = ''
-        if [[ ! -f ${cfg.dataDir}/config ]]; then
-          ipfs init ${optionalString cfg.emptyRepo "-e"} \
-            ${optionalString (! cfg.localDiscovery) "--profile=server"}
+        if [[ ! -f "$IPFS_PATH/config" ]]; then
+          ipfs init ${optionalString cfg.emptyRepo "-e"} --profile=${profile}
         else
-          ${if cfg.localDiscovery
-            then "ipfs --offline config profile apply local-discovery"
-            else "ipfs --offline config profile apply server"
-          }
+          # After an unclean shutdown this file may exist which will cause the config command to attempt to talk to the daemon. This will hang forever if systemd is holding our sockets open.
+          rm -vf "$IPFS_PATH/api"
+
+          ipfs --offline config profile apply ${profile}
         fi
       '' + optionalString cfg.autoMount ''
         ipfs --offline config Mounts.FuseAllowOther --json true
diff --git a/nixos/modules/services/networking/firefox/sync-server.nix b/nixos/modules/services/networking/firefox/sync-server.nix
index 24f768649530..1ad573abfca3 100644
--- a/nixos/modules/services/networking/firefox/sync-server.nix
+++ b/nixos/modules/services/networking/firefox/sync-server.nix
@@ -119,7 +119,7 @@ in
           password, and the <option>syncserver.secret</option> setting is used by the server to
           generate cryptographically-signed authentication tokens.
 
-          If this file does not exists, then it is created with a generated
+          If this file does not exist, then it is created with a generated
           <option>syncserver.secret</option> settings.
        '';
       };
diff --git a/nixos/modules/system/activation/activation-script.nix b/nixos/modules/system/activation/activation-script.nix
index 3a6930314b1a..548b4de852b7 100644
--- a/nixos/modules/system/activation/activation-script.nix
+++ b/nixos/modules/system/activation/activation-script.nix
@@ -17,6 +17,41 @@ let
     '';
   });
 
+  systemActivationScript = set: onlyDry: let
+    set' = filterAttrs (_: v: onlyDry -> v.supportsDryActivation) (mapAttrs (_: v: if isString v then (noDepEntry v) // { supportsDryActivation = false; } else v) set);
+    withHeadlines = addAttributeName set';
+  in
+    ''
+      #!${pkgs.runtimeShell}
+
+      systemConfig='@out@'
+
+      export PATH=/empty
+      for i in ${toString path}; do
+          PATH=$PATH:$i/bin:$i/sbin
+      done
+
+      _status=0
+      trap "_status=1 _localstatus=\$?" ERR
+
+      # Ensure a consistent umask.
+      umask 0022
+
+      ${textClosureMap id (withHeadlines) (attrNames withHeadlines)}
+
+    '' + optionalString (!onlyDry) ''
+      # Make this configuration the current configuration.
+      # The readlink is there to ensure that when $systemConfig = /system
+      # (which is a symlink to the store), /run/current-system is still
+      # used as a garbage collection root.
+      ln -sfn "$(readlink -f "$systemConfig")" /run/current-system
+
+      # Prevent the current configuration from being garbage-collected.
+      ln -sfn /run/current-system /nix/var/nix/gcroots/current-system
+
+      exit $_status
+    '';
+
   path = with pkgs; map getBin
     [ coreutils
       gnugrep
@@ -28,7 +63,7 @@ let
       util-linux # needed for mount and mountpoint
     ];
 
-  scriptType = with types;
+  scriptType = withDry: with types;
     let scriptOptions =
       { deps = mkOption
           { type = types.listOf types.str;
@@ -39,6 +74,19 @@ let
           { type = types.lines;
             description = "The content of the script.";
           };
+      } // optionalAttrs withDry {
+        supportsDryActivation = mkOption
+          { type = types.bool;
+            default = false;
+            description = ''
+              Whether this activation script supports being dry-activated.
+              These activation scripts will also be executed on dry-activate
+              activations with the environment variable
+              <literal>NIXOS_ACTION</literal> being set to <literal>dry-activate
+              </literal>.  it's important that these activation scripts  don't
+              modify anything about the system when the variable is set.
+            '';
+          };
       };
     in either str (submodule { options = scriptOptions; });
 
@@ -74,47 +122,19 @@ in
         idempotent and fast.
       '';
 
-      type = types.attrsOf scriptType;
-
-      apply = set: {
-        script =
-          ''
-            #! ${pkgs.runtimeShell}
-
-            systemConfig=@out@
-
-            export PATH=/empty
-            for i in ${toString path}; do
-                PATH=$PATH:$i/bin:$i/sbin
-            done
-
-            _status=0
-            trap "_status=1 _localstatus=\$?" ERR
-
-            # Ensure a consistent umask.
-            umask 0022
-
-            ${
-              let
-                set' = mapAttrs (n: v: if isString v then noDepEntry v else v) set;
-                withHeadlines = addAttributeName set';
-              in textClosureMap id (withHeadlines) (attrNames withHeadlines)
-            }
-
-            # Make this configuration the current configuration.
-            # The readlink is there to ensure that when $systemConfig = /system
-            # (which is a symlink to the store), /run/current-system is still
-            # used as a garbage collection root.
-            ln -sfn "$(readlink -f "$systemConfig")" /run/current-system
-
-            # Prevent the current configuration from being garbage-collected.
-            ln -sfn /run/current-system /nix/var/nix/gcroots/current-system
-
-            exit $_status
-          '';
+      type = types.attrsOf (scriptType true);
+      apply = set: set // {
+        script = systemActivationScript set false;
       };
     };
 
+    system.dryActivationScript = mkOption {
+      description = "The shell script that is to be run when dry-activating a system.";
+      readOnly = true;
+      internal = true;
+      default = systemActivationScript (removeAttrs config.system.activationScripts [ "script" ]) true;
+    };
+
     system.userActivationScripts = mkOption {
       default = {};
 
@@ -137,7 +157,7 @@ in
         idempotent and fast.
       '';
 
-      type = with types; attrsOf scriptType;
+      type = with types; attrsOf (scriptType false);
 
       apply = set: {
         script = ''
diff --git a/nixos/modules/system/activation/switch-to-configuration.pl b/nixos/modules/system/activation/switch-to-configuration.pl
index dd391c8b5d78..b7a062755296 100644
--- a/nixos/modules/system/activation/switch-to-configuration.pl
+++ b/nixos/modules/system/activation/switch-to-configuration.pl
@@ -36,6 +36,8 @@ EOF
     exit 1;
 }
 
+$ENV{NIXOS_ACTION} = $action;
+
 # This is a NixOS installation if it has /etc/NIXOS or a proper
 # /etc/os-release.
 die "This is not a NixOS installation!\n" unless
@@ -360,6 +362,10 @@ if ($action eq "dry-activate") {
         if scalar @unitsToStopFiltered > 0;
     print STDERR "would NOT stop the following changed units: ", join(", ", sort(keys %unitsToSkip)), "\n"
         if scalar(keys %unitsToSkip) > 0;
+
+    print STDERR "would activate the configuration...\n";
+    system("$out/dry-activate", "$out");
+
     print STDERR "would restart systemd\n" if $restartSystemd;
     print STDERR "would restart the following units: ", join(", ", sort(keys %unitsToRestart)), "\n"
         if scalar(keys %unitsToRestart) > 0;
diff --git a/nixos/modules/system/activation/top-level.nix b/nixos/modules/system/activation/top-level.nix
index d3e4923a993f..616e1422aa8c 100644
--- a/nixos/modules/system/activation/top-level.nix
+++ b/nixos/modules/system/activation/top-level.nix
@@ -56,9 +56,13 @@ let
       ''}
 
       echo "$activationScript" > $out/activate
+      echo "$dryActivationScript" > $out/dry-activate
       substituteInPlace $out/activate --subst-var out
-      chmod u+x $out/activate
-      unset activationScript
+      substituteInPlace $out/dry-activate --subst-var out
+      chmod u+x $out/activate $out/dry-activate
+      unset activationScript dryActivationScript
+      ${pkgs.runtimeShell} -n $out/activate
+      ${pkgs.runtimeShell} -n $out/dry-activate
 
       cp ${config.system.build.bootStage2} $out/init
       substituteInPlace $out/init --subst-var-by systemConfig $out
@@ -108,6 +112,7 @@ let
       config.system.build.installBootLoader
       or "echo 'Warning: do not know how to make this configuration bootable; please enable a boot loader.' 1>&2; true";
     activationScript = config.system.activationScripts.script;
+    dryActivationScript = config.system.dryActivationScript;
     nixosLabel = config.system.nixos.label;
 
     configurationName = config.boot.loader.grub.configurationName;
diff --git a/nixos/modules/tasks/lvm.nix b/nixos/modules/tasks/lvm.nix
index 98a0e2ddef90..aaa76b49fa30 100644
--- a/nixos/modules/tasks/lvm.nix
+++ b/nixos/modules/tasks/lvm.nix
@@ -46,22 +46,32 @@ in {
         kernelModules = [ "dm-snapshot" "dm-thin-pool" ];
 
         extraUtilsCommands = ''
-          copy_bin_and_libs ${pkgs.thin-provisioning-tools}/bin/pdata_tools
-          copy_bin_and_libs ${pkgs.thin-provisioning-tools}/bin/thin_check
+          for BIN in ${pkgs.thin-provisioning-tools}/bin/*; do
+            copy_bin_and_libs $BIN
+          done
+        '';
+
+        extraUtilsCommandsTest = ''
+          ls ${pkgs.thin-provisioning-tools}/bin/ | grep -v pdata_tools | while read BIN; do
+            $out/bin/$(basename $BIN) --help > /dev/null
+          done
         '';
       };
 
-      environment.etc."lvm/lvm.conf".text = ''
-        global/thin_check_executable = "${pkgs.thin-provisioning-tools}/bin/thin_check"
-      '';
+      environment.etc."lvm/lvm.conf".text = concatMapStringsSep "\n"
+        (bin: "global/${bin}_executable = ${pkgs.thin-provisioning-tools}/bin/${bin}")
+        [ "thin_check" "thin_dump" "thin_repair" "cache_check" "cache_dump" "cache_repair" ];
     })
     (mkIf (cfg.dmeventd.enable || cfg.boot.thin.enable) {
       boot.initrd.preLVMCommands = ''
           mkdir -p /etc/lvm
           cat << EOF >> /etc/lvm/lvm.conf
-          ${optionalString cfg.boot.thin.enable ''
-            global/thin_check_executable = "$(command -v thin_check)"
-          ''}
+          ${optionalString cfg.boot.thin.enable (
+            concatMapStringsSep "\n"
+              (bin: "global/${bin}_executable = $(command -v ${bin})")
+              [ "thin_check" "thin_dump" "thin_repair" "cache_check" "cache_dump" "cache_repair" ]
+            )
+          }
           ${optionalString cfg.dmeventd.enable ''
             dmeventd/executable = "$(command -v false)"
             activation/monitoring = 0
diff --git a/nixos/tests/miniflux.nix b/nixos/tests/miniflux.nix
index 9a25a9e77cc9..1015550fa8c7 100644
--- a/nixos/tests/miniflux.nix
+++ b/nixos/tests/miniflux.nix
@@ -11,7 +11,7 @@ in
 with lib;
 {
   name = "miniflux";
-  meta.maintainers = with pkgs.lib.maintainers; [ bricewge ];
+  meta.maintainers = with pkgs.lib.maintainers; [ ];
 
   nodes = {
     default =
diff --git a/nixos/tests/mutable-users.nix b/nixos/tests/mutable-users.nix
index e3f002d9b198..ebe32e6487ef 100644
--- a/nixos/tests/mutable-users.nix
+++ b/nixos/tests/mutable-users.nix
@@ -12,6 +12,7 @@ import ./make-test-python.nix ({ pkgs, ...} : {
     };
     mutable = { ... }: {
       users.mutableUsers = true;
+      users.users.dry-test.isNormalUser = true;
     };
   };
 
@@ -41,5 +42,32 @@ import ./make-test-python.nix ({ pkgs, ...} : {
             "${mutableSystem}/bin/switch-to-configuration test"
         )
         assert "/run/wrappers/" in machine.succeed("which passwd")
+
+    with subtest("dry-activation does not change files"):
+        machine.succeed('test -e /home/dry-test')  # home was created
+        machine.succeed('rm -rf /home/dry-test')
+
+        files_to_check = ['/etc/group',
+                          '/etc/passwd',
+                          '/etc/shadow',
+                          '/etc/subuid',
+                          '/etc/subgid',
+                          '/var/lib/nixos/uid-map',
+                          '/var/lib/nixos/gid-map',
+                          '/var/lib/nixos/declarative-groups',
+                          '/var/lib/nixos/declarative-users'
+                         ]
+        expected_hashes = {}
+        expected_stats = {}
+        for file in files_to_check:
+            expected_hashes[file] = machine.succeed(f"sha256sum {file}")
+            expected_stats[file] = machine.succeed(f"stat {file}")
+
+        machine.succeed("/run/current-system/bin/switch-to-configuration dry-activate")
+
+        machine.fail('test -e /home/dry-test')  # home was not recreated
+        for file in files_to_check:
+            assert machine.succeed(f"sha256sum {file}") == expected_hashes[file]
+            assert machine.succeed(f"stat {file}") == expected_stats[file]
   '';
 })