summary refs log tree commit diff
path: root/nixos/modules/misc
diff options
context:
space:
mode:
Diffstat (limited to 'nixos/modules/misc')
-rw-r--r--nixos/modules/misc/assertions.nix34
-rw-r--r--nixos/modules/misc/crashdump.nix76
-rw-r--r--nixos/modules/misc/documentation.nix346
-rw-r--r--nixos/modules/misc/extra-arguments.nix7
-rw-r--r--nixos/modules/misc/ids.nix677
-rw-r--r--nixos/modules/misc/label.nix72
-rw-r--r--nixos/modules/misc/lib.nix15
-rw-r--r--nixos/modules/misc/locate.nix313
-rw-r--r--nixos/modules/misc/man-db.nix73
-rw-r--r--nixos/modules/misc/mandoc.nix61
-rw-r--r--nixos/modules/misc/meta.nix76
-rw-r--r--nixos/modules/misc/nixops-autoluks.nix43
-rw-r--r--nixos/modules/misc/nixpkgs.nix259
-rw-r--r--nixos/modules/misc/nixpkgs/test.nix8
-rw-r--r--nixos/modules/misc/passthru.nix16
-rw-r--r--nixos/modules/misc/version.nix141
-rw-r--r--nixos/modules/misc/wordlist.nix59
17 files changed, 2276 insertions, 0 deletions
diff --git a/nixos/modules/misc/assertions.nix b/nixos/modules/misc/assertions.nix
new file mode 100644
index 00000000000..550b3ac97f6
--- /dev/null
+++ b/nixos/modules/misc/assertions.nix
@@ -0,0 +1,34 @@
+{ lib, ... }:
+
+with lib;
+
+{
+
+  options = {
+
+    assertions = mkOption {
+      type = types.listOf types.unspecified;
+      internal = true;
+      default = [];
+      example = [ { assertion = false; message = "you can't enable this for that reason"; } ];
+      description = ''
+        This option allows modules to express conditions that must
+        hold for the evaluation of the system configuration to
+        succeed, along with associated error messages for the user.
+      '';
+    };
+
+    warnings = mkOption {
+      internal = true;
+      default = [];
+      type = types.listOf types.str;
+      example = [ "The `foo' service is deprecated and will go away soon!" ];
+      description = ''
+        This option allows modules to show warnings to users during
+        the evaluation of the system configuration.
+      '';
+    };
+
+  };
+  # impl of assertions is in <nixpkgs/nixos/modules/system/activation/top-level.nix>
+}
diff --git a/nixos/modules/misc/crashdump.nix b/nixos/modules/misc/crashdump.nix
new file mode 100644
index 00000000000..b0f75d9caaa
--- /dev/null
+++ b/nixos/modules/misc/crashdump.nix
@@ -0,0 +1,76 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  crashdump = config.boot.crashDump;
+
+  kernelParams = concatStringsSep " " crashdump.kernelParams;
+
+in
+###### interface
+{
+  options = {
+    boot = {
+      crashDump = {
+        enable = mkOption {
+          type = types.bool;
+          default = false;
+          description = ''
+            If enabled, NixOS will set up a kernel that will
+            boot on crash, and leave the user in systemd rescue
+            to be able to save the crashed kernel dump at
+            /proc/vmcore.
+            It also activates the NMI watchdog.
+          '';
+        };
+        reservedMemory = mkOption {
+          default = "128M";
+          type = types.str;
+          description = ''
+            The amount of memory reserved for the crashdump kernel.
+            If you choose a too high value, dmesg will mention
+            "crashkernel reservation failed".
+          '';
+        };
+        kernelParams = mkOption {
+          type = types.listOf types.str;
+          default = [ "1" "boot.shell_on_fail" ];
+          description = ''
+            Parameters that will be passed to the kernel kexec-ed on crash.
+          '';
+        };
+      };
+    };
+  };
+
+###### implementation
+
+  config = mkIf crashdump.enable {
+    boot = {
+      postBootCommands = ''
+        echo "loading crashdump kernel...";
+        ${pkgs.kexec-tools}/sbin/kexec -p /run/current-system/kernel \
+        --initrd=/run/current-system/initrd \
+        --reset-vga --console-vga \
+        --command-line="init=$(readlink -f /run/current-system/init) irqpoll maxcpus=1 reset_devices ${kernelParams}"
+      '';
+      kernelParams = [
+       "crashkernel=${crashdump.reservedMemory}"
+       "nmi_watchdog=panic"
+       "softlockup_panic=1"
+      ];
+      kernelPatches = [ {
+        name = "crashdump-config";
+        patch = null;
+        extraConfig = ''
+                CRASH_DUMP y
+                DEBUG_INFO y
+                PROC_VMCORE y
+                LOCKUP_DETECTOR y
+                HARDLOCKUP_DETECTOR y
+              '';
+        } ];
+    };
+  };
+}
diff --git a/nixos/modules/misc/documentation.nix b/nixos/modules/misc/documentation.nix
new file mode 100644
index 00000000000..9304c307af2
--- /dev/null
+++ b/nixos/modules/misc/documentation.nix
@@ -0,0 +1,346 @@
+{ config, options, lib, pkgs, utils, modules, baseModules, extraModules, modulesPath, ... }:
+
+with lib;
+
+let
+
+  cfg = config.documentation;
+  allOpts = options;
+
+  /* Modules for which to show options even when not imported. */
+  extraDocModules = [ ../virtualisation/qemu-vm.nix ];
+
+  canCacheDocs = m:
+    let
+      f = import m;
+      instance = f (mapAttrs (n: _: abort "evaluating ${n} for `meta` failed") (functionArgs f));
+    in
+      cfg.nixos.options.splitBuild
+        && builtins.isPath m
+        && isFunction f
+        && instance ? options
+        && instance.meta.buildDocsInSandbox or true;
+
+  docModules =
+    let
+      p = partition canCacheDocs (baseModules ++ extraDocModules);
+    in
+      {
+        lazy = p.right;
+        eager = p.wrong ++ optionals cfg.nixos.includeAllModules (extraModules ++ modules);
+      };
+
+  manual = import ../../doc/manual rec {
+    inherit pkgs config;
+    version = config.system.nixos.release;
+    revision = "release-${version}";
+    extraSources = cfg.nixos.extraModuleSources;
+    options =
+      let
+        scrubbedEval = evalModules {
+          modules = [ {
+            _module.check = false;
+          } ] ++ docModules.eager;
+          specialArgs = {
+            pkgs = scrubDerivations "pkgs" pkgs;
+            # allow access to arbitrary options for eager modules, eg for getting
+            # option types from lazy modules
+            options = allOpts;
+            inherit modulesPath utils;
+          };
+        };
+        scrubDerivations = namePrefix: pkgSet: mapAttrs
+          (name: value:
+            let wholeName = "${namePrefix}.${name}"; in
+            if isAttrs value then
+              scrubDerivations wholeName value
+              // (optionalAttrs (isDerivation value) { outPath = "\${${wholeName}}"; })
+            else value
+          )
+          pkgSet;
+      in scrubbedEval.options;
+    baseOptionsJSON =
+      let
+        filter =
+          builtins.filterSource
+            (n: t:
+              (t == "directory" -> baseNameOf n != "tests")
+              && (t == "file" -> hasSuffix ".nix" n)
+            );
+      in
+        pkgs.runCommand "lazy-options.json" {
+          libPath = filter "${toString pkgs.path}/lib";
+          pkgsLibPath = filter "${toString pkgs.path}/pkgs/pkgs-lib";
+          nixosPath = filter "${toString pkgs.path}/nixos";
+          modules = map (p: ''"${removePrefix "${modulesPath}/" (toString p)}"'') docModules.lazy;
+        } ''
+          export NIX_STORE_DIR=$TMPDIR/store
+          export NIX_STATE_DIR=$TMPDIR/state
+          ${pkgs.buildPackages.nix}/bin/nix-instantiate \
+            --show-trace \
+            --eval --json --strict \
+            --argstr libPath "$libPath" \
+            --argstr pkgsLibPath "$pkgsLibPath" \
+            --argstr nixosPath "$nixosPath" \
+            --arg modules "[ $modules ]" \
+            --argstr stateVersion "${options.system.stateVersion.default}" \
+            --argstr release "${config.system.nixos.release}" \
+            $nixosPath/lib/eval-cacheable-options.nix > $out \
+            || {
+              echo -en "\e[1;31m"
+              echo 'Cacheable portion of option doc build failed.'
+              echo 'Usually this means that an option attribute that ends up in documentation (eg' \
+                '`default` or `description`) depends on the restricted module arguments' \
+                '`config` or `pkgs`.'
+              echo
+              echo 'Rebuild your configuration with `--show-trace` to find the offending' \
+                'location. Remove the references to restricted arguments (eg by escaping' \
+                'their antiquotations or adding a `defaultText`) or disable the sandboxed' \
+                'build for the failing module by setting `meta.buildDocsInSandbox = false`.'
+              echo -en "\e[0m"
+              exit 1
+            } >&2
+        '';
+    inherit (cfg.nixos.options) warningsAreErrors;
+  };
+
+
+  nixos-help = let
+    helpScript = pkgs.writeShellScriptBin "nixos-help" ''
+      # Finds first executable browser in a colon-separated list.
+      # (see how xdg-open defines BROWSER)
+      browser="$(
+        IFS=: ; for b in $BROWSER; do
+          [ -n "$(type -P "$b" || true)" ] && echo "$b" && break
+        done
+      )"
+      if [ -z "$browser" ]; then
+        browser="$(type -P xdg-open || true)"
+        if [ -z "$browser" ]; then
+          browser="${pkgs.w3m-nographics}/bin/w3m"
+        fi
+      fi
+      exec "$browser" ${manual.manualHTMLIndex}
+    '';
+
+    desktopItem = pkgs.makeDesktopItem {
+      name = "nixos-manual";
+      desktopName = "NixOS Manual";
+      genericName = "View NixOS documentation in a web browser";
+      icon = "nix-snowflake";
+      exec = "nixos-help";
+      categories = ["System"];
+    };
+
+    in pkgs.symlinkJoin {
+      name = "nixos-help";
+      paths = [
+        helpScript
+        desktopItem
+      ];
+    };
+
+in
+
+{
+  imports = [
+    (mkRenamedOptionModule [ "programs" "info" "enable" ] [ "documentation" "info" "enable" ])
+    (mkRenamedOptionModule [ "programs" "man"  "enable" ] [ "documentation" "man"  "enable" ])
+    (mkRenamedOptionModule [ "services" "nixosManual" "enable" ] [ "documentation" "nixos" "enable" ])
+  ];
+
+  options = {
+
+    documentation = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Whether to install documentation of packages from
+          <option>environment.systemPackages</option> into the generated system path.
+
+          See "Multiple-output packages" chapter in the nixpkgs manual for more info.
+        '';
+        # which is at ../../../doc/multiple-output.chapter.md
+      };
+
+      man.enable = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Whether to install manual pages.
+          This also includes <literal>man</literal> outputs.
+        '';
+      };
+
+      man.generateCaches = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to generate the manual page index caches.
+          This allows searching for a page or
+          keyword using utilities like
+          <citerefentry>
+            <refentrytitle>apropos</refentrytitle>
+            <manvolnum>1</manvolnum>
+          </citerefentry>
+          and the <literal>-k</literal> option of
+          <citerefentry>
+            <refentrytitle>man</refentrytitle>
+            <manvolnum>1</manvolnum>
+          </citerefentry>.
+        '';
+      };
+
+      info.enable = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Whether to install info pages and the <command>info</command> command.
+          This also includes "info" outputs.
+        '';
+      };
+
+      doc.enable = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Whether to install documentation distributed in packages' <literal>/share/doc</literal>.
+          Usually plain text and/or HTML.
+          This also includes "doc" outputs.
+        '';
+      };
+
+      dev.enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to install documentation targeted at developers.
+          <itemizedlist>
+          <listitem><para>This includes man pages targeted at developers if <option>documentation.man.enable</option> is
+                    set (this also includes "devman" outputs).</para></listitem>
+          <listitem><para>This includes info pages targeted at developers if <option>documentation.info.enable</option>
+                    is set (this also includes "devinfo" outputs).</para></listitem>
+          <listitem><para>This includes other pages targeted at developers if <option>documentation.doc.enable</option>
+                    is set (this also includes "devdoc" outputs).</para></listitem>
+          </itemizedlist>
+        '';
+      };
+
+      nixos.enable = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Whether to install NixOS's own documentation.
+          <itemizedlist>
+          <listitem><para>This includes man pages like
+                    <citerefentry><refentrytitle>configuration.nix</refentrytitle>
+                    <manvolnum>5</manvolnum></citerefentry> if <option>documentation.man.enable</option> is
+                    set.</para></listitem>
+          <listitem><para>This includes the HTML manual and the <command>nixos-help</command> command if
+                    <option>documentation.doc.enable</option> is set.</para></listitem>
+          </itemizedlist>
+        '';
+      };
+
+      nixos.options.splitBuild = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Whether to split the option docs build into a cacheable and an uncacheable part.
+          Splitting the build can substantially decrease the amount of time needed to build
+          the manual, but some user modules may be incompatible with this splitting.
+        '';
+      };
+
+      nixos.options.warningsAreErrors = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Treat warning emitted during the option documentation build (eg for missing option
+          descriptions) as errors.
+        '';
+      };
+
+      nixos.includeAllModules = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether the generated NixOS's documentation should include documentation for all
+          the options from all the NixOS modules included in the current
+          <literal>configuration.nix</literal>. Disabling this will make the manual
+          generator to ignore options defined outside of <literal>baseModules</literal>.
+        '';
+      };
+
+      nixos.extraModuleSources = mkOption {
+        type = types.listOf (types.either types.path types.str);
+        default = [ ];
+        description = ''
+          Which extra NixOS module paths the generated NixOS's documentation should strip
+          from options.
+        '';
+        example = literalExpression ''
+          # e.g. with options from modules in ''${pkgs.customModules}/nix:
+          [ pkgs.customModules ]
+        '';
+      };
+
+    };
+
+  };
+
+  config = mkIf cfg.enable (mkMerge [
+    {
+      assertions = [
+        {
+          assertion = !(cfg.man.man-db.enable && cfg.man.mandoc.enable);
+          message = ''
+            man-db and mandoc can't be used as the default man page viewer at the same time!
+          '';
+        }
+      ];
+    }
+
+    # The actual implementation for this lives in man-db.nix or mandoc.nix,
+    # depending on which backend is active.
+    (mkIf cfg.man.enable {
+      environment.pathsToLink = [ "/share/man" ];
+      environment.extraOutputsToInstall = [ "man" ] ++ optional cfg.dev.enable "devman";
+    })
+
+    (mkIf cfg.info.enable {
+      environment.systemPackages = [ pkgs.texinfoInteractive ];
+      environment.pathsToLink = [ "/share/info" ];
+      environment.extraOutputsToInstall = [ "info" ] ++ optional cfg.dev.enable "devinfo";
+      environment.extraSetup = ''
+        if [ -w $out/share/info ]; then
+          shopt -s nullglob
+          for i in $out/share/info/*.info $out/share/info/*.info.gz; do
+              ${pkgs.buildPackages.texinfo}/bin/install-info $i $out/share/info/dir
+          done
+        fi
+      '';
+    })
+
+    (mkIf cfg.doc.enable {
+      environment.pathsToLink = [ "/share/doc" ];
+      environment.extraOutputsToInstall = [ "doc" ] ++ optional cfg.dev.enable "devdoc";
+    })
+
+    (mkIf cfg.nixos.enable {
+      system.build.manual = manual;
+
+      environment.systemPackages = []
+        ++ optional cfg.man.enable manual.manpages
+        ++ optionals cfg.doc.enable [ manual.manualHTML nixos-help ];
+
+      services.getty.helpLine = mkIf cfg.doc.enable (
+          "\nRun 'nixos-help' for the NixOS manual."
+      );
+    })
+
+  ]);
+
+}
diff --git a/nixos/modules/misc/extra-arguments.nix b/nixos/modules/misc/extra-arguments.nix
new file mode 100644
index 00000000000..48891b44049
--- /dev/null
+++ b/nixos/modules/misc/extra-arguments.nix
@@ -0,0 +1,7 @@
+{ lib, config, pkgs, ... }:
+
+{
+  _module.args = {
+    utils = import ../../lib/utils.nix { inherit lib config pkgs; };
+  };
+}
diff --git a/nixos/modules/misc/ids.nix b/nixos/modules/misc/ids.nix
new file mode 100644
index 00000000000..7d1faa50f4b
--- /dev/null
+++ b/nixos/modules/misc/ids.nix
@@ -0,0 +1,677 @@
+# This module defines the global list of uids and gids.  We keep a
+# central list to prevent id collisions.
+
+# IMPORTANT!
+# We only add static uids and gids for services where it is not feasible
+# to change uids/gids on service start, in example a service with a lot of
+# files. Please also check if the service is applicable for systemd's
+# DynamicUser option and does not need a uid/gid allocation at all.
+# Systemd can also change ownership of service directories using the
+# RuntimeDirectory/StateDirectory options.
+
+{ lib, ... }:
+
+let
+  inherit (lib) types;
+in
+{
+  options = {
+
+    ids.uids = lib.mkOption {
+      internal = true;
+      description = ''
+        The user IDs used in NixOS.
+      '';
+      type = types.attrsOf types.int;
+    };
+
+    ids.gids = lib.mkOption {
+      internal = true;
+      description = ''
+        The group IDs used in NixOS.
+      '';
+      type = types.attrsOf types.int;
+    };
+
+  };
+
+
+  config = {
+
+    ids.uids = {
+      root = 0;
+      #wheel = 1; # unused
+      #kmem = 2; # unused
+      #tty = 3; # unused
+      messagebus = 4; # D-Bus
+      haldaemon = 5;
+      #disk = 6; # unused
+      #vsftpd = 7; # dynamically allocated ass of 2021-09-14
+      ftp = 8;
+      # bitlbee = 9; # removed 2021-10-05 #139765
+      #avahi = 10; # removed 2019-05-22
+      nagios = 11;
+      atd = 12;
+      postfix = 13;
+      #postdrop = 14; # unused
+      dovecot = 15;
+      tomcat = 16;
+      #audio = 17; # unused
+      #floppy = 18; # unused
+      uucp = 19;
+      #lp = 20; # unused
+      #proc = 21; # unused
+      pulseaudio = 22; # must match `pulseaudio' GID
+      gpsd = 23;
+      #cdrom = 24; # unused
+      #tape = 25; # unused
+      #video = 26; # unused
+      #dialout = 27; # unused
+      polkituser = 28;
+      #utmp = 29; # unused
+      # ddclient = 30; # converted to DynamicUser = true
+      davfs2 = 31;
+      disnix = 33;
+      osgi = 34;
+      tor = 35;
+      cups = 36;
+      foldingathome = 37;
+      sabnzbd = 38;
+      #kdm = 39; # dropped in 17.03
+      #ghostone = 40; # dropped in 18.03
+      git = 41;
+      #fourstore = 42; # dropped in 20.03
+      #fourstorehttp = 43; # dropped in 20.03
+      #virtuoso = 44;  dropped module
+      #rtkit = 45; # dynamically allocated 2021-09-03
+      dovecot2 = 46;
+      dovenull2 = 47;
+      prayer = 49;
+      mpd = 50;
+      clamav = 51;
+      #fprot = 52; # unused
+      # bind = 53; #dynamically allocated as of 2021-09-03
+      wwwrun = 54;
+      #adm = 55; # unused
+      spamd = 56;
+      #networkmanager = 57; # unused
+      nslcd = 58;
+      scanner = 59;
+      nginx = 60;
+      chrony = 61;
+      #systemd-journal = 62; # unused
+      smtpd = 63;
+      smtpq = 64;
+      supybot = 65;
+      iodined = 66;
+      #libvirtd = 67; # unused
+      graphite = 68;
+      #statsd = 69; # removed 2018-11-14
+      transmission = 70;
+      postgres = 71;
+      #vboxusers = 72; # unused
+      #vboxsf = 73; # unused
+      smbguest = 74;  # unused
+      varnish = 75;
+      datadog = 76;
+      lighttpd = 77;
+      lightdm = 78;
+      freenet = 79;
+      ircd = 80;
+      bacula = 81;
+      #almir = 82; # removed 2018-03-25, the almir package was removed in 30291227f2411abaca097773eedb49b8f259e297 during 2017-08
+      deluge = 83;
+      mysql = 84;
+      rabbitmq = 85;
+      activemq = 86;
+      gnunet = 87;
+      oidentd = 88;
+      quassel = 89;
+      amule = 90;
+      minidlna = 91;
+      elasticsearch = 92;
+      tcpcryptd = 93; # tcpcryptd uses a hard-coded uid. We patch it in Nixpkgs to match this choice.
+      firebird = 95;
+      #keys = 96; # unused
+      #haproxy = 97; # dynamically allocated as of 2020-03-11
+      #mongodb = 98; #dynamically allocated as of 2021-09-03
+      #openldap = 99; # dynamically allocated as of PR#94610
+      #users = 100; # unused
+      # cgminer = 101; #dynamically allocated as of 2021-09-17
+      munin = 102;
+      #logcheck = 103; #dynamically allocated as of 2021-09-17
+      #nix-ssh = 104; #dynamically allocated as of 2021-09-03
+      dictd = 105;
+      couchdb = 106;
+      #searx = 107; # dynamically allocated as of 2020-10-27
+      #kippo = 108; # removed 2021-10-07, the kippo package was removed in 1b213f321cdbfcf868b96fd9959c24207ce1b66a during 2021-04
+      jenkins = 109;
+      systemd-journal-gateway = 110;
+      #notbit = 111; # unused
+      aerospike = 111;
+      #ngircd = 112; #dynamically allocated as of 2021-09-03
+      #btsync = 113; # unused
+      #minecraft = 114; #dynamically allocated as of 2021-09-03
+      vault = 115;
+      # rippled = 116; #dynamically allocated as of 2021-09-18
+      murmur = 117;
+      foundationdb = 118;
+      newrelic = 119;
+      starbound = 120;
+      hydra = 122;
+      spiped = 123;
+      teamspeak = 124;
+      influxdb = 125;
+      nsd = 126;
+      gitolite = 127;
+      znc = 128;
+      polipo = 129;
+      mopidy = 130;
+      #docker = 131; # unused
+      gdm = 132;
+      #dhcpd = 133; # dynamically allocated as of 2021-09-03
+      siproxd = 134;
+      mlmmj = 135;
+      #neo4j = 136;# dynamically allocated as of 2021-09-03
+      riemann = 137;
+      riemanndash = 138;
+      #radvd = 139;# dynamically allocated as of 2021-09-03
+      #zookeeper = 140;# dynamically allocated as of 2021-09-03
+      #dnsmasq = 141;# dynamically allocated as of 2021-09-03
+      #uhub = 142; # unused
+      yandexdisk = 143;
+      mxisd = 144; # was once collectd
+      #consul = 145;# dynamically allocated as of 2021-09-03
+      #mailpile = 146; # removed 2022-01-12
+      redmine = 147;
+      #seeks = 148; # removed 2020-06-21
+      prosody = 149;
+      i2pd = 150;
+      systemd-coredump = 151;
+      systemd-network = 152;
+      systemd-resolve = 153;
+      systemd-timesync = 154;
+      liquidsoap = 155;
+      #etcd = 156;# dynamically allocated as of 2021-09-03
+      hbase = 158;
+      opentsdb = 159;
+      scollector = 160;
+      bosun = 161;
+      kubernetes = 162;
+      peerflix = 163;
+      #chronos = 164; # removed 2020-08-15
+      gitlab = 165;
+      # tox-bootstrapd = 166; removed 2021-09-15
+      cadvisor = 167;
+      nylon = 168;
+      #apache-kafka = 169;# dynamically allocated as of 2021-09-03
+      #panamax = 170; # unused
+      exim = 172;
+      #fleet = 173; # unused
+      #input = 174; # unused
+      sddm = 175;
+      #tss = 176; # dynamically allocated as of 2021-09-17
+      #memcached = 177; removed 2018-01-03
+      #ntp = 179; # dynamically allocated as of 2021-09-17
+      zabbix = 180;
+      #redis = 181; removed 2018-01-03
+      #unifi = 183; dynamically allocated as of 2021-09-17
+      uptimed = 184;
+      #zope2 = 185; # dynamically allocated as of 2021-09-18
+      #ripple-data-api = 186; dynamically allocated as of 2021-09-17
+      mediatomb = 187;
+      #rdnssd = 188; #dynamically allocated as of 2021-09-18
+      ihaskell = 189;
+      i2p = 190;
+      lambdabot = 191;
+      asterisk = 192;
+      plex = 193;
+      plexpy = 195;
+      grafana = 196;
+      skydns = 197;
+      # ripple-rest = 198; # unused, removed 2017-08-12
+      # nix-serve = 199; # unused, removed 2020-12-12
+      #tvheadend = 200; # dynamically allocated as of 2021-09-18
+      uwsgi = 201;
+      gitit = 202;
+      riemanntools = 203;
+      subsonic = 204;
+      riak = 205;
+      #shout = 206; # dynamically allocated as of 2021-09-18
+      gateone = 207;
+      namecoin = 208;
+      #lxd = 210; # unused
+      #kibana = 211;# dynamically allocated as of 2021-09-03
+      xtreemfs = 212;
+      calibre-server = 213;
+      #heapster = 214; #dynamically allocated as of 2021-09-17
+      bepasty = 215;
+      # pumpio = 216; # unused, removed 2018-02-24
+      nm-openvpn = 217;
+      # mathics = 218; # unused, removed 2020-08-15
+      ejabberd = 219;
+      postsrsd = 220;
+      opendkim = 221;
+      dspam = 222;
+      # gale = 223; removed 2021-06-10
+      matrix-synapse = 224;
+      rspamd = 225;
+      # rmilter = 226; # unused, removed 2019-08-22
+      cfdyndns = 227;
+      # gammu-smsd = 228; #dynamically allocated as of 2021-09-17
+      pdnsd = 229;
+      octoprint = 230;
+      avahi-autoipd = 231;
+      # nntp-proxy = 232; #dynamically allocated as of 2021-09-17
+      mjpg-streamer = 233;
+      #radicale = 234;# dynamically allocated as of 2021-09-03
+      hydra-queue-runner = 235;
+      hydra-www = 236;
+      syncthing = 237;
+      caddy = 239;
+      taskd = 240;
+      # factorio = 241; # DynamicUser = true
+      # emby = 242; # unusued, removed 2019-05-01
+      #graylog = 243;# dynamically allocated as of 2021-09-03
+      sniproxy = 244;
+      nzbget = 245;
+      mosquitto = 246;
+      #toxvpn = 247; # dynamically allocated as of 2021-09-18
+      # squeezelite = 248; # DynamicUser = true
+      turnserver = 249;
+      #smokeping = 250;# dynamically allocated as of 2021-09-03
+      gocd-agent = 251;
+      gocd-server = 252;
+      terraria = 253;
+      mattermost = 254;
+      prometheus = 255;
+      telegraf = 256;
+      gitlab-runner = 257;
+      postgrey = 258;
+      hound = 259;
+      leaps = 260;
+      ipfs  = 261;
+      # stanchion = 262; # unused, removed 2020-10-14
+      # riak-cs = 263; # unused, removed 2020-10-14
+      infinoted = 264;
+      sickbeard = 265;
+      headphones = 266;
+      # couchpotato = 267; # unused, removed 2022-01-01
+      gogs = 268;
+      #pdns-recursor = 269; # dynamically allocated as of 2020-20-18
+      #kresd = 270; # switched to "knot-resolver" with dynamic ID
+      rpc = 271;
+      #geoip = 272; # new module uses DynamicUser
+      fcron = 273;
+      sonarr = 274;
+      radarr = 275;
+      jackett = 276;
+      aria2 = 277;
+      clickhouse = 278;
+      rslsync = 279;
+      minio = 280;
+      kanboard = 281;
+      # pykms = 282; # DynamicUser = true
+      kodi = 283;
+      restya-board = 284;
+      mighttpd2 = 285;
+      hass = 286;
+      #monero = 287; # dynamically allocated as of 2021-05-08
+      ceph = 288;
+      duplicati = 289;
+      monetdb = 290;
+      restic = 291;
+      openvpn = 292;
+      # meguca = 293; # removed 2020-08-21
+      yarn = 294;
+      hdfs = 295;
+      mapred = 296;
+      hadoop = 297;
+      hydron = 298;
+      cfssl = 299;
+      cassandra = 300;
+      qemu-libvirtd = 301;
+      # kvm = 302; # unused
+      # render = 303; # unused
+      # zeronet = 304; # removed 2019-01-03
+      lirc = 305;
+      lidarr = 306;
+      slurm = 307;
+      kapacitor = 308;
+      solr = 309;
+      alerta = 310;
+      minetest = 311;
+      rss2email = 312;
+      cockroachdb = 313;
+      zoneminder = 314;
+      paperless = 315;
+      #mailman = 316;  # removed 2019-08-30
+      zigbee2mqtt = 317;
+      # shadow = 318; # unused
+      hqplayer = 319;
+      moonraker = 320;
+      distcc = 321;
+      webdav = 322;
+      pipewire = 323;
+      rstudio-server = 324;
+
+      # When adding a uid, make sure it doesn't match an existing gid. And don't use uids above 399!
+
+      nixbld = 30000; # start of range of uids
+      nobody = 65534;
+    };
+
+    ids.gids = {
+      root = 0;
+      wheel = 1;
+      kmem = 2;
+      tty = 3;
+      messagebus = 4; # D-Bus
+      haldaemon = 5;
+      disk = 6;
+      #vsftpd = 7; # dynamically allocated as of 2021-09-14
+      ftp = 8;
+      # bitlbee = 9; # removed 2021-10-05 #139765
+      #avahi = 10; # removed 2019-05-22
+      #nagios = 11; # unused
+      atd = 12;
+      postfix = 13;
+      postdrop = 14;
+      dovecot = 15;
+      tomcat = 16;
+      audio = 17;
+      floppy = 18;
+      uucp = 19;
+      lp = 20;
+      proc = 21;
+      pulseaudio = 22; # must match `pulseaudio' UID
+      gpsd = 23;
+      cdrom = 24;
+      tape = 25;
+      video = 26;
+      dialout = 27;
+      #polkituser = 28; # currently unused, polkitd doesn't need a group
+      utmp = 29;
+      # ddclient = 30; # converted to DynamicUser = true
+      davfs2 = 31;
+      disnix = 33;
+      osgi = 34;
+      tor = 35;
+      #cups = 36; # unused
+      #foldingathome = 37; # unused
+      #sabnzd = 38; # unused
+      #kdm = 39; # unused, even before 17.03
+      #ghostone = 40; # dropped in 18.03
+      git = 41;
+      fourstore = 42;
+      fourstorehttp = 43;
+      virtuoso = 44;
+      #rtkit = 45; # unused
+      dovecot2 = 46;
+      dovenull2 = 47;
+      prayer = 49;
+      mpd = 50;
+      clamav = 51;
+      #fprot = 52; # unused
+      #bind = 53; # unused
+      wwwrun = 54;
+      adm = 55;
+      spamd = 56;
+      networkmanager = 57;
+      nslcd = 58;
+      scanner = 59;
+      nginx = 60;
+      chrony = 61;
+      systemd-journal = 62;
+      smtpd = 63;
+      smtpq = 64;
+      supybot = 65;
+      iodined = 66;
+      libvirtd = 67;
+      graphite = 68;
+      #statsd = 69; # removed 2018-11-14
+      transmission = 70;
+      postgres = 71;
+      vboxusers = 72;
+      vboxsf = 73;
+      smbguest = 74;  # unused
+      varnish = 75;
+      datadog = 76;
+      lighttpd = 77;
+      lightdm = 78;
+      freenet = 79;
+      ircd = 80;
+      bacula = 81;
+      #almir = 82; # removed 2018-03-25, the almir package was removed in 30291227f2411abaca097773eedb49b8f259e297 during 2017-08
+      deluge = 83;
+      mysql = 84;
+      rabbitmq = 85;
+      activemq = 86;
+      gnunet = 87;
+      oidentd = 88;
+      quassel = 89;
+      amule = 90;
+      minidlna = 91;
+      elasticsearch = 92;
+      #tcpcryptd = 93; # unused
+      firebird = 95;
+      keys = 96;
+      #haproxy = 97; # dynamically allocated as of 2020-03-11
+      #mongodb = 98; # unused
+      #openldap = 99; # dynamically allocated as of PR#94610
+      munin = 102;
+      #logcheck = 103; # unused
+      #nix-ssh = 104; # unused
+      dictd = 105;
+      couchdb = 106;
+      #searx = 107; # dynamically allocated as of 2020-10-27
+      #kippo = 108; # removed 2021-10-07, the kippo package was removed in 1b213f321cdbfcf868b96fd9959c24207ce1b66a during 2021-04
+      jenkins = 109;
+      systemd-journal-gateway = 110;
+      #notbit = 111; # unused
+      aerospike = 111;
+      #ngircd = 112; # unused
+      #btsync = 113; # unused
+      #minecraft = 114; # unused
+      vault = 115;
+      #ripped = 116; # unused
+      murmur = 117;
+      foundationdb = 118;
+      newrelic = 119;
+      starbound = 120;
+      hydra = 122;
+      spiped = 123;
+      teamspeak = 124;
+      influxdb = 125;
+      nsd = 126;
+      gitolite = 127;
+      znc = 128;
+      polipo = 129;
+      mopidy = 130;
+      docker = 131;
+      gdm = 132;
+      #dhcpcd = 133; # unused
+      siproxd = 134;
+      mlmmj = 135;
+      #neo4j = 136; # unused
+      riemann = 137;
+      riemanndash = 138;
+      #radvd = 139; # unused
+      #zookeeper = 140; # unused
+      #dnsmasq = 141; # unused
+      uhub = 142;
+      #yandexdisk = 143; # unused
+      mxisd = 144; # was once collectd
+      #consul = 145; # unused
+      #mailpile = 146; # removed 2022-01-12
+      redmine = 147;
+      #seeks = 148; # removed 2020-06-21
+      prosody = 149;
+      i2pd = 150;
+      systemd-network = 152;
+      systemd-resolve = 153;
+      systemd-timesync = 154;
+      liquidsoap = 155;
+      #etcd = 156; # unused
+      hbase = 158;
+      opentsdb = 159;
+      scollector = 160;
+      bosun = 161;
+      kubernetes = 162;
+      #peerflix = 163; # unused
+      #chronos = 164; # unused
+      gitlab = 165;
+      nylon = 168;
+      #panamax = 170; # unused
+      exim = 172;
+      #fleet = 173; # unused
+      input = 174;
+      sddm = 175;
+      #tss = 176; #dynamically allocateda as of 2021-09-20
+      #memcached = 177; # unused, removed 2018-01-03
+      #ntp = 179; # unused
+      zabbix = 180;
+      #redis = 181; # unused, removed 2018-01-03
+      #unifi = 183; # unused
+      #uptimed = 184; # unused
+      #zope2 = 185; # unused
+      #ripple-data-api = 186; #unused
+      mediatomb = 187;
+      #rdnssd = 188; # unused
+      ihaskell = 189;
+      i2p = 190;
+      lambdabot = 191;
+      asterisk = 192;
+      plex = 193;
+      sabnzbd = 194;
+      #grafana = 196; #unused
+      #skydns = 197; #unused
+      # ripple-rest = 198; # unused, removed 2017-08-12
+      #nix-serve = 199; #unused
+      #tvheadend = 200; #unused
+      uwsgi = 201;
+      gitit = 202;
+      riemanntools = 203;
+      subsonic = 204;
+      riak = 205;
+      #shout = 206; #unused
+      gateone = 207;
+      namecoin = 208;
+      #lxd = 210; # unused
+      #kibana = 211;
+      xtreemfs = 212;
+      calibre-server = 213;
+      bepasty = 215;
+      # pumpio = 216; # unused, removed 2018-02-24
+      nm-openvpn = 217;
+      mathics = 218;
+      ejabberd = 219;
+      postsrsd = 220;
+      opendkim = 221;
+      dspam = 222;
+      # gale = 223; removed 2021-06-10
+      matrix-synapse = 224;
+      rspamd = 225;
+      # rmilter = 226; # unused, removed 2019-08-22
+      cfdyndns = 227;
+      pdnsd = 229;
+      octoprint = 230;
+      #radicale = 234;# dynamically allocated as of 2021-09-03
+      syncthing = 237;
+      caddy = 239;
+      taskd = 240;
+      # factorio = 241; # unused
+      # emby = 242; # unused, removed 2019-05-01
+      sniproxy = 244;
+      nzbget = 245;
+      mosquitto = 246;
+      #toxvpn = 247; # unused
+      #squeezelite = 248; #unused
+      turnserver = 249;
+      #smokeping = 250;# dynamically allocated as of 2021-09-03
+      gocd-agent = 251;
+      gocd-server = 252;
+      terraria = 253;
+      mattermost = 254;
+      prometheus = 255;
+      #telegraf = 256; # unused
+      gitlab-runner = 257;
+      postgrey = 258;
+      hound = 259;
+      leaps = 260;
+      ipfs = 261;
+      # stanchion = 262; # unused, removed 2020-10-14
+      # riak-cs = 263; # unused, removed 2020-10-14
+      infinoted = 264;
+      sickbeard = 265;
+      headphones = 266;
+      # couchpotato = 267; # unused, removed 2022-01-01
+      gogs = 268;
+      #kresd = 270; # switched to "knot-resolver" with dynamic ID
+      #rpc = 271; # unused
+      #geoip = 272; # unused
+      fcron = 273;
+      sonarr = 274;
+      radarr = 275;
+      jackett = 276;
+      aria2 = 277;
+      clickhouse = 278;
+      rslsync = 279;
+      minio = 280;
+      kanboard = 281;
+      # pykms = 282; # DynamicUser = true
+      kodi = 283;
+      restya-board = 284;
+      mighttpd2 = 285;
+      hass = 286;
+      # monero = 287; # dynamically allocated as of 2021-05-08
+      ceph = 288;
+      duplicati = 289;
+      monetdb = 290;
+      restic = 291;
+      openvpn = 292;
+      # meguca = 293; # removed 2020-08-21
+      yarn = 294;
+      hdfs = 295;
+      mapred = 296;
+      hadoop = 297;
+      hydron = 298;
+      cfssl = 299;
+      cassandra = 300;
+      qemu-libvirtd = 301;
+      kvm = 302; # default udev rules from systemd requires these
+      render = 303; # default udev rules from systemd requires these
+      sgx = 304; # default udev rules from systemd requires these
+      lirc = 305;
+      lidarr = 306;
+      slurm = 307;
+      kapacitor = 308;
+      solr = 309;
+      alerta = 310;
+      minetest = 311;
+      rss2email = 312;
+      cockroachdb = 313;
+      zoneminder = 314;
+      paperless = 315;
+      #mailman = 316;  # removed 2019-08-30
+      zigbee2mqtt = 317;
+      shadow = 318;
+      hqplayer = 319;
+      moonraker = 320;
+      distcc = 321;
+      webdav = 322;
+      pipewire = 323;
+      rstudio-server = 324;
+
+      # When adding a gid, make sure it doesn't match an existing
+      # uid. Users and groups with the same name should have equal
+      # uids and gids. Also, don't use gids above 399!
+
+      users = 100;
+      nixbld = 30000;
+      nogroup = 65534;
+    };
+
+  };
+
+}
diff --git a/nixos/modules/misc/label.nix b/nixos/modules/misc/label.nix
new file mode 100644
index 00000000000..02b91555b3c
--- /dev/null
+++ b/nixos/modules/misc/label.nix
@@ -0,0 +1,72 @@
+{ config, lib, ... }:
+
+with lib;
+
+let
+  cfg = config.system.nixos;
+in
+
+{
+
+  options.system = {
+
+    nixos.label = mkOption {
+      type = types.str;
+      description = ''
+        NixOS version name to be used in the names of generated
+        outputs and boot labels.
+
+        If you ever wanted to influence the labels in your GRUB menu,
+        this is the option for you.
+
+        The default is <option>system.nixos.tags</option> separated by
+        "-" + "-" + <envar>NIXOS_LABEL_VERSION</envar> environment
+        variable (defaults to the value of
+        <option>system.nixos.version</option>).
+
+        Can be overriden by setting <envar>NIXOS_LABEL</envar>.
+
+        Useful for not loosing track of configurations built from different
+        nixos branches/revisions, e.g.:
+
+        <screen>
+        #!/bin/sh
+        today=`date +%Y%m%d`
+        branch=`(cd nixpkgs ; git branch 2>/dev/null | sed -n '/^\* / { s|^\* ||; p; }')`
+        revision=`(cd nixpkgs ; git rev-parse HEAD)`
+        export NIXOS_LABEL_VERSION="$today.$branch-''${revision:0:7}"
+        nixos-rebuild switch</screen>
+      '';
+    };
+
+    nixos.tags = mkOption {
+      type = types.listOf types.str;
+      default = [];
+      example = [ "with-xen" ];
+      description = ''
+        Strings to prefix to the default
+        <option>system.nixos.label</option>.
+
+        Useful for not loosing track of configurations built with
+        different options, e.g.:
+
+        <screen>
+        {
+          system.nixos.tags = [ "with-xen" ];
+          virtualisation.xen.enable = true;
+        }
+        </screen>
+      '';
+    };
+
+  };
+
+  config = {
+    # This is set here rather than up there so that changing it would
+    # not rebuild the manual
+    system.nixos.label = mkDefault (maybeEnv "NIXOS_LABEL"
+                                             (concatStringsSep "-" ((sort (x: y: x < y) cfg.tags)
+                                              ++ [ (maybeEnv "NIXOS_LABEL_VERSION" cfg.version) ])));
+  };
+
+}
diff --git a/nixos/modules/misc/lib.nix b/nixos/modules/misc/lib.nix
new file mode 100644
index 00000000000..121f396701e
--- /dev/null
+++ b/nixos/modules/misc/lib.nix
@@ -0,0 +1,15 @@
+{ lib, ... }:
+
+{
+  options = {
+    lib = lib.mkOption {
+      default = {};
+
+      type = lib.types.attrsOf lib.types.attrs;
+
+      description = ''
+        This option allows modules to define helper functions, constants, etc.
+      '';
+    };
+  };
+}
diff --git a/nixos/modules/misc/locate.nix b/nixos/modules/misc/locate.nix
new file mode 100644
index 00000000000..204a8914300
--- /dev/null
+++ b/nixos/modules/misc/locate.nix
@@ -0,0 +1,313 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.locate;
+  isMLocate = hasPrefix "mlocate" cfg.locate.name;
+  isPLocate = hasPrefix "plocate" cfg.locate.name;
+  isMorPLocate = (isMLocate || isPLocate);
+  isFindutils = hasPrefix "findutils" cfg.locate.name;
+in
+{
+  imports = [
+    (mkRenamedOptionModule [ "services" "locate" "period" ] [ "services" "locate" "interval" ])
+    (mkRemovedOptionModule [ "services" "locate" "includeStore" ] "Use services.locate.prunePaths")
+  ];
+
+  options.services.locate = with types; {
+    enable = mkOption {
+      type = bool;
+      default = false;
+      description = ''
+        If enabled, NixOS will periodically update the database of
+        files used by the <command>locate</command> command.
+      '';
+    };
+
+    locate = mkOption {
+      type = package;
+      default = pkgs.findutils;
+      defaultText = literalExpression "pkgs.findutils";
+      example = literalExpression "pkgs.mlocate";
+      description = ''
+        The locate implementation to use
+      '';
+    };
+
+    interval = mkOption {
+      type = str;
+      default = "02:15";
+      example = "hourly";
+      description = ''
+        Update the locate database at this interval. Updates by
+        default at 2:15 AM every day.
+
+        The format is described in
+        <citerefentry><refentrytitle>systemd.time</refentrytitle>
+        <manvolnum>7</manvolnum></citerefentry>.
+
+        To disable automatic updates, set to <literal>"never"</literal>
+        and run <command>updatedb</command> manually.
+      '';
+    };
+
+    extraFlags = mkOption {
+      type = listOf str;
+      default = [ ];
+      description = ''
+        Extra flags to pass to <command>updatedb</command>.
+      '';
+    };
+
+    output = mkOption {
+      type = path;
+      default = "/var/cache/locatedb";
+      description = ''
+        The database file to build.
+      '';
+    };
+
+    localuser = mkOption {
+      type = nullOr str;
+      default = "nobody";
+      description = ''
+        The user to search non-network directories as, using
+        <command>su</command>.
+      '';
+    };
+
+    pruneFS = mkOption {
+      type = listOf str;
+      default = [
+        "afs"
+        "anon_inodefs"
+        "auto"
+        "autofs"
+        "bdev"
+        "binfmt"
+        "binfmt_misc"
+        "ceph"
+        "cgroup"
+        "cgroup2"
+        "cifs"
+        "coda"
+        "configfs"
+        "cramfs"
+        "cpuset"
+        "curlftpfs"
+        "debugfs"
+        "devfs"
+        "devpts"
+        "devtmpfs"
+        "ecryptfs"
+        "eventpollfs"
+        "exofs"
+        "futexfs"
+        "ftpfs"
+        "fuse"
+        "fusectl"
+        "fusesmb"
+        "fuse.ceph"
+        "fuse.glusterfs"
+        "fuse.gvfsd-fuse"
+        "fuse.mfs"
+        "fuse.rclone"
+        "fuse.rozofs"
+        "fuse.sshfs"
+        "gfs"
+        "gfs2"
+        "hostfs"
+        "hugetlbfs"
+        "inotifyfs"
+        "iso9660"
+        "jffs2"
+        "lustre"
+        "lustre_lite"
+        "misc"
+        "mfs"
+        "mqueue"
+        "ncpfs"
+        "nfs"
+        "NFS"
+        "nfs4"
+        "nfsd"
+        "nnpfs"
+        "ocfs"
+        "ocfs2"
+        "pipefs"
+        "proc"
+        "ramfs"
+        "rpc_pipefs"
+        "securityfs"
+        "selinuxfs"
+        "sfs"
+        "shfs"
+        "smbfs"
+        "sockfs"
+        "spufs"
+        "sshfs"
+        "subfs"
+        "supermount"
+        "sysfs"
+        "tmpfs"
+        "tracefs"
+        "ubifs"
+        "udev"
+        "udf"
+        "usbfs"
+        "vboxsf"
+        "vperfctrfs"
+      ];
+      description = ''
+        Which filesystem types to exclude from indexing
+      '';
+    };
+
+    prunePaths = mkOption {
+      type = listOf path;
+      default = [
+        "/tmp"
+        "/var/tmp"
+        "/var/cache"
+        "/var/lock"
+        "/var/run"
+        "/var/spool"
+        "/nix/store"
+        "/nix/var/log/nix"
+      ];
+      description = ''
+        Which paths to exclude from indexing
+      '';
+    };
+
+    pruneNames = mkOption {
+      type = listOf str;
+      default = lib.optionals (!isFindutils) [ ".bzr" ".cache" ".git" ".hg" ".svn" ];
+      defaultText = literalDocBook ''
+        <literal>[ ".bzr" ".cache" ".git" ".hg" ".svn" ]</literal>, if
+        supported by the locate implementation (i.e. mlocate or plocate).
+      '';
+      description = ''
+        Directory components which should exclude paths containing them from indexing
+      '';
+    };
+
+    pruneBindMounts = mkOption {
+      type = bool;
+      default = false;
+      description = ''
+        Whether not to index bind mounts
+      '';
+    };
+
+  };
+
+  config = mkIf cfg.enable {
+    users.groups = mkMerge [
+      (mkIf isMLocate { mlocate = { }; })
+      (mkIf isPLocate { plocate = { }; })
+    ];
+
+    security.wrappers =
+      let
+        common = {
+          owner = "root";
+          permissions = "u+rx,g+x,o+x";
+          setgid = true;
+          setuid = false;
+        };
+        mlocate = (mkIf isMLocate {
+          group = "mlocate";
+          source = "${cfg.locate}/bin/locate";
+        });
+        plocate = (mkIf isPLocate {
+          group = "plocate";
+          source = "${cfg.locate}/bin/plocate";
+        });
+      in
+      mkIf isMorPLocate {
+        locate = mkMerge [ common mlocate plocate ];
+        plocate = (mkIf isPLocate (mkMerge [ common plocate ]));
+      };
+
+    nixpkgs.config = { locate.dbfile = cfg.output; };
+
+    environment.systemPackages = [ cfg.locate ];
+
+    environment.variables = mkIf (!isMorPLocate) { LOCATE_PATH = cfg.output; };
+
+    environment.etc = {
+      # write /etc/updatedb.conf for manual calls to `updatedb`
+      "updatedb.conf" = {
+        text = ''
+          PRUNEFS="${lib.concatStringsSep " " cfg.pruneFS}"
+          PRUNENAMES="${lib.concatStringsSep " " cfg.pruneNames}"
+          PRUNEPATHS="${lib.concatStringsSep " " cfg.prunePaths}"
+          PRUNE_BIND_MOUNTS="${if cfg.pruneBindMounts then "yes" else "no"}"
+        '';
+      };
+    };
+
+    warnings = optional (isMorPLocate && cfg.localuser != null)
+      "mlocate does not support the services.locate.localuser option; updatedb will run as root. (Silence with services.locate.localuser = null.)"
+    ++ optional (isFindutils && cfg.pruneNames != [ ])
+      "findutils locate does not support pruning by directory component"
+    ++ optional (isFindutils && cfg.pruneBindMounts)
+      "findutils locate does not support skipping bind mounts";
+
+    systemd.services.update-locatedb = {
+      description = "Update Locate Database";
+      path = mkIf (!isMorPLocate) [ pkgs.su ];
+
+      # mlocate's updatedb takes flags via a configuration file or
+      # on the command line, but not by environment variable.
+      script =
+        if isMorPLocate then
+          let
+            toFlags = x:
+              optional (cfg.${x} != [ ])
+                "--${lib.toLower x} '${concatStringsSep " " cfg.${x}}'";
+            args = concatLists (map toFlags [ "pruneFS" "pruneNames" "prunePaths" ]);
+          in
+          ''
+            exec ${cfg.locate}/bin/updatedb \
+              --output ${toString cfg.output} ${concatStringsSep " " args} \
+              --prune-bind-mounts ${if cfg.pruneBindMounts then "yes" else "no"} \
+              ${concatStringsSep " " cfg.extraFlags}
+          ''
+        else ''
+          exec ${cfg.locate}/bin/updatedb \
+            ${optionalString (cfg.localuser != null && !isMorPLocate) "--localuser=${cfg.localuser}"} \
+            --output=${toString cfg.output} ${concatStringsSep " " cfg.extraFlags}
+        '';
+      environment = optionalAttrs (!isMorPLocate) {
+        PRUNEFS = concatStringsSep " " cfg.pruneFS;
+        PRUNEPATHS = concatStringsSep " " cfg.prunePaths;
+        PRUNENAMES = concatStringsSep " " cfg.pruneNames;
+        PRUNE_BIND_MOUNTS = if cfg.pruneBindMounts then "yes" else "no";
+      };
+      serviceConfig.Nice = 19;
+      serviceConfig.IOSchedulingClass = "idle";
+      serviceConfig.PrivateTmp = "yes";
+      serviceConfig.PrivateNetwork = "yes";
+      serviceConfig.NoNewPrivileges = "yes";
+      serviceConfig.ReadOnlyPaths = "/";
+      # Use dirOf cfg.output because mlocate creates temporary files next to
+      # the actual database. We could specify and create them as well,
+      # but that would make this quite brittle when they change something.
+      # NOTE: If /var/cache does not exist, this leads to the misleading error message:
+      # update-locatedb.service: Failed at step NAMESPACE spawning …/update-locatedb-start: No such file or directory
+      serviceConfig.ReadWritePaths = dirOf cfg.output;
+    };
+
+    systemd.timers.update-locatedb = mkIf (cfg.interval != "never") {
+      description = "Update timer for locate database";
+      partOf = [ "update-locatedb.service" ];
+      wantedBy = [ "timers.target" ];
+      timerConfig.OnCalendar = cfg.interval;
+    };
+  };
+
+  meta.maintainers = with lib.maintainers; [ SuperSandro2000 ];
+}
diff --git a/nixos/modules/misc/man-db.nix b/nixos/modules/misc/man-db.nix
new file mode 100644
index 00000000000..8bd329bc4e0
--- /dev/null
+++ b/nixos/modules/misc/man-db.nix
@@ -0,0 +1,73 @@
+{ config, pkgs, lib, ... }:
+
+let
+  cfg = config.documentation.man.man-db;
+in
+
+{
+  options = {
+    documentation.man.man-db = {
+      enable = lib.mkEnableOption "man-db as the default man page viewer" // {
+        default = config.documentation.man.enable;
+        defaultText = lib.literalExpression "config.documentation.man.enable";
+        example = false;
+      };
+
+      manualPages = lib.mkOption {
+        type = lib.types.path;
+        default = pkgs.buildEnv {
+          name = "man-paths";
+          paths = config.environment.systemPackages;
+          pathsToLink = [ "/share/man" ];
+          extraOutputsToInstall = [ "man" ]
+            ++ lib.optionals config.documentation.dev.enable [ "devman" ];
+          ignoreCollisions = true;
+        };
+        defaultText = lib.literalDocBook "all man pages in <option>config.environment.systemPackages</option>";
+        description = ''
+          The manual pages to generate caches for if <option>documentation.man.generateCaches</option>
+          is enabled. Must be a path to a directory with man pages under
+          <literal>/share/man</literal>; see the source for an example.
+          Advanced users can make this a content-addressed derivation to save a few rebuilds.
+        '';
+      };
+
+      package = lib.mkOption {
+        type = lib.types.package;
+        default = pkgs.man-db;
+        defaultText = lib.literalExpression "pkgs.man-db";
+        description = ''
+          The <literal>man-db</literal> derivation to use. Useful to override
+          configuration options used for the package.
+        '';
+      };
+    };
+  };
+
+  imports = [
+    (lib.mkRenamedOptionModule [ "documentation" "man" "manualPages" ] [ "documentation" "man" "man-db" "manualPages" ])
+  ];
+
+  config = lib.mkIf cfg.enable {
+    environment.systemPackages = [ cfg.package ];
+    environment.etc."man_db.conf".text =
+      let
+        manualCache = pkgs.runCommandLocal "man-cache" { } ''
+          echo "MANDB_MAP ${cfg.manualPages}/share/man $out" > man.conf
+          ${cfg.package}/bin/mandb -C man.conf -psc >/dev/null 2>&1
+        '';
+      in
+      ''
+        # Manual pages paths for NixOS
+        MANPATH_MAP /run/current-system/sw/bin /run/current-system/sw/share/man
+        MANPATH_MAP /run/wrappers/bin          /run/current-system/sw/share/man
+
+        ${lib.optionalString config.documentation.man.generateCaches ''
+        # Generated manual pages cache for NixOS (immutable)
+        MANDB_MAP /run/current-system/sw/share/man ${manualCache}
+        ''}
+        # Manual pages caches for NixOS
+        MANDB_MAP /run/current-system/sw/share/man /var/cache/man/nixos
+      '';
+  };
+}
diff --git a/nixos/modules/misc/mandoc.nix b/nixos/modules/misc/mandoc.nix
new file mode 100644
index 00000000000..3da60f2f8e6
--- /dev/null
+++ b/nixos/modules/misc/mandoc.nix
@@ -0,0 +1,61 @@
+{ config, lib, pkgs, ... }:
+
+let
+  makewhatis = "${lib.getBin cfg.package}/bin/makewhatis";
+
+  cfg = config.documentation.man.mandoc;
+
+in {
+  meta.maintainers = [ lib.maintainers.sternenseemann ];
+
+  options = {
+    documentation.man.mandoc = {
+      enable = lib.mkEnableOption "mandoc as the default man page viewer";
+
+      manPath = lib.mkOption {
+        type = with lib.types; listOf str;
+        default = [ "share/man" ];
+        example = lib.literalExpression "[ \"share/man\" \"share/man/fr\" ]";
+        description = ''
+          Change the manpath, i. e. the directories where
+          <citerefentry><refentrytitle>man</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+          looks for section-specific directories of man pages.
+          You only need to change this setting if you want extra man pages
+          (e. g. in non-english languages). All values must be strings that
+          are a valid path from the target prefix (without including it).
+          The first value given takes priority.
+        '';
+      };
+
+      package = lib.mkOption {
+        type = lib.types.package;
+        default = pkgs.mandoc;
+        defaultText = lib.literalExpression "pkgs.mandoc";
+        description = ''
+          The <literal>mandoc</literal> derivation to use. Useful to override
+          configuration options used for the package.
+        '';
+      };
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    environment = {
+      systemPackages = [ cfg.package ];
+
+      # tell mandoc about man pages
+      etc."man.conf".text = lib.concatMapStrings (path: ''
+        manpath /run/current-system/sw/${path}
+      '') cfg.manPath;
+
+      # create mandoc.db for whatis(1), apropos(1) and man(1) -k
+      # TODO(@sternenseemman): fix symlinked directories not getting indexed,
+      # see: https://inbox.vuxu.org/mandoc-tech/20210906171231.GF83680@athene.usta.de/T/#e85f773c1781e3fef85562b2794f9cad7b2909a3c
+      extraSetup = lib.mkIf config.documentation.man.generateCaches ''
+        ${makewhatis} -T utf8 ${
+          lib.concatMapStringsSep " " (path: "\"$out/${path}\"") cfg.manPath
+        }
+      '';
+    };
+  };
+}
diff --git a/nixos/modules/misc/meta.nix b/nixos/modules/misc/meta.nix
new file mode 100644
index 00000000000..8e689a63f6b
--- /dev/null
+++ b/nixos/modules/misc/meta.nix
@@ -0,0 +1,76 @@
+{ lib, ... }:
+
+with lib;
+
+let
+  maintainer = mkOptionType {
+    name = "maintainer";
+    check = email: elem email (attrValues lib.maintainers);
+    merge = loc: defs: listToAttrs (singleton (nameValuePair (last defs).file (last defs).value));
+  };
+
+  listOfMaintainers = types.listOf maintainer // {
+    # Returns list of
+    #   { "module-file" = [
+    #        "maintainer1 <first@nixos.org>"
+    #        "maintainer2 <second@nixos.org>" ];
+    #   }
+    merge = loc: defs:
+      zipAttrs
+        (flatten (imap1 (n: def: imap1 (m: def':
+          maintainer.merge (loc ++ ["[${toString n}-${toString m}]"])
+            [{ inherit (def) file; value = def'; }]) def.value) defs));
+  };
+
+  docFile = types.path // {
+    # Returns tuples of
+    #   { file = "module location"; value = <path/to/doc.xml>; }
+    merge = loc: defs: defs;
+  };
+in
+
+{
+  options = {
+    meta = {
+
+      maintainers = mkOption {
+        type = listOfMaintainers;
+        internal = true;
+        default = [];
+        example = literalExpression ''[ lib.maintainers.all ]'';
+        description = ''
+          List of maintainers of each module.  This option should be defined at
+          most once per module.
+        '';
+      };
+
+      doc = mkOption {
+        type = docFile;
+        internal = true;
+        example = "./meta.chapter.xml";
+        description = ''
+          Documentation prologue for the set of options of each module.  This
+          option should be defined at most once per module.
+        '';
+      };
+
+      buildDocsInSandbox = mkOption {
+        type = types.bool // {
+          merge = loc: defs: defs;
+        };
+        internal = true;
+        default = true;
+        description = ''
+          Whether to include this module in the split options doc build.
+          Disable if the module references `config`, `pkgs` or other module
+          arguments that cannot be evaluated as constants.
+
+          This option should be defined at most once per module.
+        '';
+      };
+
+    };
+  };
+
+  meta.maintainers = singleton lib.maintainers.pierron;
+}
diff --git a/nixos/modules/misc/nixops-autoluks.nix b/nixos/modules/misc/nixops-autoluks.nix
new file mode 100644
index 00000000000..20c143286af
--- /dev/null
+++ b/nixos/modules/misc/nixops-autoluks.nix
@@ -0,0 +1,43 @@
+{ config, options, lib, ... }:
+let
+  path = [ "deployment" "autoLuks" ];
+  hasAutoLuksConfig = lib.hasAttrByPath path config && (lib.attrByPath path {} config) != {};
+
+  inherit (config.nixops) enableDeprecatedAutoLuks;
+in {
+  options.nixops.enableDeprecatedAutoLuks = lib.mkEnableOption "Enable the deprecated NixOps AutoLuks module";
+
+  config = {
+    assertions = [
+      {
+        assertion = if hasAutoLuksConfig then hasAutoLuksConfig && enableDeprecatedAutoLuks else true;
+        message = ''
+          ⚠️  !!! WARNING !!! ⚠️
+
+            NixOps autoLuks is deprecated. The feature was never widely used and the maintenance did outgrow the benefit.
+            If you still want to use the module:
+              a) Please raise your voice in the issue tracking usage of the module:
+                 https://github.com/NixOS/nixpkgs/issues/62211
+              b) make sure you set the `_netdev` option for each of the file
+                 systems referring to block devices provided by the autoLuks module.
+
+                 ⚠️ If you do not set the option your system will not boot anymore! ⚠️
+
+                  {
+                    fileSystems."/secret" = { options = [ "_netdev" ]; };
+                  }
+
+              b) set the option >nixops.enableDeprecatedAutoLuks = true< to remove this error.
+
+
+            For more details read through the following resources:
+              - https://github.com/NixOS/nixops/pull/1156
+              - https://github.com/NixOS/nixpkgs/issues/47550
+              - https://github.com/NixOS/nixpkgs/issues/62211
+              - https://github.com/NixOS/nixpkgs/pull/61321
+        '';
+      }
+    ];
+  };
+
+}
diff --git a/nixos/modules/misc/nixpkgs.nix b/nixos/modules/misc/nixpkgs.nix
new file mode 100644
index 00000000000..69967c8a760
--- /dev/null
+++ b/nixos/modules/misc/nixpkgs.nix
@@ -0,0 +1,259 @@
+{ config, options, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.nixpkgs;
+  opt = options.nixpkgs;
+
+  isConfig = x:
+    builtins.isAttrs x || lib.isFunction x;
+
+  optCall = f: x:
+    if lib.isFunction f
+    then f x
+    else f;
+
+  mergeConfig = lhs_: rhs_:
+    let
+      lhs = optCall lhs_ { inherit pkgs; };
+      rhs = optCall rhs_ { inherit pkgs; };
+    in
+    recursiveUpdate lhs rhs //
+    optionalAttrs (lhs ? packageOverrides) {
+      packageOverrides = pkgs:
+        optCall lhs.packageOverrides pkgs //
+        optCall (attrByPath ["packageOverrides"] ({}) rhs) pkgs;
+    } //
+    optionalAttrs (lhs ? perlPackageOverrides) {
+      perlPackageOverrides = pkgs:
+        optCall lhs.perlPackageOverrides pkgs //
+        optCall (attrByPath ["perlPackageOverrides"] ({}) rhs) pkgs;
+    };
+
+  configType = mkOptionType {
+    name = "nixpkgs-config";
+    description = "nixpkgs config";
+    check = x:
+      let traceXIfNot = c:
+            if c x then true
+            else lib.traceSeqN 1 x false;
+      in traceXIfNot isConfig;
+    merge = args: foldr (def: mergeConfig def.value) {};
+  };
+
+  overlayType = mkOptionType {
+    name = "nixpkgs-overlay";
+    description = "nixpkgs overlay";
+    check = lib.isFunction;
+    merge = lib.mergeOneOption;
+  };
+
+  pkgsType = mkOptionType {
+    name = "nixpkgs";
+    description = "An evaluation of Nixpkgs; the top level attribute set of packages";
+    check = builtins.isAttrs;
+  };
+
+  defaultPkgs = import ../../.. {
+    inherit (cfg) config overlays localSystem crossSystem;
+  };
+
+  finalPkgs = if opt.pkgs.isDefined then cfg.pkgs.appendOverlays cfg.overlays else defaultPkgs;
+
+in
+
+{
+  imports = [
+    ./assertions.nix
+    ./meta.nix
+  ];
+
+  options.nixpkgs = {
+
+    pkgs = mkOption {
+      defaultText = literalExpression ''
+        import "''${nixos}/.." {
+          inherit (cfg) config overlays localSystem crossSystem;
+        }
+      '';
+      type = pkgsType;
+      example = literalExpression "import <nixpkgs> {}";
+      description = ''
+        If set, the pkgs argument to all NixOS modules is the value of
+        this option, extended with <code>nixpkgs.overlays</code>, if
+        that is also set. Either <code>nixpkgs.crossSystem</code> or
+        <code>nixpkgs.localSystem</code> will be used in an assertion
+        to check that the NixOS and Nixpkgs architectures match. Any
+        other options in <code>nixpkgs.*</code>, notably <code>config</code>,
+        will be ignored.
+
+        If unset, the pkgs argument to all NixOS modules is determined
+        as shown in the default value for this option.
+
+        The default value imports the Nixpkgs source files
+        relative to the location of this NixOS module, because
+        NixOS and Nixpkgs are distributed together for consistency,
+        so the <code>nixos</code> in the default value is in fact a
+        relative path. The <code>config</code>, <code>overlays</code>,
+        <code>localSystem</code>, and <code>crossSystem</code> come
+        from this option's siblings.
+
+        This option can be used by applications like NixOps to increase
+        the performance of evaluation, or to create packages that depend
+        on a container that should be built with the exact same evaluation
+        of Nixpkgs, for example. Applications like this should set
+        their default value using <code>lib.mkDefault</code>, so
+        user-provided configuration can override it without using
+        <code>lib</code>.
+
+        Note that using a distinct version of Nixpkgs with NixOS may
+        be an unexpected source of problems. Use this option with care.
+      '';
+    };
+
+    config = mkOption {
+      default = {};
+      example = literalExpression
+        ''
+          { allowBroken = true; allowUnfree = true; }
+        '';
+      type = configType;
+      description = ''
+        The configuration of the Nix Packages collection.  (For
+        details, see the Nixpkgs documentation.)  It allows you to set
+        package configuration options.
+
+        Ignored when <code>nixpkgs.pkgs</code> is set.
+      '';
+    };
+
+    overlays = mkOption {
+      default = [];
+      example = literalExpression
+        ''
+          [
+            (self: super: {
+              openssh = super.openssh.override {
+                hpnSupport = true;
+                kerberos = self.libkrb5;
+              };
+            })
+          ]
+        '';
+      type = types.listOf overlayType;
+      description = ''
+        List of overlays to use with the Nix Packages collection.
+        (For details, see the Nixpkgs documentation.)  It allows
+        you to override packages globally. Each function in the list
+        takes as an argument the <emphasis>original</emphasis> Nixpkgs.
+        The first argument should be used for finding dependencies, and
+        the second should be used for overriding recipes.
+
+        If <code>nixpkgs.pkgs</code> is set, overlays specified here
+        will be applied after the overlays that were already present
+        in <code>nixpkgs.pkgs</code>.
+      '';
+    };
+
+    localSystem = mkOption {
+      type = types.attrs; # TODO utilize lib.systems.parsedPlatform
+      default = { inherit (cfg) system; };
+      example = { system = "aarch64-linux"; config = "aarch64-unknown-linux-gnu"; };
+      # Make sure that the final value has all fields for sake of other modules
+      # referring to this. TODO make `lib.systems` itself use the module system.
+      apply = lib.systems.elaborate;
+      defaultText = literalExpression
+        ''(import "''${nixos}/../lib").lib.systems.examples.aarch64-multiplatform'';
+      description = ''
+        Specifies the platform on which NixOS should be built. When
+        <code>nixpkgs.crossSystem</code> is unset, it also specifies
+        the platform <emphasis>for</emphasis> which NixOS should be
+        built.  If this option is unset, it defaults to the platform
+        type of the machine where evaluation happens. Specifying this
+        option is useful when doing distributed multi-platform
+        deployment, or when building virtual machines. See its
+        description in the Nixpkgs manual for more details.
+
+        Ignored when <code>nixpkgs.pkgs</code> is set.
+      '';
+    };
+
+    crossSystem = mkOption {
+      type = types.nullOr types.attrs; # TODO utilize lib.systems.parsedPlatform
+      default = null;
+      example = { system = "aarch64-linux"; config = "aarch64-unknown-linux-gnu"; };
+      description = ''
+        Specifies the platform for which NixOS should be
+        built. Specify this only if it is different from
+        <code>nixpkgs.localSystem</code>, the platform
+        <emphasis>on</emphasis> which NixOS should be built. In other
+        words, specify this to cross-compile NixOS. Otherwise it
+        should be set as null, the default. See its description in the
+        Nixpkgs manual for more details.
+
+        Ignored when <code>nixpkgs.pkgs</code> is set.
+      '';
+    };
+
+    system = mkOption {
+      type = types.str;
+      example = "i686-linux";
+      description = ''
+        Specifies the Nix platform type on which NixOS should be built.
+        It is better to specify <code>nixpkgs.localSystem</code> instead.
+        <programlisting>
+        {
+          nixpkgs.system = ..;
+        }
+        </programlisting>
+        is the same as
+        <programlisting>
+        {
+          nixpkgs.localSystem.system = ..;
+        }
+        </programlisting>
+        See <code>nixpkgs.localSystem</code> for more information.
+
+        Ignored when <code>nixpkgs.localSystem</code> is set.
+        Ignored when <code>nixpkgs.pkgs</code> is set.
+      '';
+    };
+
+    initialSystem = mkOption {
+      type = types.str;
+      internal = true;
+      description = ''
+        Preserved value of <literal>system</literal> passed to <literal>eval-config.nix</literal>.
+      '';
+    };
+  };
+
+  config = {
+    _module.args = {
+      pkgs = finalPkgs;
+    };
+
+    assertions = [
+      (
+        let
+          nixosExpectedSystem =
+            if config.nixpkgs.crossSystem != null
+            then config.nixpkgs.crossSystem.system or (lib.systems.parse.doubleFromSystem (lib.systems.parse.mkSystemFromString config.nixpkgs.crossSystem.config))
+            else config.nixpkgs.localSystem.system or (lib.systems.parse.doubleFromSystem (lib.systems.parse.mkSystemFromString config.nixpkgs.localSystem.config));
+          nixosOption =
+            if config.nixpkgs.crossSystem != null
+            then "nixpkgs.crossSystem"
+            else "nixpkgs.localSystem";
+          pkgsSystem = finalPkgs.stdenv.targetPlatform.system;
+        in {
+          assertion = nixosExpectedSystem == pkgsSystem;
+          message = "The NixOS nixpkgs.pkgs option was set to a Nixpkgs invocation that compiles to target system ${pkgsSystem} but NixOS was configured for system ${nixosExpectedSystem} via NixOS option ${nixosOption}. The NixOS system settings must match the Nixpkgs target system.";
+        }
+      )
+    ];
+  };
+
+  # needs a full nixpkgs path to import nixpkgs
+  meta.buildDocsInSandbox = false;
+}
diff --git a/nixos/modules/misc/nixpkgs/test.nix b/nixos/modules/misc/nixpkgs/test.nix
new file mode 100644
index 00000000000..ec5fab9fb4a
--- /dev/null
+++ b/nixos/modules/misc/nixpkgs/test.nix
@@ -0,0 +1,8 @@
+{ evalMinimalConfig, pkgs, lib, stdenv }:
+lib.recurseIntoAttrs {
+  invokeNixpkgsSimple =
+    (evalMinimalConfig ({ config, modulesPath, ... }: {
+      imports = [ (modulesPath + "/misc/nixpkgs.nix") ];
+      nixpkgs.system = stdenv.hostPlatform.system;
+    }))._module.args.pkgs.hello;
+}
diff --git a/nixos/modules/misc/passthru.nix b/nixos/modules/misc/passthru.nix
new file mode 100644
index 00000000000..4e99631fdd8
--- /dev/null
+++ b/nixos/modules/misc/passthru.nix
@@ -0,0 +1,16 @@
+# This module allows you to export something from configuration
+# Use case: export kernel source expression for ease of configuring
+
+{ lib, ... }:
+
+{
+  options = {
+    passthru = lib.mkOption {
+      visible = false;
+      description = ''
+        This attribute set will be exported as a system attribute.
+        You can put whatever you want here.
+      '';
+    };
+  };
+}
diff --git a/nixos/modules/misc/version.nix b/nixos/modules/misc/version.nix
new file mode 100644
index 00000000000..d825f4beb30
--- /dev/null
+++ b/nixos/modules/misc/version.nix
@@ -0,0 +1,141 @@
+{ config, lib, options, pkgs, ... }:
+
+let
+  cfg = config.system.nixos;
+  opt = options.system.nixos;
+
+  inherit (lib)
+    concatStringsSep mapAttrsToList toLower
+    literalExpression mkRenamedOptionModule mkDefault mkOption trivial types;
+
+  needsEscaping = s: null != builtins.match "[a-zA-Z0-9]+" s;
+  escapeIfNeccessary = s: if needsEscaping s then s else ''"${lib.escape [ "\$" "\"" "\\" "\`" ] s}"'';
+  attrsToText = attrs:
+    concatStringsSep "\n" (
+      mapAttrsToList (n: v: ''${n}=${escapeIfNeccessary (toString v)}'') attrs
+    );
+
+in
+{
+  imports = [
+    (mkRenamedOptionModule [ "system" "nixosVersion" ] [ "system" "nixos" "version" ])
+    (mkRenamedOptionModule [ "system" "nixosVersionSuffix" ] [ "system" "nixos" "versionSuffix" ])
+    (mkRenamedOptionModule [ "system" "nixosRevision" ] [ "system" "nixos" "revision" ])
+    (mkRenamedOptionModule [ "system" "nixosLabel" ] [ "system" "nixos" "label" ])
+  ];
+
+  options.system = {
+
+    nixos.version = mkOption {
+      internal = true;
+      type = types.str;
+      description = "The full NixOS version (e.g. <literal>16.03.1160.f2d4ee1</literal>).";
+    };
+
+    nixos.release = mkOption {
+      readOnly = true;
+      type = types.str;
+      default = trivial.release;
+      description = "The NixOS release (e.g. <literal>16.03</literal>).";
+    };
+
+    nixos.versionSuffix = mkOption {
+      internal = true;
+      type = types.str;
+      default = trivial.versionSuffix;
+      description = "The NixOS version suffix (e.g. <literal>1160.f2d4ee1</literal>).";
+    };
+
+    nixos.revision = mkOption {
+      internal = true;
+      type = types.nullOr types.str;
+      default = trivial.revisionWithDefault null;
+      description = "The Git revision from which this NixOS configuration was built.";
+    };
+
+    nixos.codeName = mkOption {
+      readOnly = true;
+      type = types.str;
+      default = trivial.codeName;
+      description = "The NixOS release code name (e.g. <literal>Emu</literal>).";
+    };
+
+    stateVersion = mkOption {
+      type = types.str;
+      default = cfg.release;
+      defaultText = literalExpression "config.${opt.release}";
+      description = ''
+        Every once in a while, a new NixOS release may change
+        configuration defaults in a way incompatible with stateful
+        data. For instance, if the default version of PostgreSQL
+        changes, the new version will probably be unable to read your
+        existing databases. To prevent such breakage, you should set the
+        value of this option to the NixOS release with which you want
+        to be compatible. The effect is that NixOS will use
+        defaults corresponding to the specified release (such as using
+        an older version of PostgreSQL).
+        It‘s perfectly fine and recommended to leave this value at the
+        release version of the first install of this system.
+        Changing this option will not upgrade your system. In fact it
+        is meant to stay constant exactly when you upgrade your system.
+        You should only bump this option, if you are sure that you can
+        or have migrated all state on your system which is affected
+        by this option.
+      '';
+    };
+
+    defaultChannel = mkOption {
+      internal = true;
+      type = types.str;
+      default = "https://nixos.org/channels/nixos-unstable";
+      description = "Default NixOS channel to which the root user is subscribed.";
+    };
+
+    configurationRevision = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      description = "The Git revision of the top-level flake from which this configuration was built.";
+    };
+
+  };
+
+  config = {
+
+    system.nixos = {
+      # These defaults are set here rather than up there so that
+      # changing them would not rebuild the manual
+      version = mkDefault (cfg.release + cfg.versionSuffix);
+    };
+
+    # Generate /etc/os-release.  See
+    # https://www.freedesktop.org/software/systemd/man/os-release.html for the
+    # format.
+    environment.etc = {
+      "lsb-release".text = attrsToText {
+        LSB_VERSION = "${cfg.release} (${cfg.codeName})";
+        DISTRIB_ID = "nixos";
+        DISTRIB_RELEASE = cfg.release;
+        DISTRIB_CODENAME = toLower cfg.codeName;
+        DISTRIB_DESCRIPTION = "NixOS ${cfg.release} (${cfg.codeName})";
+      };
+
+      "os-release".text = attrsToText {
+        NAME = "NixOS";
+        ID = "nixos";
+        VERSION = "${cfg.release} (${cfg.codeName})";
+        VERSION_CODENAME = toLower cfg.codeName;
+        VERSION_ID = cfg.release;
+        BUILD_ID = cfg.version;
+        PRETTY_NAME = "NixOS ${cfg.release} (${cfg.codeName})";
+        LOGO = "nix-snowflake";
+        HOME_URL = "https://nixos.org/";
+        DOCUMENTATION_URL = "https://nixos.org/learn.html";
+        SUPPORT_URL = "https://nixos.org/community.html";
+        BUG_REPORT_URL = "https://github.com/NixOS/nixpkgs/issues";
+      };
+    };
+  };
+
+  # uses version info nixpkgs, which requires a full nixpkgs path
+  meta.buildDocsInSandbox = false;
+}
diff --git a/nixos/modules/misc/wordlist.nix b/nixos/modules/misc/wordlist.nix
new file mode 100644
index 00000000000..988b522d743
--- /dev/null
+++ b/nixos/modules/misc/wordlist.nix
@@ -0,0 +1,59 @@
+{ config, lib, pkgs, ... }:
+with lib;
+let
+  concatAndSort = name: files: pkgs.runCommand name {} ''
+    awk 1 ${lib.escapeShellArgs files} | sed '{ /^\s*$/d; s/^\s\+//; s/\s\+$// }' | sort | uniq > $out
+  '';
+in
+{
+  options = {
+    environment.wordlist = {
+      enable = mkEnableOption "environment variables for lists of words";
+
+      lists = mkOption {
+        type = types.attrsOf (types.nonEmptyListOf types.path);
+
+        default = {
+          WORDLIST = [ "${pkgs.scowl}/share/dict/words.txt" ];
+        };
+
+        defaultText = literalExpression ''
+          {
+            WORDLIST = [ "''${pkgs.scowl}/share/dict/words.txt" ];
+          }
+        '';
+
+        description = ''
+          A set with the key names being the environment variable you'd like to
+          set and the values being a list of paths to text documents containing
+          lists of words. The various files will be merged, sorted, duplicates
+          removed, and extraneous spacing removed.
+
+          If you have a handful of words that you want to add to an already
+          existing wordlist, you may find `builtins.toFile` useful for this
+          task.
+        '';
+
+        example = literalExpression ''
+          {
+            WORDLIST = [ "''${pkgs.scowl}/share/dict/words.txt" ];
+            AUGMENTED_WORDLIST = [
+              "''${pkgs.scowl}/share/dict/words.txt"
+              "''${pkgs.scowl}/share/dict/words.variants.txt"
+              (builtins.toFile "extra-words" '''
+                desynchonization
+                oobleck''')
+            ];
+          }
+        '';
+      };
+    };
+  };
+
+  config = mkIf config.environment.wordlist.enable {
+    environment.variables =
+      lib.mapAttrs
+        (name: value: "${concatAndSort "wordlist-${name}" value}")
+        config.environment.wordlist.lists;
+  };
+}