summary refs log tree commit diff
path: root/nixos/modules/config
diff options
context:
space:
mode:
Diffstat (limited to 'nixos/modules/config')
-rw-r--r--nixos/modules/config/console.nix36
-rw-r--r--nixos/modules/config/fonts/fontconfig.nix61
-rw-r--r--nixos/modules/config/fonts/fontdir.nix47
-rw-r--r--nixos/modules/config/fonts/fonts.nix12
-rw-r--r--nixos/modules/config/gnu.nix10
-rw-r--r--nixos/modules/config/i18n.nix2
-rw-r--r--nixos/modules/config/iproute2.nix18
-rw-r--r--nixos/modules/config/krb5/default.nix34
-rw-r--r--nixos/modules/config/ldap.nix35
-rw-r--r--nixos/modules/config/malloc.nix9
-rw-r--r--nixos/modules/config/networking.nix4
-rw-r--r--nixos/modules/config/no-x-libs.nix4
-rw-r--r--nixos/modules/config/nsswitch.nix4
-rw-r--r--nixos/modules/config/pulseaudio.nix25
-rw-r--r--nixos/modules/config/shells-environment.nix12
-rw-r--r--nixos/modules/config/swap.nix28
-rw-r--r--nixos/modules/config/system-path.nix38
-rw-r--r--nixos/modules/config/update-users-groups.pl38
-rw-r--r--nixos/modules/config/users-groups.nix108
-rw-r--r--nixos/modules/config/xdg/portal.nix2
-rw-r--r--nixos/modules/config/xdg/portals/wlr.nix67
-rw-r--r--nixos/modules/config/zram.nix20
22 files changed, 439 insertions, 175 deletions
diff --git a/nixos/modules/config/console.nix b/nixos/modules/config/console.nix
index f662ed62d31..c5150305bd8 100644
--- a/nixos/modules/config/console.nix
+++ b/nixos/modules/config/console.nix
@@ -43,13 +43,14 @@ in
 
   options.console  = {
     font = mkOption {
-      type = types.str;
+      type = with types; either str path;
       default = "Lat2-Terminus16";
       example = "LatArCyrHeb-16";
       description = ''
         The font used for the virtual consoles.  Leave empty to use
         whatever the <command>setfont</command> program considers the
         default font.
+        Can be either a font name or a path to a PSF font file.
       '';
     };
 
@@ -82,28 +83,13 @@ in
 
     packages = mkOption {
       type = types.listOf types.package;
-      default = with pkgs.kbdKeymaps; [ dvp neo ];
-      defaultText = ''with pkgs.kbdKeymaps; [ dvp neo ]'';
+      default = [ ];
       description = ''
         List of additional packages that provide console fonts, keymaps and
         other resources for virtual consoles use.
       '';
     };
 
-    extraTTYs = mkOption {
-      default = [];
-      type = types.listOf types.str;
-      example = ["tty8" "tty9"];
-      description = ''
-        TTY (virtual console) devices, in addition to the consoles on
-        which mingetty and syslogd run, that must be initialised.
-        Only useful if you have some program that you want to run on
-        some fixed console.  For example, the NixOS installation CD
-        opens the manual in a web browser on console 7, so it sets
-        <option>console.extraTTYs</option> to <literal>["tty7"]</literal>.
-      '';
-    };
-
     useXkbConfig = mkOption {
       type = types.bool;
       default = false;
@@ -158,10 +144,16 @@ in
           ''}
         '';
 
-        systemd.services.systemd-vconsole-setup =
-          { before = [ "display-manager.service" ];
-            after = [ "systemd-udev-settle.service" ];
+        systemd.services.reload-systemd-vconsole-setup =
+          { description = "Reset console on configuration changes";
+            wantedBy = [ "multi-user.target" ];
             restartTriggers = [ vconsoleConf consoleEnv ];
+            reloadIfChanged = true;
+            serviceConfig =
+              { RemainAfterExit = true;
+                ExecStart = "${pkgs.coreutils}/bin/true";
+                ExecReload = "/run/current-system/systemd/bin/systemctl restart systemd-vconsole-setup";
+              };
           };
       }
 
@@ -199,5 +191,9 @@ in
     (mkRenamedOptionModule [ "i18n" "consoleUseXkbConfig" ] [ "console" "useXkbConfig" ])
     (mkRenamedOptionModule [ "boot" "earlyVconsoleSetup" ] [ "console" "earlySetup" ])
     (mkRenamedOptionModule [ "boot" "extraTTYs" ] [ "console" "extraTTYs" ])
+    (mkRemovedOptionModule [ "console" "extraTTYs" ] ''
+      Since NixOS switched to systemd (circa 2012), TTYs have been spawned on
+      demand, so there is no need to configure them manually.
+    '')
   ];
 }
diff --git a/nixos/modules/config/fonts/fontconfig.nix b/nixos/modules/config/fonts/fontconfig.nix
index 1f1044bc5af..72827c5abaa 100644
--- a/nixos/modules/config/fonts/fontconfig.nix
+++ b/nixos/modules/config/fonts/fontconfig.nix
@@ -1,6 +1,6 @@
 /*
 
-Configuration files are linked to /etc/fonts/${pkgs.fontconfig.configVersion}/conf.d/
+Configuration files are linked to /etc/fonts/conf.d/
 
 This module generates a package containing configuration files and link it in /etc/fonts.
 
@@ -35,7 +35,7 @@ let
     in
     pkgs.writeText "fc-00-nixos-cache.conf" ''
       <?xml version='1.0'?>
-      <!DOCTYPE fontconfig SYSTEM 'fonts.dtd'>
+      <!DOCTYPE fontconfig SYSTEM 'urn:fontconfig:fonts.dtd'>
       <fontconfig>
         <!-- Font directories -->
         ${concatStringsSep "\n" (map (font: "<dir>${font}</dir>") config.fonts.fonts)}
@@ -53,7 +53,7 @@ let
   # priority 10
   renderConf = pkgs.writeText "fc-10-nixos-rendering.conf" ''
     <?xml version='1.0'?>
-    <!DOCTYPE fontconfig SYSTEM 'fonts.dtd'>
+    <!DOCTYPE fontconfig SYSTEM 'urn:fontconfig:fonts.dtd'>
     <fontconfig>
 
       <!-- Default rendering settings -->
@@ -110,7 +110,7 @@ let
     in
     pkgs.writeText "fc-52-nixos-default-fonts.conf" ''
     <?xml version='1.0'?>
-    <!DOCTYPE fontconfig SYSTEM 'fonts.dtd'>
+    <!DOCTYPE fontconfig SYSTEM 'urn:fontconfig:fonts.dtd'>
     <fontconfig>
 
       <!-- Default fonts -->
@@ -129,7 +129,7 @@ let
   # priority 53
   rejectBitmaps = pkgs.writeText "fc-53-no-bitmaps.conf" ''
     <?xml version="1.0"?>
-    <!DOCTYPE fontconfig SYSTEM "fonts.dtd">
+    <!DOCTYPE fontconfig SYSTEM "urn:fontconfig:fonts.dtd">
     <fontconfig>
 
     ${optionalString (!cfg.allowBitmaps) ''
@@ -157,7 +157,7 @@ let
   # priority 53
   rejectType1 = pkgs.writeText "fc-53-nixos-reject-type1.conf" ''
     <?xml version="1.0"?>
-    <!DOCTYPE fontconfig SYSTEM "fonts.dtd">
+    <!DOCTYPE fontconfig SYSTEM "urn:fontconfig:fonts.dtd">
     <fontconfig>
 
     <!-- Reject Type 1 fonts -->
@@ -176,15 +176,16 @@ let
   confPkg = pkgs.runCommand "fontconfig-conf" {
     preferLocalBuild = true;
   } ''
-    dst=$out/etc/fonts/${pkg.configVersion}/conf.d
+    dst=$out/etc/fonts/conf.d
     mkdir -p $dst
 
     # fonts.conf
     ln -s ${pkg.out}/etc/fonts/fonts.conf \
           $dst/../fonts.conf
     # TODO: remove this legacy symlink once people stop using packages built before #95358 was merged
-    ln -s /etc/fonts/${pkg.configVersion}/fonts.conf \
-          $out/etc/fonts/fonts.conf
+    mkdir -p $out/etc/fonts/2.11
+    ln -s /etc/fonts/fonts.conf \
+          $out/etc/fonts/2.11/fonts.conf
 
     # fontconfig default config files
     ln -s ${pkg.out}/etc/fonts/conf.d/*.conf \
@@ -197,10 +198,8 @@ let
     ln -s ${renderConf}       $dst/10-nixos-rendering.conf
 
     # 50-user.conf
-    # Since latest fontconfig looks for default files inside the package,
-    # we had to move this one elsewhere to be able to exclude it here.
-    ${optionalString cfg.includeUserConf ''
-    ln -s ${pkg.out}/etc/fonts/conf.d.bak/50-user.conf $dst/50-user.conf
+    ${optionalString (!cfg.includeUserConf) ''
+    rm $dst/50-user.conf
     ''}
 
     # local.conf (indirect priority 51)
@@ -437,7 +436,7 @@ in
         useEmbeddedBitmaps = mkOption {
           type = types.bool;
           default = false;
-          description = ''Use embedded bitmaps in fonts like Calibri.'';
+          description = "Use embedded bitmaps in fonts like Calibri.";
         };
 
       };
@@ -449,6 +448,40 @@ in
     (mkIf cfg.enable {
       environment.systemPackages    = [ pkgs.fontconfig ];
       environment.etc.fonts.source  = "${fontconfigEtc}/etc/fonts/";
+      security.apparmor.includes."abstractions/fonts" = ''
+        # fonts.conf
+        r ${pkg.out}/etc/fonts/fonts.conf,
+
+        # fontconfig default config files
+        r ${pkg.out}/etc/fonts/conf.d/*.conf,
+
+        # 00-nixos-cache.conf
+        r ${cacheConf},
+
+        # 10-nixos-rendering.conf
+        r ${renderConf},
+
+        # 50-user.conf
+        ${optionalString cfg.includeUserConf ''
+        r ${pkg.out}/etc/fonts/conf.d.bak/50-user.conf,
+        ''}
+
+        # local.conf (indirect priority 51)
+        ${optionalString (cfg.localConf != "") ''
+        r ${localConf},
+        ''}
+
+        # 52-nixos-default-fonts.conf
+        r ${defaultFontsConf},
+
+        # 53-no-bitmaps.conf
+        r ${rejectBitmaps},
+
+        ${optionalString (!cfg.allowType1) ''
+        # 53-nixos-reject-type1.conf
+        r ${rejectType1},
+        ''}
+      '';
     })
     (mkIf cfg.enable {
       fonts.fontconfig.confPackages = [ confPkg ];
diff --git a/nixos/modules/config/fonts/fontdir.nix b/nixos/modules/config/fonts/fontdir.nix
index a6aa84ae822..c4bd3a077d3 100644
--- a/nixos/modules/config/fonts/fontdir.nix
+++ b/nixos/modules/config/fonts/fontdir.nix
@@ -4,15 +4,19 @@ with lib;
 
 let
 
+  cfg = config.fonts.fontDir;
+
   x11Fonts = pkgs.runCommand "X11-fonts" { preferLocalBuild = true; } ''
-    mkdir -p "$out/share/X11-fonts"
-    find ${toString config.fonts.fonts} \
-      \( -name fonts.dir -o -name '*.ttf' -o -name '*.otf' \) \
-      -exec ln -sf -t "$out/share/X11-fonts" '{}' \;
-    cd "$out/share/X11-fonts"
-    rm -f fonts.dir fonts.scale fonts.alias
-    ${pkgs.xorg.mkfontdir}/bin/mkfontdir
+    mkdir -p "$out/share/X11/fonts"
+    font_regexp='.*\.\(ttf\|ttc\|otf\|pcf\|pfa\|pfb\|bdf\)\(\.gz\)?'
+    find ${toString config.fonts.fonts} -regex "$font_regexp" \
+      -exec ln -sf -t "$out/share/X11/fonts" '{}' \;
+    cd "$out/share/X11/fonts"
+    ${optionalString cfg.decompressFonts ''
+      ${pkgs.gzip}/bin/gunzip -f *.gz
+    ''}
     ${pkgs.xorg.mkfontscale}/bin/mkfontscale
+    ${pkgs.xorg.mkfontdir}/bin/mkfontdir
     cat $(find ${pkgs.xorg.fontalias}/ -name fonts.alias) >fonts.alias
   '';
 
@@ -21,28 +25,43 @@ in
 {
 
   options = {
+    fonts.fontDir = {
 
-    fonts = {
-
-      enableFontDir = mkOption {
+      enable = mkOption {
         type = types.bool;
         default = false;
         description = ''
           Whether to create a directory with links to all fonts in
-          <filename>/run/current-system/sw/share/X11-fonts</filename>.
+          <filename>/run/current-system/sw/share/X11/fonts</filename>.
         '';
       };
 
-    };
+      decompressFonts = mkOption {
+        type = types.bool;
+        default = config.programs.xwayland.enable;
+        description = ''
+          Whether to decompress fonts in
+          <filename>/run/current-system/sw/share/X11/fonts</filename>.
+        '';
+      };
 
+    };
   };
 
-  config = mkIf config.fonts.enableFontDir {
+  config = mkIf cfg.enable {
 
+    # This is enough to make a symlink because the xserver
+    # module already links all /share/X11 paths.
     environment.systemPackages = [ x11Fonts ];
 
-    environment.pathsToLink = [ "/share/X11-fonts" ];
+    services.xserver.filesSection = ''
+      FontPath "${x11Fonts}/share/X11/fonts"
+    '';
 
   };
 
+  imports = [
+    (mkRenamedOptionModule [ "fonts" "enableFontDir" ] [ "fonts" "fontDir" "enable" ])
+  ];
+
 }
diff --git a/nixos/modules/config/fonts/fonts.nix b/nixos/modules/config/fonts/fonts.nix
index b9bae44b2f9..3911196c101 100644
--- a/nixos/modules/config/fonts/fonts.nix
+++ b/nixos/modules/config/fonts/fonts.nix
@@ -35,19 +35,21 @@ with lib;
   config = {
 
     fonts.fonts = mkIf config.fonts.enableDefaultFonts
-      [
-        pkgs.xorg.fontbhlucidatypewriter100dpi
-        pkgs.xorg.fontbhlucidatypewriter75dpi
+      ([
         pkgs.dejavu_fonts
         pkgs.freefont_ttf
         pkgs.gyre-fonts # TrueType substitutes for standard PostScript fonts
         pkgs.liberation_ttf
-        pkgs.xorg.fontbh100dpi
         pkgs.xorg.fontmiscmisc
         pkgs.xorg.fontcursormisc
         pkgs.unifont
         pkgs.noto-fonts-emoji
-      ];
+      ] ++ lib.optionals (config.nixpkgs.config.allowUnfree or false) [
+        # these are unfree, and will make usage with xserver fail
+        pkgs.xorg.fontbhlucidatypewriter100dpi
+        pkgs.xorg.fontbhlucidatypewriter75dpi
+        pkgs.xorg.fontbh100dpi
+      ]);
 
   };
 
diff --git a/nixos/modules/config/gnu.nix b/nixos/modules/config/gnu.nix
index 93d13097019..255d9741ba7 100644
--- a/nixos/modules/config/gnu.nix
+++ b/nixos/modules/config/gnu.nix
@@ -1,11 +1,9 @@
 { config, lib, pkgs, ... }:
 
-with lib;
-
 {
   options = {
-    gnu = mkOption {
-      type = types.bool;
+    gnu = lib.mkOption {
+      type = lib.types.bool;
       default = false;
       description = ''
         When enabled, GNU software is chosen by default whenever a there is
@@ -15,7 +13,7 @@ with lib;
     };
   };
 
-  config = mkIf config.gnu {
+  config = lib.mkIf config.gnu {
 
     environment.systemPackages = with pkgs;
       # TODO: Adjust `requiredPackages' from `system-path.nix'.
@@ -26,7 +24,7 @@ with lib;
         nano zile
         texinfo # for the stand-alone Info reader
       ]
-      ++ stdenv.lib.optional (!stdenv.isAarch32) grub2;
+      ++ lib.optional (!stdenv.isAarch32) grub2;
 
 
     # GNU GRUB, where available.
diff --git a/nixos/modules/config/i18n.nix b/nixos/modules/config/i18n.nix
index feb76581a72..991b449d80b 100644
--- a/nixos/modules/config/i18n.nix
+++ b/nixos/modules/config/i18n.nix
@@ -84,7 +84,7 @@ with lib;
     environment.etc."locale.conf".source = pkgs.writeText "locale.conf"
       ''
         LANG=${config.i18n.defaultLocale}
-        ${concatStringsSep "\n" (mapAttrsToList (n: v: ''${n}=${v}'') config.i18n.extraLocaleSettings)}
+        ${concatStringsSep "\n" (mapAttrsToList (n: v: "${n}=${v}") config.i18n.extraLocaleSettings)}
       '';
 
   };
diff --git a/nixos/modules/config/iproute2.nix b/nixos/modules/config/iproute2.nix
index a1d9ebcec66..5f41f3d21e4 100644
--- a/nixos/modules/config/iproute2.nix
+++ b/nixos/modules/config/iproute2.nix
@@ -18,15 +18,15 @@ in
   };
 
   config = mkIf cfg.enable {
-    environment.etc."iproute2/bpf_pinning" = { mode = "0644"; text = fileContents "${pkgs.iproute}/etc/iproute2/bpf_pinning"; };
-    environment.etc."iproute2/ematch_map"  = { mode = "0644"; text = fileContents "${pkgs.iproute}/etc/iproute2/ematch_map";  };
-    environment.etc."iproute2/group"       = { mode = "0644"; text = fileContents "${pkgs.iproute}/etc/iproute2/group";       };
-    environment.etc."iproute2/nl_protos"   = { mode = "0644"; text = fileContents "${pkgs.iproute}/etc/iproute2/nl_protos";   };
-    environment.etc."iproute2/rt_dsfield"  = { mode = "0644"; text = fileContents "${pkgs.iproute}/etc/iproute2/rt_dsfield";  };
-    environment.etc."iproute2/rt_protos"   = { mode = "0644"; text = fileContents "${pkgs.iproute}/etc/iproute2/rt_protos";   };
-    environment.etc."iproute2/rt_realms"   = { mode = "0644"; text = fileContents "${pkgs.iproute}/etc/iproute2/rt_realms";   };
-    environment.etc."iproute2/rt_scopes"   = { mode = "0644"; text = fileContents "${pkgs.iproute}/etc/iproute2/rt_scopes";   };
-    environment.etc."iproute2/rt_tables"   = { mode = "0644"; text = (fileContents "${pkgs.iproute}/etc/iproute2/rt_tables")
+    environment.etc."iproute2/bpf_pinning" = { mode = "0644"; text = fileContents "${pkgs.iproute2}/etc/iproute2/bpf_pinning"; };
+    environment.etc."iproute2/ematch_map"  = { mode = "0644"; text = fileContents "${pkgs.iproute2}/etc/iproute2/ematch_map";  };
+    environment.etc."iproute2/group"       = { mode = "0644"; text = fileContents "${pkgs.iproute2}/etc/iproute2/group";       };
+    environment.etc."iproute2/nl_protos"   = { mode = "0644"; text = fileContents "${pkgs.iproute2}/etc/iproute2/nl_protos";   };
+    environment.etc."iproute2/rt_dsfield"  = { mode = "0644"; text = fileContents "${pkgs.iproute2}/etc/iproute2/rt_dsfield";  };
+    environment.etc."iproute2/rt_protos"   = { mode = "0644"; text = fileContents "${pkgs.iproute2}/etc/iproute2/rt_protos";   };
+    environment.etc."iproute2/rt_realms"   = { mode = "0644"; text = fileContents "${pkgs.iproute2}/etc/iproute2/rt_realms";   };
+    environment.etc."iproute2/rt_scopes"   = { mode = "0644"; text = fileContents "${pkgs.iproute2}/etc/iproute2/rt_scopes";   };
+    environment.etc."iproute2/rt_tables"   = { mode = "0644"; text = (fileContents "${pkgs.iproute2}/etc/iproute2/rt_tables")
                                                                    + (optionalString (cfg.rttablesExtraConfig != "") "\n\n${cfg.rttablesExtraConfig}"); };
   };
 }
diff --git a/nixos/modules/config/krb5/default.nix b/nixos/modules/config/krb5/default.nix
index ff16ffcf9c6..c2302451d70 100644
--- a/nixos/modules/config/krb5/default.nix
+++ b/nixos/modules/config/krb5/default.nix
@@ -41,31 +41,30 @@ let
         value)
     else value;
 
-  mkIndent = depth: concatStrings (builtins.genList (_:  " ") (2 * depth));
+  indent = "  ";
 
-  mkRelation = name: value: "${name} = ${mkVal { inherit value; }}";
+  mkRelation = name: value:
+    if (isList value) then
+      concatMapStringsSep "\n" (mkRelation name) value
+    else "${name} = ${mkVal value}";
 
-  mkVal = { value, depth ? 0 }:
+  mkVal = value:
     if (value == true) then "true"
     else if (value == false) then "false"
     else if (isInt value) then (toString value)
-    else if (isList value) then
-      concatMapStringsSep " " mkVal { inherit value depth; }
     else if (isAttrs value) then
-      (concatStringsSep "\n${mkIndent (depth + 1)}"
-        ([ "{" ] ++ (mapAttrsToList
-          (attrName: attrValue: let
-            mappedAttrValue = mkVal {
-              value = attrValue;
-              depth = depth + 1;
-            };
-          in "${attrName} = ${mappedAttrValue}")
-        value))) + "\n${mkIndent depth}}"
+      let configLines = concatLists
+        (map (splitString "\n")
+          (mapAttrsToList mkRelation value));
+      in
+      (concatStringsSep "\n${indent}"
+        ([ "{" ] ++ configLines))
+      + "\n}"
     else value;
 
   mkMappedAttrsOrString = value: concatMapStringsSep "\n"
     (line: if builtins.stringLength line > 0
-      then "${mkIndent 1}${line}"
+      then "${indent}${line}"
       else line)
     (splitString "\n"
       (if isAttrs value then
@@ -114,7 +113,10 @@ in {
           {
             "ATHENA.MIT.EDU" = {
               admin_server = "athena.mit.edu";
-              kdc = "athena.mit.edu";
+              kdc = [
+                "athena01.mit.edu"
+                "athena02.mit.edu"
+              ];
             };
           };
         '';
diff --git a/nixos/modules/config/ldap.nix b/nixos/modules/config/ldap.nix
index 1a5dbcd4e26..35813c168fd 100644
--- a/nixos/modules/config/ldap.nix
+++ b/nixos/modules/config/ldap.nix
@@ -59,30 +59,28 @@ in
 
     users.ldap = {
 
-      enable = mkOption {
-        type = types.bool;
-        default = false;
-        description = "Whether to enable authentication against an LDAP server.";
-      };
+      enable = mkEnableOption "authentication against an LDAP server";
 
       loginPam = mkOption {
         type = types.bool;
         default = true;
-        description = "Whether to include authentication against LDAP in login PAM";
+        description = "Whether to include authentication against LDAP in login PAM.";
       };
 
       nsswitch = mkOption {
         type = types.bool;
         default = true;
-        description = "Whether to include lookup against LDAP in NSS";
+        description = "Whether to include lookup against LDAP in NSS.";
       };
 
       server = mkOption {
+        type = types.str;
         example = "ldap://ldap.example.org/";
         description = "The URL of the LDAP server.";
       };
 
       base = mkOption {
+        type = types.str;
         example = "dc=example,dc=org";
         description = "The distinguished name of the search base.";
       };
@@ -129,7 +127,7 @@ in
           type = types.lines;
           description = ''
             Extra configuration options that will be added verbatim at
-            the end of the nslcd configuration file (nslcd.conf).
+            the end of the nslcd configuration file (<literal>nslcd.conf(5)</literal>).
           '' ;
         } ;
 
@@ -180,7 +178,7 @@ in
           description = ''
             Specifies the time limit (in seconds) to use when connecting
             to the directory server. This is distinct from the time limit
-            specified in <literal>users.ldap.timeLimit</literal> and affects
+            specified in <option>users.ldap.timeLimit</option> and affects
             the initial server connection only.
           '';
         };
@@ -197,7 +195,7 @@ in
             actually contact the directory server, and it is possible that
             a malformed configuration file will trigger reconnection. If
             <literal>soft</literal> is specified, then
-            <literal>nss_ldap</literal> will return immediately on server
+            <package>nss_ldap</package> will return immediately on server
             failure. All hard reconnect policies block with exponential
             backoff before retrying.
           '';
@@ -209,10 +207,10 @@ in
         type = types.lines;
         description = ''
           Extra configuration options that will be added verbatim at
-          the end of the ldap configuration file (ldap.conf).
-          If <literal>users.ldap.daemon</literal> is enabled, this
+          the end of the ldap configuration file (<literal>ldap.conf(5)</literal>).
+          If <option>users.ldap.daemon</option> is enabled, this
           configuration will not be used. In that case, use
-          <literal>users.ldap.daemon.extraConfig</literal> instead.
+          <option>users.ldap.daemon.extraConfig</option> instead.
         '' ;
       };
 
@@ -240,9 +238,9 @@ in
       '';
     };
 
-    system.nssModules = singleton (
+    system.nssModules = mkIf cfg.nsswitch (singleton (
       if cfg.daemon.enable then nss_pam_ldapd else nss_ldap
-    );
+    ));
 
     system.nssDatabases.group = optional cfg.nsswitch "ldap";
     system.nssDatabases.passwd = optional cfg.nsswitch "ldap";
@@ -276,7 +274,12 @@ in
           } >"$conf"
           mv -fT "$conf" /run/nslcd/nslcd.conf
         '';
-        restartTriggers = [ "/run/nslcd/nslcd.conf" ];
+
+        restartTriggers = [
+          nslcdConfig
+          cfg.bind.passwordFile
+          cfg.daemon.rootpwmodpwFile
+        ];
 
         serviceConfig = {
           ExecStart = "${nslcdWrapped}/bin/nslcd";
diff --git a/nixos/modules/config/malloc.nix b/nixos/modules/config/malloc.nix
index 31a659ee83f..fc35993b5a8 100644
--- a/nixos/modules/config/malloc.nix
+++ b/nixos/modules/config/malloc.nix
@@ -23,7 +23,7 @@ let
     };
 
     scudo = {
-      libPath = "${pkgs.llvmPackages.compiler-rt}/lib/linux/libclang_rt.scudo-x86_64.so";
+      libPath = "${pkgs.llvmPackages_latest.compiler-rt}/lib/linux/libclang_rt.scudo-x86_64.so";
       description = ''
         A user-mode allocator based on LLVM Sanitizer’s CombinedAllocator,
         which aims at providing additional mitigations against heap based
@@ -87,5 +87,12 @@ in
     environment.etc."ld-nix.so.preload".text = ''
       ${providerLibPath}
     '';
+    security.apparmor.includes = {
+      "abstractions/base" = ''
+        r /etc/ld-nix.so.preload,
+        r ${config.environment.etc."ld-nix.so.preload".source},
+        mr ${providerLibPath},
+      '';
+    };
   };
 }
diff --git a/nixos/modules/config/networking.nix b/nixos/modules/config/networking.nix
index 4cb7d81c997..dba8977e482 100644
--- a/nixos/modules/config/networking.nix
+++ b/nixos/modules/config/networking.nix
@@ -58,6 +58,7 @@ in
         "2.nixos.pool.ntp.org"
         "3.nixos.pool.ntp.org"
       ];
+      type = types.listOf types.str;
       description = ''
         The set of NTP servers from which to synchronise.
       '';
@@ -193,6 +194,9 @@ in
           cat ${escapeShellArgs cfg.hostFiles} > $out
         '';
 
+        # /etc/netgroup: Network-wide groups.
+        netgroup.text = mkDefault "";
+
         # /etc/host.conf: resolver configuration file
         "host.conf".text = ''
           multi on
diff --git a/nixos/modules/config/no-x-libs.nix b/nixos/modules/config/no-x-libs.nix
index 941ab78f863..14fe180d0bc 100644
--- a/nixos/modules/config/no-x-libs.nix
+++ b/nixos/modules/config/no-x-libs.nix
@@ -29,12 +29,14 @@ with lib;
     nixpkgs.overlays = singleton (const (super: {
       cairo = super.cairo.override { x11Support = false; };
       dbus = super.dbus.override { x11Support = false; };
+      beam = super.beam_nox;
       networkmanager-fortisslvpn = super.networkmanager-fortisslvpn.override { withGnome = false; };
+      networkmanager-iodine = super.networkmanager-iodine.override { withGnome = false; };
       networkmanager-l2tp = super.networkmanager-l2tp.override { withGnome = false; };
       networkmanager-openconnect = super.networkmanager-openconnect.override { withGnome = false; };
       networkmanager-openvpn = super.networkmanager-openvpn.override { withGnome = false; };
+      networkmanager-sstp = super.networkmanager-vpnc.override { withGnome = false; };
       networkmanager-vpnc = super.networkmanager-vpnc.override { withGnome = false; };
-      networkmanager-iodine = super.networkmanager-iodine.override { withGnome = false; };
       gobject-introspection = super.gobject-introspection.override { x11Support = false; };
       qemu = super.qemu.override { gtkSupport = false; spiceSupport = false; sdlSupport = false; };
     }));
diff --git a/nixos/modules/config/nsswitch.nix b/nixos/modules/config/nsswitch.nix
index d19d35a4890..91a36cef10e 100644
--- a/nixos/modules/config/nsswitch.nix
+++ b/nixos/modules/config/nsswitch.nix
@@ -124,8 +124,8 @@ with lib;
       group = mkBefore [ "files" ];
       shadow = mkBefore [ "files" ];
       hosts = mkMerge [
-        (mkBefore [ "files" ])
-        (mkAfter [ "dns" ])
+        (mkOrder 998 [ "files" ])
+        (mkOrder 1499 [ "dns" ])
       ];
       services = mkBefore [ "files" ];
     };
diff --git a/nixos/modules/config/pulseaudio.nix b/nixos/modules/config/pulseaudio.nix
index 408d0a9c33f..3f7ae109e8c 100644
--- a/nixos/modules/config/pulseaudio.nix
+++ b/nixos/modules/config/pulseaudio.nix
@@ -17,9 +17,9 @@ let
   binary = "${getBin overriddenPackage}/bin/pulseaudio";
   binaryNoDaemon = "${binary} --daemonize=no";
 
-  # Forces 32bit pulseaudio and alsaPlugins to be built/supported for apps
+  # Forces 32bit pulseaudio and alsa-plugins to be built/supported for apps
   # using 32bit alsa on 64bit linux.
-  enable32BitAlsaPlugins = cfg.support32Bit && stdenv.isx86_64 && (pkgs.pkgsi686Linux.alsaLib != null && pkgs.pkgsi686Linux.libpulseaudio != null);
+  enable32BitAlsaPlugins = cfg.support32Bit && stdenv.isx86_64 && (pkgs.pkgsi686Linux.alsa-lib != null && pkgs.pkgsi686Linux.libpulseaudio != null);
 
 
   myConfigFile =
@@ -36,6 +36,8 @@ let
         ${addModuleIf cfg.zeroconf.discovery.enable "module-zeroconf-discover"}
         ${addModuleIf cfg.tcp.enable (concatStringsSep " "
            ([ "module-native-protocol-tcp" ] ++ allAnon ++ ipAnon))}
+        ${addModuleIf config.services.jack.jackd.enable "module-jack-sink"}
+        ${addModuleIf config.services.jack.jackd.enable "module-jack-source"}
         ${cfg.extraConfig}
       '';
     };
@@ -60,18 +62,18 @@ let
   # plugin.
   alsaConf = writeText "asound.conf" (''
     pcm_type.pulse {
-      libs.native = ${pkgs.alsaPlugins}/lib/alsa-lib/libasound_module_pcm_pulse.so ;
+      libs.native = ${pkgs.alsa-plugins}/lib/alsa-lib/libasound_module_pcm_pulse.so ;
       ${lib.optionalString enable32BitAlsaPlugins
-     "libs.32Bit = ${pkgs.pkgsi686Linux.alsaPlugins}/lib/alsa-lib/libasound_module_pcm_pulse.so ;"}
+     "libs.32Bit = ${pkgs.pkgsi686Linux.alsa-plugins}/lib/alsa-lib/libasound_module_pcm_pulse.so ;"}
     }
     pcm.!default {
       type pulse
       hint.description "Default Audio Device (via PulseAudio)"
     }
     ctl_type.pulse {
-      libs.native = ${pkgs.alsaPlugins}/lib/alsa-lib/libasound_module_ctl_pulse.so ;
+      libs.native = ${pkgs.alsa-plugins}/lib/alsa-lib/libasound_module_ctl_pulse.so ;
       ${lib.optionalString enable32BitAlsaPlugins
-     "libs.32Bit = ${pkgs.pkgsi686Linux.alsaPlugins}/lib/alsa-lib/libasound_module_ctl_pulse.so ;"}
+     "libs.32Bit = ${pkgs.pkgsi686Linux.alsa-plugins}/lib/alsa-lib/libasound_module_ctl_pulse.so ;"}
     }
     ctl.!default {
       type pulse
@@ -144,7 +146,9 @@ in {
 
       package = mkOption {
         type = types.package;
-        default = pkgs.pulseaudio;
+        default = if config.services.jack.jackd.enable
+                  then pkgs.pulseaudioFull
+                  else pkgs.pulseaudio;
         defaultText = "pkgs.pulseaudio";
         example = literalExample "pkgs.pulseaudioFull";
         description = ''
@@ -179,7 +183,7 @@ in {
         config = mkOption {
           type = types.attrsOf types.unspecified;
           default = {};
-          description = ''Config of the pulse daemon. See <literal>man pulse-daemon.conf</literal>.'';
+          description = "Config of the pulse daemon. See <literal>man pulse-daemon.conf</literal>.";
           example = literalExample ''{ realtime-scheduling = "yes"; }'';
         };
       };
@@ -259,7 +263,7 @@ in {
           (drv: drv.override { pulseaudio = overriddenPackage; })
           cfg.extraModules;
         modulePaths = builtins.map
-          (drv: "${drv}/lib/pulse-${overriddenPackage.version}/modules")
+          (drv: "${drv}/${overriddenPackage.pulseDir}/modules")
           # User-provided extra modules take precedence
           (overriddenModules ++ [ overriddenPackage ]);
       in lib.concatStringsSep ":" modulePaths;
@@ -284,6 +288,8 @@ in {
             RestartSec = "500ms";
             PassEnvironment = "DISPLAY";
           };
+        } // optionalAttrs config.services.jack.jackd.enable {
+          environment.JACK_PROMISCUOUS_SERVER = "jackaudio";
         };
         sockets.pulseaudio = {
           wantedBy = [ "sockets.target" ];
@@ -300,6 +306,7 @@ in {
         description = "PulseAudio system service user";
         home = stateDir;
         createHome = true;
+        isSystemUser = true;
       };
 
       users.groups.pulse.gid = gid;
diff --git a/nixos/modules/config/shells-environment.nix b/nixos/modules/config/shells-environment.nix
index a0a20228a74..34e558d8603 100644
--- a/nixos/modules/config/shells-environment.nix
+++ b/nixos/modules/config/shells-environment.nix
@@ -126,6 +126,14 @@ in
       type = types.bool;
     };
 
+    environment.localBinInPath = mkOption {
+      description = ''
+        Add ~/.local/bin/ to $PATH
+      '';
+      default = false;
+      type = types.bool;
+    };
+
     environment.binsh = mkOption {
       default = "${config.system.build.binsh}/bin/sh";
       defaultText = "\${config.system.build.binsh}/bin/sh";
@@ -198,6 +206,10 @@ in
           # ~/bin if it exists overrides other bin directories.
           export PATH="$HOME/bin:$PATH"
         ''}
+
+        ${optionalString cfg.localBinInPath ''
+          export PATH="$HOME/.local/bin:$PATH"
+        ''}
       '';
 
     system.activationScripts.binsh = stringAfter [ "stdio" ]
diff --git a/nixos/modules/config/swap.nix b/nixos/modules/config/swap.nix
index adb4e229421..ff2ae1da31b 100644
--- a/nixos/modules/config/swap.nix
+++ b/nixos/modules/config/swap.nix
@@ -114,6 +114,28 @@ let
         '';
       };
 
+      discardPolicy = mkOption {
+        default = null;
+        example = "once";
+        type = types.nullOr (types.enum ["once" "pages" "both" ]);
+        description = ''
+          Specify the discard policy for the swap device. If "once", then the
+          whole swap space is discarded at swapon invocation. If "pages",
+          asynchronous discard on freed pages is performed, before returning to
+          the available pages pool. With "both", both policies are activated.
+          See swapon(8) for more information.
+        '';
+      };
+
+      options = mkOption {
+        default = [ "defaults" ];
+        example = [ "nofail" ];
+        type = types.listOf types.nonEmptyStr;
+        description = ''
+          Options used to mount the swap.
+        '';
+      };
+
       deviceName = mkOption {
         type = types.str;
         internal = true;
@@ -185,9 +207,7 @@ in
           { description = "Initialisation of swap device ${sw.device}";
             wantedBy = [ "${realDevice'}.swap" ];
             before = [ "${realDevice'}.swap" ];
-            # If swap is encrypted, depending on rngd resolves a possible entropy starvation during boot
-            after = mkIf (config.security.rngd.enable && sw.randomEncryption.enable) [ "rngd.service" ];
-            path = [ pkgs.utillinux ] ++ optional sw.randomEncryption.enable pkgs.cryptsetup;
+            path = [ pkgs.util-linux ] ++ optional sw.randomEncryption.enable pkgs.cryptsetup;
 
             script =
               ''
@@ -204,7 +224,7 @@ in
                   fi
                 ''}
                 ${optionalString sw.randomEncryption.enable ''
-                  cryptsetup plainOpen -c ${sw.randomEncryption.cipher} -d ${sw.randomEncryption.source} ${sw.device} ${sw.deviceName}
+                  cryptsetup plainOpen -c ${sw.randomEncryption.cipher} -d ${sw.randomEncryption.source} ${optionalString (sw.discardPolicy != null) "--allow-discards"} ${sw.device} ${sw.deviceName}
                   mkswap ${sw.realDevice}
                 ''}
               '';
diff --git a/nixos/modules/config/system-path.nix b/nixos/modules/config/system-path.nix
index b3c5c6f93f3..1292c3008c6 100644
--- a/nixos/modules/config/system-path.nix
+++ b/nixos/modules/config/system-path.nix
@@ -29,18 +29,25 @@ let
       pkgs.xz
       pkgs.less
       pkgs.libcap
-      pkgs.nano
       pkgs.ncurses
       pkgs.netcat
       config.programs.ssh.package
+      pkgs.mkpasswd
       pkgs.procps
       pkgs.su
       pkgs.time
-      pkgs.utillinux
+      pkgs.util-linux
       pkgs.which
       pkgs.zstd
     ];
 
+    defaultPackages = map (pkg: setPrio ((pkg.meta.priority or 5) + 3) pkg)
+      [ pkgs.nano
+        pkgs.perl
+        pkgs.rsync
+        pkgs.strace
+      ];
+
 in
 
 {
@@ -63,6 +70,29 @@ in
         '';
       };
 
+      defaultPackages = mkOption {
+        type = types.listOf types.package;
+        default = defaultPackages;
+        example = literalExample "[]";
+        description = ''
+          Set of default packages that aren't strictly neccessary
+          for a running system, entries can be removed for a more
+          minimal NixOS installation.
+
+          Note: If <package>pkgs.nano</package> is removed from this list,
+          make sure another editor is installed and the
+          <literal>EDITOR</literal> environment variable is set to it.
+          Environment variables can be set using
+          <option>environment.variables</option>.
+
+          Like with systemPackages, packages are installed to
+          <filename>/run/current-system/sw</filename>. They are
+          automatically available to all users, and are
+          automatically updated every time you rebuild the system
+          configuration.
+        '';
+      };
+
       pathsToLink = mkOption {
         type = types.listOf types.str;
         # Note: We need `/lib' to be among `pathsToLink' for NSS modules
@@ -102,7 +132,7 @@ in
 
   config = {
 
-    environment.systemPackages = requiredPackages;
+    environment.systemPackages = requiredPackages ++ config.environment.defaultPackages;
 
     environment.pathsToLink =
       [ "/bin"
@@ -121,6 +151,8 @@ in
         "/share/kservices5"
         "/share/kservicetypes5"
         "/share/kxmlgui5"
+        "/share/systemd"
+        "/share/thumbnailers"
       ];
 
     system.path = pkgs.buildEnv {
diff --git a/nixos/modules/config/update-users-groups.pl b/nixos/modules/config/update-users-groups.pl
index e1c7a46e430..bef08dc4020 100644
--- a/nixos/modules/config/update-users-groups.pl
+++ b/nixos/modules/config/update-users-groups.pl
@@ -16,8 +16,7 @@ my $gidMap = -e $gidMapFile ? decode_json(read_file($gidMapFile)) : {};
 
 sub updateFile {
     my ($path, $contents, $perms) = @_;
-    write_file("$path.tmp", { binmode => ':utf8', perms => $perms // 0644 }, $contents);
-    rename("$path.tmp", $path) or die;
+    write_file($path, { atomic => 1, binmode => ':utf8', perms => $perms // 0644 }, $contents) or die;
 }
 
 
@@ -98,7 +97,7 @@ sub parseGroup {
     return ($f[0], { name => $f[0], password => $f[1], gid => $gid, members => $f[3] });
 }
 
-my %groupsCur = -f "/etc/group" ? map { parseGroup } read_file("/etc/group") : ();
+my %groupsCur = -f "/etc/group" ? map { parseGroup } read_file("/etc/group", { binmode => ":utf8" }) : ();
 
 # Read the current /etc/passwd.
 sub parseUser {
@@ -109,20 +108,19 @@ sub parseUser {
     return ($f[0], { name => $f[0], fakePassword => $f[1], uid => $uid,
         gid => $f[3], description => $f[4], home => $f[5], shell => $f[6] });
 }
-
-my %usersCur = -f "/etc/passwd" ? map { parseUser } read_file("/etc/passwd") : ();
+my %usersCur = -f "/etc/passwd" ? map { parseUser } read_file("/etc/passwd", { binmode => ":utf8" }) : ();
 
 # Read the groups that were created declaratively (i.e. not by groups)
 # in the past. These must be removed if they are no longer in the
 # current spec.
 my $declGroupsFile = "/var/lib/nixos/declarative-groups";
 my %declGroups;
-$declGroups{$_} = 1 foreach split / /, -e $declGroupsFile ? read_file($declGroupsFile) : "";
+$declGroups{$_} = 1 foreach split / /, -e $declGroupsFile ? read_file($declGroupsFile, { binmode => ":utf8" }) : "";
 
 # Idem for the users.
 my $declUsersFile = "/var/lib/nixos/declarative-users";
 my %declUsers;
-$declUsers{$_} = 1 foreach split / /, -e $declUsersFile ? read_file($declUsersFile) : "";
+$declUsers{$_} = 1 foreach split / /, -e $declUsersFile ? read_file($declUsersFile, { binmode => ":utf8" }) : "";
 
 
 # Generate a new /etc/group containing the declared groups.
@@ -175,7 +173,7 @@ foreach my $name (keys %groupsCur) {
 # Rewrite /etc/group. FIXME: acquire lock.
 my @lines = map { join(":", $_->{name}, $_->{password}, $_->{gid}, $_->{members}) . "\n" }
     (sort { $a->{gid} <=> $b->{gid} } values(%groupsOut));
-updateFile($gidMapFile, encode_json($gidMap));
+updateFile($gidMapFile, to_json($gidMap));
 updateFile("/etc/group", \@lines);
 system("nscd --invalidate group");
 
@@ -211,10 +209,11 @@ foreach my $u (@{$spec->{users}}) {
         }
     }
 
-    # Create a home directory.
+    # Ensure home directory incl. ownership and permissions.
     if ($u->{createHome}) {
         make_path($u->{home}, { mode => 0700 }) if ! -e $u->{home};
         chown $u->{uid}, $u->{gid}, $u->{home};
+        chmod 0700, $u->{home};
     }
 
     if (defined $u->{passwordFile}) {
@@ -228,6 +227,15 @@ foreach my $u (@{$spec->{users}}) {
         $u->{hashedPassword} = hashPassword($u->{password});
     }
 
+    if (!defined $u->{shell}) {
+        if (defined $existing) {
+            $u->{shell} = $existing->{shell};
+        } else {
+            warn "warning: no declarative or previous shell for ‘$name’, setting shell to nologin\n";
+            $u->{shell} = "/run/current-system/sw/bin/nologin";
+        }
+    }
+
     $u->{fakePassword} = $existing->{fakePassword} // "x";
     $usersOut{$name} = $u;
 
@@ -251,7 +259,7 @@ foreach my $name (keys %usersCur) {
 # Rewrite /etc/passwd. FIXME: acquire lock.
 @lines = map { join(":", $_->{name}, $_->{fakePassword}, $_->{uid}, $_->{gid}, $_->{description}, $_->{home}, $_->{shell}) . "\n" }
     (sort { $a->{uid} <=> $b->{uid} } (values %usersOut));
-updateFile($uidMapFile, encode_json($uidMap));
+updateFile($uidMapFile, to_json($uidMap));
 updateFile("/etc/passwd", \@lines);
 system("nscd --invalidate passwd");
 
@@ -260,7 +268,7 @@ system("nscd --invalidate passwd");
 my @shadowNew;
 my %shadowSeen;
 
-foreach my $line (-f "/etc/shadow" ? read_file("/etc/shadow") : ()) {
+foreach my $line (-f "/etc/shadow" ? read_file("/etc/shadow", { binmode => ":utf8" }) : ()) {
     chomp $line;
     my ($name, $hashedPassword, @rest) = split(':', $line, -9);
     my $u = $usersOut{$name};;
@@ -280,7 +288,13 @@ foreach my $u (values %usersOut) {
     push @shadowNew, join(":", $u->{name}, $hashedPassword, "1::::::") . "\n";
 }
 
-updateFile("/etc/shadow", \@shadowNew, 0600);
+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: $!";
+}
 
 # 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 56b7af98b61..d5e7745c53f 100644
--- a/nixos/modules/config/users-groups.nix
+++ b/nixos/modules/config/users-groups.nix
@@ -6,6 +6,12 @@ let
   ids = config.ids;
   cfg = config.users;
 
+  isPasswdCompatible = str: !(hasInfix ":" str || hasInfix "\n" str);
+  passwdEntry = type: lib.types.addCheck type isPasswdCompatible // {
+    name = "passwdEntry ${type.name}";
+    description = "${type.description}, not containing newlines or colons";
+  };
+
   # Check whether a password hash will allow login.
   allowsLogin = hash:
     hash == "" # login without password
@@ -35,8 +41,7 @@ let
   '';
 
   hashedPasswordDescription = ''
-    To generate a hashed password install the <literal>mkpasswd</literal>
-    package and run <literal>mkpasswd -m sha-512</literal>.
+    To generate a hashed password run <literal>mkpasswd -m sha-512</literal>.
 
     If set to an empty string (<literal>""</literal>), this user will
     be able to log in without being asked for a password (but not via remote
@@ -55,7 +60,7 @@ let
     options = {
 
       name = mkOption {
-        type = types.str;
+        type = passwdEntry types.str;
         apply = x: assert (builtins.stringLength x < 32 || abort "Username '${x}' is longer than 31 characters which is not allowed!"); x;
         description = ''
           The name of the user account. If undefined, the name of the
@@ -64,7 +69,7 @@ let
       };
 
       description = mkOption {
-        type = types.str;
+        type = passwdEntry types.str;
         default = "";
         example = "Alice Q. User";
         description = ''
@@ -93,6 +98,8 @@ let
           the user's UID is allocated in the range for system users
           (below 500) or in the range for normal users (starting at
           1000).
+          Exactly one of <literal>isNormalUser</literal> and
+          <literal>isSystemUser</literal> must be true.
         '';
       };
 
@@ -108,6 +115,8 @@ let
           <option>useDefaultShell</option> to <literal>true</literal>,
           and <option>isSystemUser</option> to
           <literal>false</literal>.
+          Exactly one of <literal>isNormalUser</literal> and
+          <literal>isSystemUser</literal> must be true.
         '';
       };
 
@@ -125,7 +134,7 @@ let
       };
 
       home = mkOption {
-        type = types.path;
+        type = passwdEntry types.path;
         default = "/var/empty";
         description = "The user's home directory.";
       };
@@ -139,8 +148,22 @@ let
         '';
       };
 
+      pamMount = mkOption {
+        type = with types; attrsOf str;
+        default = {};
+        description = ''
+          Attributes for user's entry in
+          <filename>pam_mount.conf.xml</filename>.
+          Useful attributes might include <code>path</code>,
+          <code>options</code>, <code>fstype</code>, and <code>server</code>.
+          See <link
+          xlink:href="http://pam-mount.sourceforge.net/pam_mount.conf.5.html" />
+          for more information.
+        '';
+      };
+
       shell = mkOption {
-        type = types.either types.shellPackage types.path;
+        type = types.nullOr (types.either types.shellPackage (passwdEntry types.path));
         default = pkgs.shadow;
         defaultText = "pkgs.shadow";
         example = literalExample "pkgs.bashInteractive";
@@ -185,10 +208,8 @@ let
         type = types.bool;
         default = false;
         description = ''
-          If true, the home directory will be created automatically. If this
-          option is true and the home directory already exists but is not
-          owned by the user, directory owner and group will be changed to
-          match the user.
+          Whether to create the home directory and ensure ownership as well as
+          permissions to match the user.
         '';
       };
 
@@ -202,7 +223,7 @@ let
       };
 
       hashedPassword = mkOption {
-        type = with types; nullOr str;
+        type = with types; nullOr (passwdEntry str);
         default = null;
         description = ''
           Specifies the hashed password for the user.
@@ -236,7 +257,7 @@ let
       };
 
       initialHashedPassword = mkOption {
-        type = with types; nullOr str;
+        type = with types; nullOr (passwdEntry str);
         default = null;
         description = ''
           Specifies the initial hashed password for the user, i.e. the
@@ -308,7 +329,7 @@ let
     options = {
 
       name = mkOption {
-        type = types.str;
+        type = passwdEntry types.str;
         description = ''
           The name of the group. If undefined, the name of the attribute set
           will be used.
@@ -325,7 +346,7 @@ let
       };
 
       members = mkOption {
-        type = with types; listOf str;
+        type = with types; listOf (passwdEntry str);
         default = [];
         description = ''
           The user names of the group members, added to the
@@ -353,7 +374,7 @@ let
       count = mkOption {
         type = types.int;
         default = 1;
-        description = ''Count of subordinate user ids'';
+        description = "Count of subordinate user ids";
       };
     };
   };
@@ -370,12 +391,12 @@ let
       count = mkOption {
         type = types.int;
         default = 1;
-        description = ''Count of subordinate group ids'';
+        description = "Count of subordinate group ids";
       };
     };
   };
 
-  idsAreUnique = set: idAttr: !(fold (name: args@{ dup, acc }:
+  idsAreUnique = set: idAttr: !(foldr (name: args@{ dup, acc }:
     let
       id = builtins.toString (builtins.getAttr idAttr (builtins.getAttr name set));
       exists = builtins.hasAttr id acc;
@@ -463,7 +484,7 @@ in {
 
     users.users = mkOption {
       default = {};
-      type = with types; loaOf (submodule userOpts);
+      type = with types; attrsOf (submodule userOpts);
       example = {
         alice = {
           uid = 1234;
@@ -487,7 +508,7 @@ in {
         { students.gid = 1001;
           hackers = { };
         };
-      type = with types; loaOf (submodule groupOpts);
+      type = with types; attrsOf (submodule groupOpts);
       description = ''
         Additional groups to be created automatically by the system.
       '';
@@ -510,6 +531,7 @@ in {
       };
       nobody = {
         uid = ids.uids.nobody;
+        isSystemUser = true;
         description = "Unprivileged account (don't use!)";
         group = "nogroup";
       };
@@ -537,6 +559,7 @@ in {
       input.gid = ids.gids.input;
       kvm.gid = ids.gids.kvm;
       render.gid = ids.gids.render;
+      shadow.gid = ids.gids.shadow;
     };
 
     system.activationScripts.users = stringAfter [ "stdio" ]
@@ -544,10 +567,8 @@ in {
         install -m 0700 -d /root
         install -m 0755 -d /home
 
-        ${pkgs.perl}/bin/perl -w \
-          -I${pkgs.perlPackages.FileSlurp}/${pkgs.perl.libPrefix} \
-          -I${pkgs.perlPackages.JSON}/${pkgs.perl.libPrefix} \
-          ${./update-users-groups.pl} ${spec}
+        ${pkgs.perl.withPackages (p: [ p.FileSlurp p.JSON ])}/bin/perl \
+        -w ${./update-users-groups.pl} ${spec}
       '';
 
     # for backwards compatibility
@@ -556,7 +577,7 @@ in {
     # Install all the user shells
     environment.systemPackages = systemShells;
 
-    environment.etc = (mapAttrs' (name: { packages, ... }: {
+    environment.etc = (mapAttrs' (_: { packages, name, ... }: {
       name = "profiles/per-user/${name}";
       value.source = pkgs.buildEnv {
         name = "user-environment";
@@ -581,8 +602,8 @@ in {
         # password or an SSH authorized key. Privileged accounts are
         # root and users in the wheel group.
         assertion = !cfg.mutableUsers ->
-          any id ((mapAttrsToList (name: cfg:
-            (name == "root"
+          any id ((mapAttrsToList (_: cfg:
+            (cfg.name == "root"
              || cfg.group == "wheel"
              || elem "wheel" cfg.extraGroups)
             &&
@@ -598,21 +619,32 @@ in {
           Neither the root account nor any wheel user has a password or SSH authorized key.
           You must set one to prevent being locked out of your system.'';
       }
-    ] ++ flip mapAttrsToList cfg.users (name: user:
-      {
+    ] ++ flatten (flip mapAttrsToList cfg.users (name: user:
+      [
+        {
         assertion = (user.hashedPassword != null)
-                    -> (builtins.match ".*:.*" user.hashedPassword == null);
+        -> (builtins.match ".*:.*" user.hashedPassword == null);
         message = ''
-          The password hash of user "${name}" contains a ":" character.
-          This is invalid and would break the login system because the fields
-          of /etc/shadow (file where hashes are stored) are colon-separated.
-          Please check the value of option `users.users."${name}".hashedPassword`.'';
-      }
-    );
+            The password hash of user "${user.name}" contains a ":" character.
+            This is invalid and would break the login system because the fields
+            of /etc/shadow (file where hashes are stored) are colon-separated.
+            Please check the value of option `users.users."${user.name}".hashedPassword`.'';
+          }
+          {
+            assertion = let
+              xor = a: b: a && !b || b && !a;
+              isEffectivelySystemUser = user.isSystemUser || (user.uid != null && user.uid < 500);
+            in xor isEffectivelySystemUser user.isNormalUser;
+            message = ''
+              Exactly one of users.users.${user.name}.isSystemUser and users.users.${user.name}.isNormalUser must be set.
+            '';
+          }
+        ]
+    ));
 
     warnings =
       builtins.filter (x: x != null) (
-        flip mapAttrsToList cfg.users (name: user:
+        flip mapAttrsToList cfg.users (_: user:
         # This regex matches a subset of the Modular Crypto Format (MCF)[1]
         # informal standard. Since this depends largely on the OS or the
         # specific implementation of crypt(3) we only support the (sane)
@@ -635,9 +667,9 @@ in {
             && user.hashedPassword != ""  # login without password
             && builtins.match mcf user.hashedPassword == null)
         then ''
-          The password hash of user "${name}" may be invalid. You must set a
+          The password hash of user "${user.name}" may be invalid. You must set a
           valid hash or the user will be locked out of their account. Please
-          check the value of option `users.users."${name}".hashedPassword`.''
+          check the value of option `users.users."${user.name}".hashedPassword`.''
         else null
       ));
 
diff --git a/nixos/modules/config/xdg/portal.nix b/nixos/modules/config/xdg/portal.nix
index 3c7cd729c60..80ec3126ca5 100644
--- a/nixos/modules/config/xdg/portal.nix
+++ b/nixos/modules/config/xdg/portal.nix
@@ -62,7 +62,7 @@ with lib;
       services.dbus.packages  = packages;
       systemd.packages = packages;
 
-      environment.variables = {
+      environment.sessionVariables = {
         GTK_USE_PORTAL = mkIf cfg.gtkUsePortal "1";
         XDG_DESKTOP_PORTAL_DIR = "${joinedPortals}/share/xdg-desktop-portal/portals";
       };
diff --git a/nixos/modules/config/xdg/portals/wlr.nix b/nixos/modules/config/xdg/portals/wlr.nix
new file mode 100644
index 00000000000..55baab0026b
--- /dev/null
+++ b/nixos/modules/config/xdg/portals/wlr.nix
@@ -0,0 +1,67 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+  cfg = config.xdg.portal.wlr;
+  package = pkgs.xdg-desktop-portal-wlr;
+  settingsFormat = pkgs.formats.ini { };
+  configFile = settingsFormat.generate "xdg-desktop-portal-wlr.ini" cfg.settings;
+in
+{
+  meta = {
+    maintainers = with maintainers; [ minijackson ];
+  };
+
+  options.xdg.portal.wlr = {
+    enable = mkEnableOption ''
+      desktop portal for wlroots-based desktops
+
+      This will add the <package>xdg-desktop-portal-wlr</package> package into
+      the <option>xdg.portal.extraPortals</option> option, and provide the
+      configuration file
+    '';
+
+    settings = mkOption {
+      description = ''
+        Configuration for <package>xdg-desktop-portal-wlr</package>.
+
+        See <literal>xdg-desktop-portal-wlr(5)</literal> for supported
+        values.
+      '';
+
+      type = types.submodule {
+        freeformType = settingsFormat.type;
+      };
+
+      default = { };
+
+      # Example taken from the manpage
+      example = literalExample ''
+        {
+          screencast = {
+            output_name = "HDMI-A-1";
+            max_fps = 30;
+            exec_before = "disable_notifications.sh";
+            exec_after = "enable_notifications.sh";
+            chooser_type = "simple";
+            chooser_cmd = "''${pkgs.slurp}/bin/slurp -f %o -or";
+          };
+        }
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+    xdg.portal = {
+      enable = true;
+      extraPortals = [ package ];
+    };
+
+    systemd.user.services.xdg-desktop-portal-wlr.serviceConfig.ExecStart = [
+      # Empty ExecStart value to override the field
+      ""
+      "${package}/libexec/xdg-desktop-portal-wlr --config=${configFile}"
+    ];
+  };
+}
diff --git a/nixos/modules/config/zram.nix b/nixos/modules/config/zram.nix
index 5e9870bf6b1..1f513b7e4da 100644
--- a/nixos/modules/config/zram.nix
+++ b/nixos/modules/config/zram.nix
@@ -80,6 +80,15 @@ in
         '';
       };
 
+      memoryMax = mkOption {
+        default = null;
+        type = with types; nullOr int;
+        description = ''
+          Maximum total amount of memory (in bytes) that can be used by the zram
+          swap devices.
+        '';
+      };
+
       priority = mkOption {
         default = 5;
         type = types.int;
@@ -146,11 +155,16 @@ in
 
               # Calculate memory to use for zram
               mem=$(${pkgs.gawk}/bin/awk '/MemTotal: / {
-                  print int($2*${toString cfg.memoryPercent}/100.0/${toString devicesCount}*1024)
+                  value=int($2*${toString cfg.memoryPercent}/100.0/${toString devicesCount}*1024);
+                    ${lib.optionalString (cfg.memoryMax != null) ''
+                      memory_max=int(${toString cfg.memoryMax}/${toString devicesCount});
+                      if (value > memory_max) { value = memory_max }
+                    ''}
+                  print value
               }' /proc/meminfo)
 
-              ${pkgs.utillinux}/sbin/zramctl --size $mem --algorithm ${cfg.algorithm} /dev/${dev}
-              ${pkgs.utillinux}/sbin/mkswap /dev/${dev}
+              ${pkgs.util-linux}/sbin/zramctl --size $mem --algorithm ${cfg.algorithm} /dev/${dev}
+              ${pkgs.util-linux}/sbin/mkswap /dev/${dev}
             '';
             restartIfChanged = false;
           };