about summary refs log tree commit diff
path: root/nixos/modules
diff options
context:
space:
mode:
Diffstat (limited to 'nixos/modules')
-rw-r--r--nixos/modules/config/fonts/fontdir.nix1
-rw-r--r--nixos/modules/config/fonts/ghostscript.nix1
-rw-r--r--nixos/modules/config/ldap.nix2
-rw-r--r--nixos/modules/config/nsswitch.nix153
-rw-r--r--nixos/modules/hardware/all-firmware.nix1
-rw-r--r--nixos/modules/installer/cd-dvd/installation-cd-base.nix5
-rw-r--r--nixos/modules/installer/tools/nixos-install.sh8
-rw-r--r--nixos/modules/module-list.nix3
-rw-r--r--nixos/modules/programs/chromium.nix10
-rw-r--r--nixos/modules/programs/fish.nix4
-rw-r--r--nixos/modules/programs/singularity.nix4
-rw-r--r--nixos/modules/programs/venus.nix2
-rw-r--r--nixos/modules/programs/xonsh.nix27
-rw-r--r--nixos/modules/rename.nix4
-rw-r--r--nixos/modules/security/acme.nix12
-rw-r--r--nixos/modules/security/acme.xml247
-rw-r--r--nixos/modules/security/apparmor-suid.nix1
-rw-r--r--nixos/modules/security/google_oslogin.nix1
-rw-r--r--nixos/modules/security/pam.nix27
-rw-r--r--nixos/modules/security/prey.nix51
-rw-r--r--nixos/modules/services/backup/znapzend.nix35
-rw-r--r--nixos/modules/services/continuous-integration/gitlab-runner.nix540
-rw-r--r--nixos/modules/services/databases/openldap.nix4
-rw-r--r--nixos/modules/services/databases/postgresql.nix8
-rw-r--r--nixos/modules/services/editors/emacs.xml2
-rw-r--r--nixos/modules/services/mail/postfix.nix1
-rw-r--r--nixos/modules/services/mail/roundcube.nix2
-rw-r--r--nixos/modules/services/mail/spamassassin.nix1
-rw-r--r--nixos/modules/services/misc/autofs.nix1
-rwxr-xr-xnixos/modules/services/misc/confd.nix2
-rw-r--r--nixos/modules/services/misc/disnix.nix5
-rw-r--r--nixos/modules/services/misc/etcd.nix2
-rw-r--r--nixos/modules/services/misc/gitea.nix162
-rw-r--r--nixos/modules/services/misc/gitlab.nix2
-rw-r--r--nixos/modules/services/misc/gogs.nix6
-rw-r--r--nixos/modules/services/misc/leaps.nix2
-rw-r--r--nixos/modules/services/misc/nix-daemon.nix5
-rw-r--r--nixos/modules/services/misc/octoprint.nix9
-rw-r--r--nixos/modules/services/misc/pykms.nix1
-rw-r--r--nixos/modules/services/misc/ssm-agent.nix3
-rw-r--r--nixos/modules/services/misc/sssd.nix5
-rw-r--r--nixos/modules/services/monitoring/bosun.nix2
-rw-r--r--nixos/modules/services/monitoring/grafana-reporter.nix2
-rw-r--r--nixos/modules/services/monitoring/grafana.nix2
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/snmp.nix2
-rw-r--r--nixos/modules/services/monitoring/scollector.nix2
-rw-r--r--nixos/modules/services/network-filesystems/ipfs.nix4
-rw-r--r--nixos/modules/services/networking/consul.nix8
-rw-r--r--nixos/modules/services/networking/flannel.nix6
-rw-r--r--nixos/modules/services/networking/gogoclient.nix1
-rw-r--r--nixos/modules/services/networking/hostapd.nix39
-rw-r--r--nixos/modules/services/networking/openfire.nix1
-rw-r--r--nixos/modules/services/networking/prosody.nix392
-rw-r--r--nixos/modules/services/networking/prosody.xml88
-rw-r--r--nixos/modules/services/networking/skydns.nix2
-rw-r--r--nixos/modules/services/networking/ssh/lshd.nix6
-rw-r--r--nixos/modules/services/networking/tcpcrypt.nix1
-rw-r--r--nixos/modules/services/networking/thelounge.nix1
-rw-r--r--nixos/modules/services/networking/wicd.nix1
-rw-r--r--nixos/modules/services/networking/yggdrasil.nix11
-rw-r--r--nixos/modules/services/printing/cupsd.nix16
-rw-r--r--nixos/modules/services/security/hologram-agent.nix4
-rw-r--r--nixos/modules/services/security/hologram-server.nix2
-rw-r--r--nixos/modules/services/security/oauth2_proxy.nix2
-rw-r--r--nixos/modules/services/torrent/deluge.nix4
-rw-r--r--nixos/modules/services/web-apps/mattermost.nix2
-rw-r--r--nixos/modules/services/web-apps/mediawiki.nix23
-rw-r--r--nixos/modules/services/web-apps/nextcloud.nix4
-rw-r--r--nixos/modules/services/web-servers/apache-httpd/default.nix2
-rw-r--r--nixos/modules/services/web-servers/jboss/default.nix1
-rw-r--r--nixos/modules/services/web-servers/unit/default.nix30
-rw-r--r--nixos/modules/services/x11/desktop-managers/pantheon.nix2
-rw-r--r--nixos/modules/services/x11/desktop-managers/pantheon.xml2
-rw-r--r--nixos/modules/services/x11/display-managers/gdm.nix3
-rw-r--r--nixos/modules/services/x11/hardware/digimend.nix7
-rw-r--r--nixos/modules/services/x11/picom.nix187
-rw-r--r--nixos/modules/system/boot/initrd-ssh.nix8
-rw-r--r--nixos/modules/system/boot/networkd.nix139
-rw-r--r--nixos/modules/system/boot/resolved.nix4
-rw-r--r--nixos/modules/system/boot/systemd.nix24
-rw-r--r--nixos/modules/virtualisation/cri-o.nix29
-rw-r--r--nixos/modules/virtualisation/ecs-agent.nix3
-rw-r--r--nixos/modules/virtualisation/lxd.nix2
-rw-r--r--nixos/modules/virtualisation/nixos-containers.nix2
-rw-r--r--nixos/modules/virtualisation/oci-containers.nix (renamed from nixos/modules/virtualisation/docker-containers.nix)124
-rw-r--r--nixos/modules/virtualisation/podman.nix60
-rw-r--r--nixos/modules/virtualisation/qemu-vm.nix15
-rw-r--r--nixos/modules/virtualisation/xen-dom0.nix11
88 files changed, 1990 insertions, 655 deletions
diff --git a/nixos/modules/config/fonts/fontdir.nix b/nixos/modules/config/fonts/fontdir.nix
index cc70fbf8744d..a6aa84ae8224 100644
--- a/nixos/modules/config/fonts/fontdir.nix
+++ b/nixos/modules/config/fonts/fontdir.nix
@@ -25,6 +25,7 @@ in
     fonts = {
 
       enableFontDir = mkOption {
+        type = types.bool;
         default = false;
         description = ''
           Whether to create a directory with links to all fonts in
diff --git a/nixos/modules/config/fonts/ghostscript.nix b/nixos/modules/config/fonts/ghostscript.nix
index 1c62a525de94..b1dd81bf2d2e 100644
--- a/nixos/modules/config/fonts/ghostscript.nix
+++ b/nixos/modules/config/fonts/ghostscript.nix
@@ -9,6 +9,7 @@ with lib;
     fonts = {
 
       enableGhostscriptFonts = mkOption {
+        type = types.bool;
         default = false;
         description = ''
           Whether to add the fonts provided by Ghostscript (such as
diff --git a/nixos/modules/config/ldap.nix b/nixos/modules/config/ldap.nix
index b554f197dc4b..4c8b527676b2 100644
--- a/nixos/modules/config/ldap.nix
+++ b/nixos/modules/config/ldap.nix
@@ -88,6 +88,7 @@ in
       };
 
       useTLS = mkOption {
+        type = types.bool;
         default = false;
         description = ''
           If enabled, use TLS (encryption) over an LDAP (port 389)
@@ -109,6 +110,7 @@ in
 
       daemon = {
         enable = mkOption {
+          type = types.bool;
           default = false;
           description = ''
             Whether to let the nslcd daemon (nss-pam-ldapd) handle the
diff --git a/nixos/modules/config/nsswitch.nix b/nixos/modules/config/nsswitch.nix
index 13277fe56e42..22ddb3490c8e 100644
--- a/nixos/modules/config/nsswitch.nix
+++ b/nixos/modules/config/nsswitch.nix
@@ -8,37 +8,28 @@ let
 
   # only with nscd up and running we can load NSS modules that are not integrated in NSS
   canLoadExternalModules = config.services.nscd.enable;
-  myhostname = canLoadExternalModules;
-  mymachines = canLoadExternalModules;
+  # XXX Move these to their respective modules
   nssmdns = canLoadExternalModules && config.services.avahi.nssmdns;
   nsswins = canLoadExternalModules && config.services.samba.nsswins;
   ldap = canLoadExternalModules && (config.users.ldap.enable && config.users.ldap.nsswitch);
-  sssd = canLoadExternalModules && config.services.sssd.enable;
-  resolved = canLoadExternalModules && config.services.resolved.enable;
-  googleOsLogin = canLoadExternalModules && config.security.googleOsLogin.enable;
-
-  hostArray = [ "files" ]
-    ++ optional mymachines "mymachines"
-    ++ optional nssmdns "mdns_minimal [NOTFOUND=return]"
-    ++ optional nsswins "wins"
-    ++ optional resolved "resolve [!UNAVAIL=return]"
-    ++ [ "dns" ]
-    ++ optional nssmdns "mdns"
-    ++ optional myhostname "myhostname";
-
-  passwdArray = [ "files" ]
-    ++ optional sssd "sss"
-    ++ optional ldap "ldap"
-    ++ optional mymachines "mymachines"
-    ++ optional googleOsLogin "cache_oslogin oslogin"
-    ++ [ "systemd" ];
-
-  shadowArray = [ "files" ]
-    ++ optional sssd "sss"
-    ++ optional ldap "ldap";
-
-  servicesArray = [ "files" ]
-    ++ optional sssd "sss";
+
+  hostArray = mkMerge [
+    (mkBefore [ "files" ])
+    (mkIf nssmdns [ "mdns_minimal [NOTFOUND=return]" ])
+    (mkIf nsswins [ "wins" ])
+    (mkAfter [ "dns" ])
+    (mkIf nssmdns (mkOrder 1501 [ "mdns" ])) # 1501 to ensure it's after dns
+  ];
+
+  passwdArray = mkMerge [
+    (mkBefore [ "files" ])
+    (mkIf ldap [ "ldap" ])
+  ];
+
+  shadowArray = mkMerge [
+    (mkBefore [ "files" ])
+    (mkIf ldap [ "ldap" ])
+  ];
 
 in {
   options = {
@@ -61,17 +52,73 @@ in {
         };
     };
 
-    system.nssHosts = mkOption {
-      type = types.listOf types.str;
-      default = [];
-      example = [ "mdns" ];
-      description = ''
-        List of host entries to configure in <filename>/etc/nsswitch.conf</filename>.
-      '';
-    };
+    system.nssDatabases = {
+      passwd = mkOption {
+        type = types.listOf types.str;
+        description = ''
+          List of passwd entries to configure in <filename>/etc/nsswitch.conf</filename>.
+
+          Note that "files" is always prepended while "systemd" is appended if nscd is enabled.
+
+          This option only takes effect if nscd is enabled.
+        '';
+        default = [];
+      };
+
+      group = mkOption {
+        type = types.listOf types.str;
+        description = ''
+          List of group entries to configure in <filename>/etc/nsswitch.conf</filename>.
+
+          Note that "files" is always prepended while "systemd" is appended if nscd is enabled.
+
+          This option only takes effect if nscd is enabled.
+        '';
+        default = [];
+      };
+
+      shadow = mkOption {
+        type = types.listOf types.str;
+        description = ''
+          List of shadow entries to configure in <filename>/etc/nsswitch.conf</filename>.
 
+          Note that "files" is always prepended.
+
+          This option only takes effect if nscd is enabled.
+        '';
+        default = [];
+      };
+
+      hosts = mkOption {
+        type = types.listOf types.str;
+        description = ''
+          List of hosts entries to configure in <filename>/etc/nsswitch.conf</filename>.
+
+          Note that "files" is always prepended, and "dns" and "myhostname" are always appended.
+
+          This option only takes effect if nscd is enabled.
+        '';
+        default = [];
+      };
+
+      services = mkOption {
+        type = types.listOf types.str;
+        description = ''
+          List of services entries to configure in <filename>/etc/nsswitch.conf</filename>.
+
+          Note that "files" is always prepended.
+
+          This option only takes effect if nscd is enabled.
+        '';
+        default = [];
+      };
+    };
   };
 
+  imports = [
+    (mkRenamedOptionModule [ "system" "nssHosts" ] [ "system" "nssDatabases" "hosts" ])
+  ];
+
   config = {
     assertions = [
       {
@@ -79,38 +126,30 @@ in {
         assertion = config.system.nssModules.path != "" -> canLoadExternalModules;
         message = "Loading NSS modules from path ${config.system.nssModules.path} requires nscd being enabled.";
       }
-      {
-        # resolved does not need to add to nssModules, therefore needs an extra assertion
-        assertion = resolved -> canLoadExternalModules;
-        message = "Loading systemd-resolved's nss-resolve NSS module requires nscd being enabled.";
-      }
     ];
 
     # Name Service Switch configuration file.  Required by the C
-    # library.  !!! Factor out the mdns stuff.  The avahi module
-    # should define an option used by this module.
+    # library.
     environment.etc."nsswitch.conf".text = ''
-      passwd:    ${concatStringsSep " " passwdArray}
-      group:     ${concatStringsSep " " passwdArray}
-      shadow:    ${concatStringsSep " " shadowArray}
+      passwd:    ${concatStringsSep " " config.system.nssDatabases.passwd}
+      group:     ${concatStringsSep " " config.system.nssDatabases.group}
+      shadow:    ${concatStringsSep " " config.system.nssDatabases.shadow}
 
-      hosts:     ${concatStringsSep " " config.system.nssHosts}
+      hosts:     ${concatStringsSep " " config.system.nssDatabases.hosts}
       networks:  files
 
       ethers:    files
-      services:  ${concatStringsSep " " servicesArray}
+      services:  ${concatStringsSep " " config.system.nssDatabases.services}
       protocols: files
       rpc:       files
     '';
 
-    system.nssHosts = hostArray;
-
-    # Systemd provides nss-myhostname to ensure that our hostname
-    # always resolves to a valid IP address.  It returns all locally
-    # configured IP addresses, or ::1 and 127.0.0.2 as
-    # fallbacks. Systemd also provides nss-mymachines to return IP
-    # addresses of local containers.
-    system.nssModules = (optionals canLoadExternalModules [ config.systemd.package.out ])
-      ++ optional googleOsLogin pkgs.google-compute-engine-oslogin.out;
+    system.nssDatabases = {
+      passwd = passwdArray;
+      group = passwdArray;
+      shadow = shadowArray;
+      hosts = hostArray;
+      services = mkBefore [ "files" ];
+    };
   };
 }
diff --git a/nixos/modules/hardware/all-firmware.nix b/nixos/modules/hardware/all-firmware.nix
index 16be8bcfdd7d..b07edb0f6acd 100644
--- a/nixos/modules/hardware/all-firmware.nix
+++ b/nixos/modules/hardware/all-firmware.nix
@@ -51,6 +51,7 @@ in {
         rtlwifi_new-firmware
         zd1211fw
         alsa-firmware
+        sof-firmware
         openelec-dvb-firmware
       ] ++ optional (pkgs.stdenv.hostPlatform.isAarch32 || pkgs.stdenv.hostPlatform.isAarch64) raspberrypiWirelessFirmware
         ++ optionals (versionOlder config.boot.kernelPackages.kernel.version "4.13") [
diff --git a/nixos/modules/installer/cd-dvd/installation-cd-base.nix b/nixos/modules/installer/cd-dvd/installation-cd-base.nix
index 9303e8fde13a..6c7ea293e8ac 100644
--- a/nixos/modules/installer/cd-dvd/installation-cd-base.nix
+++ b/nixos/modules/installer/cd-dvd/installation-cd-base.nix
@@ -1,7 +1,7 @@
 # This module contains the basic configuration for building a NixOS
 # installation CD.
 
-{ config, lib, pkgs, ... }:
+{ config, lib, options, pkgs, ... }:
 
 with lib;
 
@@ -15,6 +15,9 @@ with lib;
       ../../profiles/installation-device.nix
     ];
 
+  # Adds terminus_font for people with HiDPI displays
+  console.packages = options.console.packages.default ++ [ pkgs.terminus_font ];
+
   # ISO naming.
   isoImage.isoName = "${config.isoImage.isoBaseName}-${config.system.nixos.label}-${pkgs.stdenv.hostPlatform.system}.iso";
 
diff --git a/nixos/modules/installer/tools/nixos-install.sh b/nixos/modules/installer/tools/nixos-install.sh
index a3ff3fe2c0c1..6e1d56af2ae5 100644
--- a/nixos/modules/installer/tools/nixos-install.sh
+++ b/nixos/modules/installer/tools/nixos-install.sh
@@ -15,7 +15,6 @@ mountPoint=/mnt
 channelPath=
 system=
 verbosity=()
-buildLogs=
 
 while [ "$#" -gt 0 ]; do
     i="$1"; shift 1
@@ -60,9 +59,6 @@ while [ "$#" -gt 0 ]; do
         -v*|--verbose)
             verbosity+=("$i")
             ;;
-        -L|--print-build-logs)
-            buildLogs="$i"
-            ;;
         *)
             echo "$0: unknown option \`$i'"
             exit 1
@@ -100,9 +96,9 @@ sub="auto?trusted=1"
 if [[ -z $system ]]; then
     echo "building the configuration in $NIXOS_CONFIG..."
     outLink="$tmpdir/system"
-    nix build --out-link "$outLink" --store "$mountPoint" "${extraBuildFlags[@]}" \
+    nix-build --out-link "$outLink" --store "$mountPoint" "${extraBuildFlags[@]}" \
         --extra-substituters "$sub" \
-        -f '<nixpkgs/nixos>' system -I "nixos-config=$NIXOS_CONFIG" ${verbosity[@]} ${buildLogs}
+        '<nixpkgs/nixos>' -A system -I "nixos-config=$NIXOS_CONFIG" ${verbosity[@]}
     system=$(readlink -f $outLink)
 fi
 
diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix
index 7244a7e0a894..28f536056bf1 100644
--- a/nixos/modules/module-list.nix
+++ b/nixos/modules/module-list.nix
@@ -196,7 +196,6 @@
   ./security/pam_usb.nix
   ./security/pam_mount.nix
   ./security/polkit.nix
-  ./security/prey.nix
   ./security/rngd.nix
   ./security/rtkit.nix
   ./security/wrappers/default.nix
@@ -985,9 +984,9 @@
   ./virtualisation/container-config.nix
   ./virtualisation/containers.nix
   ./virtualisation/nixos-containers.nix
+  ./virtualisation/oci-containers.nix
   ./virtualisation/cri-o.nix
   ./virtualisation/docker.nix
-  ./virtualisation/docker-containers.nix
   ./virtualisation/ecs-agent.nix
   ./virtualisation/libvirtd.nix
   ./virtualisation/lxc.nix
diff --git a/nixos/modules/programs/chromium.nix b/nixos/modules/programs/chromium.nix
index 41c49db8c71c..16c063ebc89d 100644
--- a/nixos/modules/programs/chromium.nix
+++ b/nixos/modules/programs/chromium.nix
@@ -9,9 +9,7 @@ let
     HomepageLocation = cfg.homepageLocation;
     DefaultSearchProviderSearchURL = cfg.defaultSearchProviderSearchURL;
     DefaultSearchProviderSuggestURL = cfg.defaultSearchProviderSuggestURL;
-    ExtensionInstallForcelist = map (extension:
-      "${extension};https://clients2.google.com/service/update2/crx"
-    ) cfg.extensions;
+    ExtensionInstallForcelist = cfg.extensions;
   };
 in
 
@@ -28,7 +26,11 @@ in
           List of chromium extensions to install.
           For list of plugins ids see id in url of extensions on
           <link xlink:href="https://chrome.google.com/webstore/category/extensions">chrome web store</link>
-          page.
+          page. To install a chromium extension not included in the chrome web
+          store, append to the extension id a semicolon ";" followed by a URL
+          pointing to an Update Manifest XML file. See
+          <link xlink:href="https://www.chromium.org/administrators/policy-list-3#ExtensionInstallForcelist">ExtensionInstallForcelist</link>
+          for additional details.
         '';
         default = [];
         example = literalExample ''
diff --git a/nixos/modules/programs/fish.nix b/nixos/modules/programs/fish.nix
index 87f6816e4ac0..48b324a0fe83 100644
--- a/nixos/modules/programs/fish.nix
+++ b/nixos/modules/programs/fish.nix
@@ -178,6 +178,10 @@ in
         set -l post (string join0 $fish_complete_path | string match --regex "[^\x00]*generated_completions.*" | string split0 | string match -er ".")
         set fish_complete_path $prev "/etc/fish/generated_completions" $post
       end
+      # prevent fish from generating completions on first run
+      if not test -d $__fish_user_data_dir/generated_completions
+        ${pkgs.coreutils}/bin/mkdir $__fish_user_data_dir/generated_completions
+      end
     '';
 
     environment.etc."fish/generated_completions".source =
diff --git a/nixos/modules/programs/singularity.nix b/nixos/modules/programs/singularity.nix
index b27e122bd1d9..6ac64a81fc24 100644
--- a/nixos/modules/programs/singularity.nix
+++ b/nixos/modules/programs/singularity.nix
@@ -5,8 +5,8 @@ let
   cfg = config.programs.singularity;
   singularity = pkgs.singularity.overrideAttrs (attrs : {
     installPhase = attrs.installPhase + ''
-      mv $bin/libexec/singularity/bin/starter-suid $bin/libexec/singularity/bin/starter-suid.orig
-      ln -s /run/wrappers/bin/singularity-suid $bin/libexec/singularity/bin/starter-suid
+      mv $out/libexec/singularity/bin/starter-suid $out/libexec/singularity/bin/starter-suid.orig
+      ln -s /run/wrappers/bin/singularity-suid $out/libexec/singularity/bin/starter-suid
     '';
   });
 in {
diff --git a/nixos/modules/programs/venus.nix b/nixos/modules/programs/venus.nix
index 110570ac3f06..58faf38777d0 100644
--- a/nixos/modules/programs/venus.nix
+++ b/nixos/modules/programs/venus.nix
@@ -75,7 +75,7 @@ in
       };
 
       link = mkOption {
-        default = "http://planet.nixos.org";
+        default = "https://planet.nixos.org";
         type = types.str;
         description = ''
           Link to the main page.
diff --git a/nixos/modules/programs/xonsh.nix b/nixos/modules/programs/xonsh.nix
index 1590020f7b64..c06fd1655c20 100644
--- a/nixos/modules/programs/xonsh.nix
+++ b/nixos/modules/programs/xonsh.nix
@@ -45,7 +45,32 @@ in
 
   config = mkIf cfg.enable {
 
-    environment.etc.xonshrc.text = cfg.config;
+    environment.etc.xonshrc.text = ''
+      # /etc/xonshrc: DO NOT EDIT -- this file has been generated automatically.
+
+
+      if not ''${...}.get('__NIXOS_SET_ENVIRONMENT_DONE'):
+          # The NixOS environment and thereby also $PATH
+          # haven't been fully set up at this point. But
+          # `source-bash` below requires `bash` to be on $PATH,
+          # so add an entry with bash's location:
+          $PATH.add('${pkgs.bash}/bin')
+
+          # Stash xonsh's ls alias, so that we don't get a collision
+          # with Bash's ls alias from environment.shellAliases:
+          _ls_alias = aliases.pop('ls', None)
+
+          # Source the NixOS environment config.
+          source-bash "${config.system.build.setEnvironment}"
+
+          # Restore xonsh's ls alias, overriding that from Bash (if any).
+          if _ls_alias is not None:
+              aliases['ls'] = _ls_alias
+          del _ls_alias
+
+
+      ${cfg.config}
+    '';
 
     environment.systemPackages = [ cfg.package ];
 
diff --git a/nixos/modules/rename.nix b/nixos/modules/rename.nix
index 410db8fd84e7..a946268494ed 100644
--- a/nixos/modules/rename.nix
+++ b/nixos/modules/rename.nix
@@ -49,6 +49,10 @@ with lib;
       simply add the brightnessctl package to environment.systemPackages.
     '')
 
+    (mkRemovedOptionModule ["services" "prey" ] ''
+      prey-bash-client is deprecated upstream
+    '')
+
     # Do NOT add any option renames here, see top of the file
   ];
 }
diff --git a/nixos/modules/security/acme.nix b/nixos/modules/security/acme.nix
index 39976380e3b4..776ef07d716c 100644
--- a/nixos/modules/security/acme.nix
+++ b/nixos/modules/security/acme.nix
@@ -87,19 +87,19 @@ let
         default = {};
         example = literalExample ''
           {
-            "example.org" = "/srv/http/nginx";
+            "example.org" = null;
             "mydomain.org" = null;
           }
         '';
         description = ''
-          A list of extra domain names, which are included in the one certificate to be issued, with their
-          own server roots if needed.
+          A list of extra domain names, which are included in the one certificate to be issued.
+          Setting a distinct server root is deprecated and not functional in 20.03+
         '';
       };
 
       keyType = mkOption {
         type = types.str;
-        default = "ec384";
+        default = "ec256";
         description = ''
           Key type to use for private keys.
           For an up to date list of supported values check the --key-type option
@@ -250,7 +250,7 @@ in
             "example.com" = {
               webroot = "/var/www/challenges/";
               email = "foo@example.com";
-              extraDomains = { "www.example.com" = null; "foo.example.com" = "/var/www/foo/"; };
+              extraDomains = { "www.example.com" = null; "foo.example.com" = null; };
             };
             "bar.example.com" = {
               webroot = "/var/www/challenges/";
@@ -458,7 +458,7 @@ in
   ];
 
   meta = {
-    maintainers = with lib.maintainers; [ abbradar fpletz globin m1cr0man ];
+    maintainers = lib.teams.acme.members;
     doc = ./acme.xml;
   };
 }
diff --git a/nixos/modules/security/acme.xml b/nixos/modules/security/acme.xml
index 2b29c1174845..f802faee9749 100644
--- a/nixos/modules/security/acme.xml
+++ b/nixos/modules/security/acme.xml
@@ -6,92 +6,249 @@
  <title>SSL/TLS Certificates with ACME</title>
  <para>
   NixOS supports automatic domain validation &amp; certificate retrieval and
-  renewal using the ACME protocol. This is currently only implemented by and
-  for Let's Encrypt. The alternative ACME client <literal>lego</literal> is
-  used under the hood.
+  renewal using the ACME protocol. Any provider can be used, but by default
+  NixOS uses Let's Encrypt. The alternative ACME client <literal>lego</literal>
+  is used under the hood.
+ </para>
+ <para>
+  Automatic cert validation and configuration for Apache and Nginx virtual
+  hosts is included in NixOS, however if you would like to generate a wildcard
+  cert or you are not using a web server you will have to configure DNS
+  based validation.
  </para>
  <section xml:id="module-security-acme-prerequisites">
   <title>Prerequisites</title>
 
   <para>
-   You need to have a running HTTP server for verification. The server must
-   have a webroot defined that can serve
+   To use the ACME module, you must accept the provider's terms of service
+   by setting <literal><xref linkend="opt-security.acme.acceptTerms" /></literal>
+   to <literal>true</literal>. The Let's Encrypt ToS can be found
+   <link xlink:href="https://letsencrypt.org/repository/">here</link>.
+  </para>
+
+  <para>
+   You must also set an email address to be used when creating accounts with
+   Let's Encrypt. You can set this for all certs with
+   <literal><xref linkend="opt-security.acme.email" /></literal>
+   and/or on a per-cert basis with
+   <literal><xref linkend="opt-security.acme.certs._name_.email" /></literal>.
+   This address is only used for registration and renewal reminders,
+   and cannot be used to administer the certificates in any way.
+  </para>
+
+  <para>
+   Alternatively, you can use a different ACME server by changing the
+   <literal><xref linkend="opt-security.acme.server" /></literal> option
+   to a provider of your choosing, or just change the server for one cert with
+   <literal><xref linkend="opt-security.acme.certs._name_.server" /></literal>.
+  </para>
+
+  <para>
+   You will need an HTTP server or DNS server for verification. For HTTP,
+   the server must have a webroot defined that can serve
    <filename>.well-known/acme-challenge</filename>. This directory must be
-   writeable by the user that will run the ACME client.
+   writeable by the user that will run the ACME client. For DNS, you must
+   set up credentials with your provider/server for use with lego.
   </para>
+ </section>
+ <section xml:id="module-security-acme-nginx">
+  <title>Using ACME certificates in Nginx</title>
 
   <para>
-   For instance, this generic snippet could be used for Nginx:
+   NixOS supports fetching ACME certificates for you by setting
+   <literal><link linkend="opt-services.nginx.virtualHosts._name_.enableACME">enableACME</link>
+   = true;</literal> in a virtualHost config. We first create self-signed
+   placeholder certificates in place of the real ACME certs. The placeholder
+   certs are overwritten when the ACME certs arrive. For
+   <literal>foo.example.com</literal> the config would look like.
+  </para>
+
 <programlisting>
-http {
-  server {
-    server_name _;
-    listen 80;
-    listen [::]:80;
-
-    location /.well-known/acme-challenge {
-      root /var/www/challenges;
-    }
+<xref linkend="opt-security.acme.acceptTerms" /> = true;
+<xref linkend="opt-security.acme.email" /> = "admin+acme@example.com";
+services.nginx = {
+  <link linkend="opt-services.nginx.enable">enable</link> = true;
+  <link linkend="opt-services.nginx.virtualHosts">virtualHosts</link> = {
+    "foo.example.com" = {
+      <link linkend="opt-services.nginx.virtualHosts._name_.forceSSL">forceSSL</link> = true;
+      <link linkend="opt-services.nginx.virtualHosts._name_.enableACME">enableACME</link> = true;
+      # All serverAliases will be added as <link linkend="opt-security.acme.certs._name_.extraDomains">extra domains</link> on the certificate.
+      <link linkend="opt-services.nginx.virtualHosts._name_.serverAliases">serverAliases</link> = [ "bar.example.com" ];
+      locations."/" = {
+        <link linkend="opt-services.nginx.virtualHosts._name_.locations._name_.root">root</link> = "/var/www";
+      };
+    };
 
-    location / {
-      return 301 https://$host$request_uri;
-    }
-  }
+    # We can also add a different vhost and reuse the same certificate
+    # but we have to append extraDomains manually.
+    <link linkend="opt-security.acme.certs._name_.extraDomains">security.acme.certs."foo.example.com".extraDomains."baz.example.com"</link> = null;
+    "baz.example.com" = {
+      <link linkend="opt-services.nginx.virtualHosts._name_.forceSSL">forceSSL</link> = true;
+      <link linkend="opt-services.nginx.virtualHosts._name_.useACMEHost">useACMEHost</link> = "foo.example.com";
+      locations."/" = {
+        <link linkend="opt-services.nginx.virtualHosts._name_.locations._name_.root">root</link> = "/var/www";
+      };
+    };
+  };
 }
 </programlisting>
+ </section>
+ <section xml:id="module-security-acme-httpd">
+  <title>Using ACME certificates in Apache/httpd</title>
+
+  <para>
+   Using ACME certificates with Apache virtual hosts is identical
+   to using them with Nginx. The attribute names are all the same, just replace
+   "nginx" with "httpd" where appropriate.
   </para>
  </section>
  <section xml:id="module-security-acme-configuring">
-  <title>Configuring</title>
+  <title>Manual configuration of HTTP-01 validation</title>
 
   <para>
-   To enable ACME certificate retrieval &amp; renewal for a certificate for
-   <literal>foo.example.com</literal>, add the following in your
-   <filename>configuration.nix</filename>:
+   First off you will need to set up a virtual host to serve the challenges.
+   This example uses a vhost called <literal>certs.example.com</literal>, with
+   the intent that you will generate certs for all your vhosts and redirect
+   everyone to HTTPS.
+  </para>
+
+<programlisting>
+<xref linkend="opt-security.acme.acceptTerms" /> = true;
+<xref linkend="opt-security.acme.email" /> = "admin+acme@example.com";
+services.nginx = {
+  <link linkend="opt-services.nginx.enable">enable</link> = true;
+  <link linkend="opt-services.nginx.virtualHosts">virtualHosts</link> = {
+    "acmechallenge.example.com" = {
+      # Catchall vhost, will redirect users to HTTPS for all vhosts
+      <link linkend="opt-services.nginx.virtualHosts._name_.serverAliases">serverAliases</link> = [ "*.example.com" ];
+      # /var/lib/acme/.challenges must be writable by the ACME user
+      # and readable by the Nginx user.
+      # By default, this is the case.
+      locations."/.well-known/acme-challenge" = {
+        <link linkend="opt-services.nginx.virtualHosts._name_.locations._name_.root">root</link> = "/var/lib/acme/.challenges";
+      };
+      locations."/" = {
+        <link linkend="opt-services.nginx.virtualHosts._name_.locations._name_.return">return</link> = "301 https://$host$request_uri";
+      };
+    };
+  };
+}
+# Alternative config for Apache
+services.httpd = {
+  <link linkend="opt-services.httpd.enable">enable = true;</link>
+  <link linkend="opt-services.httpd.virtualHosts">virtualHosts</link> = {
+    "acmechallenge.example.com" = {
+      # Catchall vhost, will redirect users to HTTPS for all vhosts
+      <link linkend="opt-services.httpd.virtualHosts._name_.serverAliases">serverAliases</link> = [ "*.example.com" ];
+      # /var/lib/acme/.challenges must be writable by the ACME user and readable by the Apache user.
+      # By default, this is the case.
+      <link linkend="opt-services.httpd.virtualHosts._name_.documentRoot">documentRoot</link> = "/var/lib/acme/.challenges";
+      <link linkend="opt-services.httpd.virtualHosts._name_.extraConfig">extraConfig</link> = ''
+        RewriteEngine On
+        RewriteCond %{HTTPS} off
+        RewriteCond %{REQUEST_URI} !^/\.well-known/acme-challenge [NC]
+        RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI} [R=301]
+      '';
+    };
+  };
+}
+</programlisting>
+
+  <para>
+   Now you need to configure ACME to generate a certificate.
+  </para>
+
 <programlisting>
 <xref linkend="opt-security.acme.certs"/>."foo.example.com" = {
-  <link linkend="opt-security.acme.certs._name_.webroot">webroot</link> = "/var/www/challenges";
+  <link linkend="opt-security.acme.certs._name_.webroot">webroot</link> = "/var/lib/acme/.challenges";
   <link linkend="opt-security.acme.certs._name_.email">email</link> = "foo@example.com";
+  # Since we have a wildcard vhost to handle port 80,
+  # we can generate certs for anything!
+  # Just make sure your DNS resolves them.
+  <link linkend="opt-security.acme.certs._name_.extraDomains">extraDomains</link> = [ "mail.example.com" ];
 };
 </programlisting>
-  </para>
 
   <para>
    The private key <filename>key.pem</filename> and certificate
    <filename>fullchain.pem</filename> will be put into
    <filename>/var/lib/acme/foo.example.com</filename>.
   </para>
+
   <para>
    Refer to <xref linkend="ch-options" /> for all available configuration
    options for the <link linkend="opt-security.acme.certs">security.acme</link>
    module.
   </para>
  </section>
- <section xml:id="module-security-acme-nginx">
-  <title>Using ACME certificates in Nginx</title>
+ <section xml:id="module-security-acme-config-dns">
+  <title>Configuring ACME for DNS validation</title>
 
   <para>
-   NixOS supports fetching ACME certificates for you by setting
-   <literal><link linkend="opt-services.nginx.virtualHosts._name_.enableACME">enableACME</link>
-   = true;</literal> in a virtualHost config. We first create self-signed
-   placeholder certificates in place of the real ACME certs. The placeholder
-   certs are overwritten when the ACME certs arrive. For
-   <literal>foo.example.com</literal> the config would look like.
+   This is useful if you want to generate a wildcard certificate, since
+   ACME servers will only hand out wildcard certs over DNS validation.
+   There a number of supported DNS providers and servers you can utilise,
+   see the <link xlink:href="https://go-acme.github.io/lego/dns/">lego docs</link>
+   for provider/server specific configuration values. For the sake of these
+   docs, we will provide a fully self-hosted example using bind.
   </para>
 
 <programlisting>
-services.nginx = {
-  <link linkend="opt-services.nginx.enable">enable = true;</link>
-  <link linkend="opt-services.nginx.virtualHosts">virtualHosts</link> = {
-    "foo.example.com" = {
-      <link linkend="opt-services.nginx.virtualHosts._name_.forceSSL">forceSSL</link> = true;
-      <link linkend="opt-services.nginx.virtualHosts._name_.enableACME">enableACME</link> = true;
-      locations."/" = {
-        <link linkend="opt-services.nginx.virtualHosts._name_.locations._name_.root">root</link> = "/var/www";
-      };
-    };
-  };
+services.bind = {
+  <link linkend="opt-services.bind.enable">enable</link> = true;
+  <link linkend="opt-services.bind.extraConfig">extraConfig</link> = ''
+    include "/var/lib/secrets/dnskeys.conf";
+  '';
+  <link linkend="opt-services.bind.zones">zones</link> = [
+    rec {
+      name = "example.com";
+      file = "/var/db/bind/${name}";
+      master = true;
+      extraConfig = "allow-update { key rfc2136key.example.com.; };";
+    }
+  ];
 }
+
+# Now we can configure ACME
+<xref linkend="opt-security.acme.acceptTerms" /> = true;
+<xref linkend="opt-security.acme.email" /> = "admin+acme@example.com";
+<xref linkend="opt-security.acme.certs" />."example.com" = {
+  <link linkend="opt-security.acme.certs._name_.domain">domain</link> = "*.example.com";
+  <link linkend="opt-security.acme.certs._name_.dnsProvider">dnsProvider</link> = "rfc2136";
+  <link linkend="opt-security.acme.certs._name_.credentialsFile">credentialsFile</link> = "/var/lib/secrets/certs.secret";
+  # We don't need to wait for propagation since this is a local DNS server
+  <link linkend="opt-security.acme.certs._name_.dnsPropagationCheck">dnsPropagationCheck</link> = false;
+};
 </programlisting>
+
+  <para>
+   The <filename>dnskeys.conf</filename> and <filename>certs.secret</filename>
+   must be kept secure and thus you should not keep their contents in your
+   Nix config. Instead, generate them one time with these commands:
+  </para>
+
+<programlisting>
+mkdir -p /var/lib/secrets
+tsig-keygen rfc2136key.example.com &gt; /var/lib/secrets/dnskeys.conf
+chown named:root /var/lib/secrets/dnskeys.conf
+chmod 400 /var/lib/secrets/dnskeys.conf
+
+# Copy the secret value from the dnskeys.conf, and put it in
+# RFC2136_TSIG_SECRET below
+
+cat &gt; /var/lib/secrets/certs.secret &lt;&lt; EOF
+RFC2136_NAMESERVER='127.0.0.1:53'
+RFC2136_TSIG_ALGORITHM='hmac-sha256.'
+RFC2136_TSIG_KEY='rfc2136key.example.com'
+RFC2136_TSIG_SECRET='your secret key'
+EOF
+chmod 400 /var/lib/secrets/certs.secret
+</programlisting>
+
+  <para>
+   Now you're all set to generate certs! You should monitor the first invokation
+   by running <literal>systemctl start acme-example.com.service &amp;
+   journalctl -fu acme-example.com.service</literal> and watching its log output.
+  </para>
  </section>
 </chapter>
diff --git a/nixos/modules/security/apparmor-suid.nix b/nixos/modules/security/apparmor-suid.nix
index 3c93f5440ab5..6c479e070e2b 100644
--- a/nixos/modules/security/apparmor-suid.nix
+++ b/nixos/modules/security/apparmor-suid.nix
@@ -9,6 +9,7 @@ with lib;
   ];
 
   options.security.apparmor.confineSUIDApplications = mkOption {
+    type = types.bool;
     default = true;
     description = ''
       Install AppArmor profiles for commonly-used SUID application
diff --git a/nixos/modules/security/google_oslogin.nix b/nixos/modules/security/google_oslogin.nix
index 6f9962e1d626..78c2089baeb9 100644
--- a/nixos/modules/security/google_oslogin.nix
+++ b/nixos/modules/security/google_oslogin.nix
@@ -49,6 +49,7 @@ in
 
     # enable the nss module, so user lookups etc. work
     system.nssModules = [ package ];
+    system.nssDatabases.passwd = [ "cache_oslogin" "oslogin" ];
 
     # Ugly: sshd refuses to start if a store path is given because /nix/store is group-writable.
     # So indirect by a symlink.
diff --git a/nixos/modules/security/pam.nix b/nixos/modules/security/pam.nix
index bfc2a881387f..b99316803f35 100644
--- a/nixos/modules/security/pam.nix
+++ b/nixos/modules/security/pam.nix
@@ -219,6 +219,14 @@ let
         '';
       };
 
+      nodelay = mkOption {
+        default = false;
+        type = types.bool;
+        description = ''
+          Wheather the delay after typing a wrong password should be disabled.
+        '';
+      };
+
       requireWheel = mkOption {
         default = false;
         type = types.bool;
@@ -366,7 +374,7 @@ let
             || cfg.enableGnomeKeyring
             || cfg.googleAuthenticator.enable
             || cfg.duoSecurity.enable)) ''
-              auth required pam_unix.so ${optionalString cfg.allowNullPassword "nullok"} likeauth
+              auth required pam_unix.so ${optionalString cfg.allowNullPassword "nullok"} ${optionalString cfg.nodelay "nodelay"} likeauth
               ${optionalString config.security.pam.enableEcryptfs
                 "auth optional ${pkgs.ecryptfs}/lib/security/pam_ecryptfs.so unwrap"}
               ${optionalString cfg.pamMount
@@ -382,7 +390,7 @@ let
                 "auth required ${pkgs.duo-unix}/lib/security/pam_duo.so"}
             '') + ''
           ${optionalString cfg.unixAuth
-              "auth sufficient pam_unix.so ${optionalString cfg.allowNullPassword "nullok"} likeauth try_first_pass"}
+              "auth sufficient pam_unix.so ${optionalString cfg.allowNullPassword "nullok"} ${optionalString cfg.nodelay "nodelay"} likeauth try_first_pass"}
           ${optionalString cfg.otpwAuth
               "auth sufficient ${pkgs.otpw}/lib/security/pam_otpw.so"}
           ${optionalString use_ldap
@@ -545,6 +553,7 @@ in
     };
 
     security.pam.enableSSHAgentAuth = mkOption {
+      type = types.bool;
       default = false;
       description =
         ''
@@ -555,12 +564,7 @@ in
         '';
     };
 
-    security.pam.enableOTPW = mkOption {
-      default = false;
-      description = ''
-        Enable the OTPW (one-time password) PAM module.
-      '';
-    };
+    security.pam.enableOTPW = mkEnableOption "the OTPW (one-time password) PAM module";
 
     security.pam.u2f = {
       enable = mkOption {
@@ -719,12 +723,7 @@ in
       };
     };
 
-    security.pam.enableEcryptfs = mkOption {
-      default = false;
-      description = ''
-        Enable eCryptfs PAM module (mounting ecryptfs home directory on login).
-      '';
-    };
+    security.pam.enableEcryptfs = mkEnableOption "eCryptfs PAM module (mounting ecryptfs home directory on login)";
 
     users.motd = mkOption {
       default = null;
diff --git a/nixos/modules/security/prey.nix b/nixos/modules/security/prey.nix
deleted file mode 100644
index b899ccb6c3e2..000000000000
--- a/nixos/modules/security/prey.nix
+++ /dev/null
@@ -1,51 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-with lib;
-
-let
-  cfg = config.services.prey;
-  myPrey = pkgs.prey-bash-client.override {
-    apiKey = cfg.apiKey;
-    deviceKey = cfg.deviceKey;
-  };
-in {
-  options = {
-
-    services.prey = {
-      enable = mkOption {
-        default = false;
-        type = types.bool;
-        description = ''
-          Enables the <link xlink:href="http://preyproject.com/" />
-          shell client. Be sure to specify both API and device keys.
-          Once enabled, a <command>cron</command> job will run every 15
-          minutes to report status information.
-        '';
-      };
-
-      deviceKey = mkOption {
-        type = types.str;
-        description = ''
-          <literal>Device key</literal> obtained by visiting
-          <link xlink:href="https://panel.preyproject.com/devices" />
-          and clicking on your device.
-        '';
-      };
-
-      apiKey = mkOption {
-        type = types.str;
-        description = ''
-          <literal>API key</literal> obtained from
-          <link xlink:href="https://panel.preyproject.com/profile" />.
-        '';
-      };
-    };
-
-  };
-
-  config = mkIf cfg.enable {
-      environment.systemPackages = [ myPrey ];
-      services.cron.systemCronJobs = [ "*/15 * * * * root ${myPrey}/prey.sh" ];
-  };
-
-}
diff --git a/nixos/modules/services/backup/znapzend.nix b/nixos/modules/services/backup/znapzend.nix
index 203631a577f0..98cd647f61ac 100644
--- a/nixos/modules/services/backup/znapzend.nix
+++ b/nixos/modules/services/backup/znapzend.nix
@@ -372,6 +372,41 @@ in
         and <citerefentry><refentrytitle>zfs</refentrytitle><manvolnum>8</manvolnum></citerefentry>
         for more info.
       '';
+      features.sendRaw = mkEnableOption ''
+        sendRaw feature which adds the options <literal>-w</literal> to the
+        <command>zfs send</command> command. For encrypted source datasets this
+        instructs zfs not to decrypt before sending which results in a remote
+        backup that can't be read without the encryption key/passphrase, useful
+        when the remote isn't fully trusted or not physically secure. This
+        option must be used consistently, raw incrementals cannot be based on
+        non-raw snapshots and vice versa.
+      '';
+      features.skipIntermediates = mkEnableOption ''
+        Enable the skipIntermediates feature to send a single increment
+        between latest common snapshot and the newly made one. It may skip
+        several source snaps if the destination was offline for some time, and
+        it should skip snapshots not managed by znapzend. Normally for online
+        destinations, the new snapshot is sent as soon as it is created on the
+        source, so there are no automatic increments to skip.
+      '';
+      features.lowmemRecurse = mkEnableOption ''
+        use lowmemRecurse on systems where you have too many datasets, so a
+        recursive listing of attributes to find backup plans exhausts the
+        memory available to <command>znapzend</command>: instead, go the slower
+        way to first list all impacted dataset names, and then query their
+        configs one by one.
+      '';
+      features.zfsGetType = mkEnableOption ''
+        use zfsGetType if your <command>zfs get</command> supports a
+        <literal>-t</literal> argument for filtering by dataset type at all AND
+        lists properties for snapshots by default when recursing, so that there
+        is too much data to process while searching for backup plans.
+        If these two conditions apply to your system, the time needed for a
+        <literal>--recursive</literal> search for backup plans can literally
+        differ by hundreds of times (depending on the amount of snapshots in
+        that dataset tree... and a decent backup plan will ensure you have a lot
+        of those), so you would benefit from requesting this feature.
+      '';
     };
   };
 
diff --git a/nixos/modules/services/continuous-integration/gitlab-runner.nix b/nixos/modules/services/continuous-integration/gitlab-runner.nix
index bd4cf6a37bad..eacfed85ddff 100644
--- a/nixos/modules/services/continuous-integration/gitlab-runner.nix
+++ b/nixos/modules/services/continuous-integration/gitlab-runner.nix
@@ -1,160 +1,494 @@
 { config, lib, pkgs, ... }:
-
 with lib;
-
 let
   cfg = config.services.gitlab-runner;
-  configFile =
-    if (cfg.configFile == null) then
-      (pkgs.runCommand "config.toml" {
-        buildInputs = [ pkgs.remarshal ];
-        preferLocalBuild = true;
-      } ''
-        remarshal -if json -of toml \
-          < ${pkgs.writeText "config.json" (builtins.toJSON cfg.configOptions)} \
-          > $out
-      '')
-    else
-      cfg.configFile;
   hasDocker = config.virtualisation.docker.enable;
+  hashedServices = with builtins; (mapAttrs' (name: service: nameValuePair
+    "${name}_${config.networking.hostName}_${
+        substring 0 12
+        (hashString "md5" (unsafeDiscardStringContext (toJSON service)))}"
+      service)
+    cfg.services);
+  configPath = "$HOME/.gitlab-runner/config.toml";
+  configureScript = pkgs.writeShellScriptBin "gitlab-runner-configure" (
+    if (cfg.configFile != null) then ''
+      mkdir -p $(dirname ${configPath})
+      cp ${cfg.configFile} ${configPath}
+      # make config file readable by service
+      chown -R --reference=$HOME $(dirname ${configPath})
+    '' else ''
+      export CONFIG_FILE=${configPath}
+
+      mkdir -p $(dirname ${configPath})
+
+      # remove no longer existing services
+      gitlab-runner verify --delete
+
+      # current and desired state
+      NEEDED_SERVICES=$(echo ${concatStringsSep " " (attrNames hashedServices)} | tr " " "\n")
+      REGISTERED_SERVICES=$(gitlab-runner list 2>&1 | grep 'Executor' | awk '{ print $1 }')
+
+      # difference between current and desired state
+      NEW_SERVICES=$(grep -vxF -f <(echo "$REGISTERED_SERVICES") <(echo "$NEEDED_SERVICES") || true)
+      OLD_SERVICES=$(grep -vxF -f <(echo "$NEEDED_SERVICES") <(echo "$REGISTERED_SERVICES") || true)
+
+      # register new services
+      ${concatStringsSep "\n" (mapAttrsToList (name: service: ''
+        if echo "$NEW_SERVICES" | grep -xq ${name}; then
+          bash -c ${escapeShellArg (concatStringsSep " \\\n " ([
+            "set -a && source ${service.registrationConfigFile} &&"
+            "gitlab-runner register"
+            "--non-interactive"
+            "--name ${name}"
+            "--executor ${service.executor}"
+            "--limit ${toString service.limit}"
+            "--request-concurrency ${toString service.requestConcurrency}"
+            "--maximum-timeout ${toString service.maximumTimeout}"
+          ] ++ service.registrationFlags
+            ++ optional (service.buildsDir != null)
+            "--builds-dir ${service.buildsDir}"
+            ++ optional (service.preCloneScript != null)
+            "--pre-clone-script ${service.preCloneScript}"
+            ++ optional (service.preBuildScript != null)
+            "--pre-build-script ${service.preBuildScript}"
+            ++ optional (service.postBuildScript != null)
+            "--post-build-script ${service.postBuildScript}"
+            ++ optional (service.tagList != [ ])
+            "--tag-list ${concatStringsSep "," service.tagList}"
+            ++ optional service.runUntagged
+            "--run-untagged"
+            ++ optional service.protected
+            "--access-level ref_protected"
+            ++ optional service.debugTraceDisabled
+            "--debug-trace-disabled"
+            ++ map (e: "--env ${escapeShellArg e}") (mapAttrsToList (name: value: "${name}=${value}") service.environmentVariables)
+            ++ optionals (service.executor == "docker") (
+              assert (
+                assertMsg (service.dockerImage != null)
+                  "dockerImage option is required for docker executor (${name})");
+              [ "--docker-image ${service.dockerImage}" ]
+              ++ optional service.dockerDisableCache
+              "--docker-disable-cache"
+              ++ optional service.dockerPrivileged
+              "--docker-privileged"
+              ++ map (v: "--docker-volumes ${escapeShellArg v}") service.dockerVolumes
+              ++ map (v: "--docker-extra-hosts ${escapeShellArg v}") service.dockerExtraHosts
+              ++ map (v: "--docker-allowed-images ${escapeShellArg v}") service.dockerAllowedImages
+              ++ map (v: "--docker-allowed-services ${escapeShellArg v}") service.dockerAllowedServices
+            )
+          ))} && sleep 1
+        fi
+      '') hashedServices)}
+
+      # unregister old services
+      for NAME in $(echo "$OLD_SERVICES")
+      do
+        [ ! -z "$NAME" ] && gitlab-runner unregister \
+          --name "$NAME" && sleep 1
+      done
+
+      # update global options
+      remarshal --if toml --of json ${configPath} \
+        | jq -cM '.check_interval = ${toString cfg.checkInterval} |
+                  .concurrent = ${toString cfg.concurrent}' \
+        | remarshal --if json --of toml \
+        | sponge ${configPath}
+
+      # make config file readable by service
+      chown -R --reference=$HOME $(dirname ${configPath})
+    '');
+  startScript = pkgs.writeShellScriptBin "gitlab-runner-start" ''
+    export CONFIG_FILE=${configPath}
+    exec gitlab-runner run --working-directory $HOME
+  '';
 in
 {
   options.services.gitlab-runner = {
     enable = mkEnableOption "Gitlab Runner";
-
     configFile = mkOption {
+      type = types.nullOr types.path;
       default = null;
       description = ''
         Configuration file for gitlab-runner.
-        Use this option in favor of configOptions to avoid placing CI tokens in the nix store.
 
-        <option>configFile</option> takes precedence over <option>configOptions</option>.
+        <option>configFile</option> takes precedence over <option>services</option>.
+        <option>checkInterval</option> and <option>concurrent</option> will be ignored too.
 
-        Warning: Not using <option>configFile</option> will potentially result in secrets
-        leaking into the WORLD-READABLE nix store.
+        This option is deprecated, please use <option>services</option> instead.
+        You can use <option>registrationConfigFile</option> and
+        <option>registrationFlags</option>
+        for settings not covered by this module.
       '';
-      type = types.nullOr types.path;
     };
-
-    configOptions = mkOption {
+    checkInterval = mkOption {
+      type = types.int;
+      default = 0;
+      example = literalExample "with lib; (length (attrNames config.services.gitlab-runner.services)) * 3";
       description = ''
-        Configuration for gitlab-runner
-        <option>configFile</option> will take precedence over this option.
-
-        Warning: all Configuration, especially CI token, will be stored in a
-        WORLD-READABLE file in the Nix Store.
-
-        If you want to protect your CI token use <option>configFile</option> instead.
+        Defines the interval length, in seconds, between new jobs check.
+        The default value is 3;
+        if set to 0 or lower, the default value will be used.
+        See <link xlink:href="https://docs.gitlab.com/runner/configuration/advanced-configuration.html#how-check_interval-works">runner documentation</link> for more information.
+      '';
+    };
+    concurrent = mkOption {
+      type = types.int;
+      default = 1;
+      example = literalExample "config.nix.maxJobs";
+      description = ''
+        Limits how many jobs globally can be run concurrently.
+        The most upper limit of jobs using all defined runners.
+        0 does not mean unlimited.
       '';
-      type = types.attrs;
-      example = {
-        concurrent = 2;
-        runners = [{
-          name = "docker-nix-1.11";
-          url = "https://CI/";
-          token = "TOKEN";
-          executor = "docker";
-          builds_dir = "";
-          docker = {
-            host = "";
-            image = "nixos/nix:1.11";
-            privileged = true;
-            disable_cache = true;
-            cache_dir = "";
-          };
-        }];
-      };
     };
-
     gracefulTermination = mkOption {
-      default = false;
       type = types.bool;
+      default = false;
       description = ''
-        Finish all remaining jobs before stopping, restarting or reconfiguring.
-        If not set gitlab-runner will stop immediatly without waiting for jobs to finish,
-        which will lead to failed builds.
+        Finish all remaining jobs before stopping.
+        If not set gitlab-runner will stop immediatly without waiting
+        for jobs to finish, which will lead to failed builds.
       '';
     };
-
     gracefulTimeout = mkOption {
-      default = "infinity";
       type = types.str;
+      default = "infinity";
       example = "5min 20s";
-      description = ''Time to wait until a graceful shutdown is turned into a forceful one.'';
-    };
-
-    workDir = mkOption {
-      default = "/var/lib/gitlab-runner";
-      type = types.path;
-      description = "The working directory used";
+      description = ''
+        Time to wait until a graceful shutdown is turned into a forceful one.
+      '';
     };
-
     package = mkOption {
-      description = "Gitlab Runner package to use";
+      type = types.package;
       default = pkgs.gitlab-runner;
       defaultText = "pkgs.gitlab-runner";
-      type = types.package;
       example = literalExample "pkgs.gitlab-runner_1_11";
+      description = "Gitlab Runner package to use.";
     };
-
-    packages = mkOption {
-      default = [ pkgs.bash pkgs.docker-machine ];
-      defaultText = "[ pkgs.bash pkgs.docker-machine ]";
+    extraPackages = mkOption {
       type = types.listOf types.package;
+      default = [ ];
       description = ''
-        Packages to add to PATH for the gitlab-runner process.
+        Extra packages to add to PATH for the gitlab-runner process.
       '';
     };
+    services = mkOption {
+      description = "GitLab Runner services.";
+      default = { };
+      example = literalExample ''
+        {
+          # runner for building in docker via host's nix-daemon
+          # nix store will be readable in runner, might be insecure
+          nix = {
+            # File should contain at least these two variables:
+            # `CI_SERVER_URL`
+            # `REGISTRATION_TOKEN`
+            registrationConfigFile = "/run/secrets/gitlab-runner-registration";
+            dockerImage = "alpine";
+            dockerVolumes = [
+              "/nix/store:/nix/store:ro"
+              "/nix/var/nix/db:/nix/var/nix/db:ro"
+              "/nix/var/nix/daemon-socket:/nix/var/nix/daemon-socket:ro"
+            ];
+            dockerDisableCache = true;
+            preBuildScript = pkgs.writeScript "setup-container" '''
+              mkdir -p -m 0755 /nix/var/log/nix/drvs
+              mkdir -p -m 0755 /nix/var/nix/gcroots
+              mkdir -p -m 0755 /nix/var/nix/profiles
+              mkdir -p -m 0755 /nix/var/nix/temproots
+              mkdir -p -m 0755 /nix/var/nix/userpool
+              mkdir -p -m 1777 /nix/var/nix/gcroots/per-user
+              mkdir -p -m 1777 /nix/var/nix/profiles/per-user
+              mkdir -p -m 0755 /nix/var/nix/profiles/per-user/root
+              mkdir -p -m 0700 "$HOME/.nix-defexpr"
 
-  };
+              . ''${pkgs.nix}/etc/profile.d/nix.sh
 
+              ''${pkgs.nix}/bin/nix-env -i ''${concatStringsSep " " (with pkgs; [ nix cacert git openssh ])}
+
+              ''${pkgs.nix}/bin/nix-channel --add https://nixos.org/channels/nixpkgs-unstable
+              ''${pkgs.nix}/bin/nix-channel --update nixpkgs
+            ''';
+            environmentVariables = {
+              ENV = "/etc/profile";
+              USER = "root";
+              NIX_REMOTE = "daemon";
+              PATH = "/nix/var/nix/profiles/default/bin:/nix/var/nix/profiles/default/sbin:/bin:/sbin:/usr/bin:/usr/sbin";
+              NIX_SSL_CERT_FILE = "/nix/var/nix/profiles/default/etc/ssl/certs/ca-bundle.crt";
+            };
+            tagList = [ "nix" ];
+          };
+          # runner for building docker images
+          docker-images = {
+            # File should contain at least these two variables:
+            # `CI_SERVER_URL`
+            # `REGISTRATION_TOKEN`
+            registrationConfigFile = "/run/secrets/gitlab-runner-registration";
+            dockerImage = "docker:stable";
+            dockerVolumes = [
+              "/var/run/docker.sock:/var/run/docker.sock"
+            ];
+            tagList = [ "docker-images" ];
+          };
+          # runner for executing stuff on host system (very insecure!)
+          # make sure to add required packages (including git!)
+          # to `environment.systemPackages`
+          shell = {
+            # File should contain at least these two variables:
+            # `CI_SERVER_URL`
+            # `REGISTRATION_TOKEN`
+            registrationConfigFile = "/run/secrets/gitlab-runner-registration";
+            executor = "shell";
+            tagList = [ "shell" ];
+          };
+          # runner for everything else
+          default = {
+            # File should contain at least these two variables:
+            # `CI_SERVER_URL`
+            # `REGISTRATION_TOKEN`
+            registrationConfigFile = "/run/secrets/gitlab-runner-registration";
+            dockerImage = "debian:stable";
+          };
+        }
+      '';
+      type = types.attrsOf (types.submodule {
+        options = {
+          registrationConfigFile = mkOption {
+            type = types.path;
+            description = ''
+              Absolute path to a file with environment variables
+              used for gitlab-runner registration.
+              A list of all supported environment variables can be found in
+              <literal>gitlab-runner register --help</literal>.
+
+              Ones that you probably want to set is
+
+              <literal>CI_SERVER_URL=&lt;CI server URL&gt;</literal>
+
+              <literal>REGISTRATION_TOKEN=&lt;registration secret&gt;</literal>
+            '';
+          };
+          registrationFlags = mkOption {
+            type = types.listOf types.str;
+            default = [ ];
+            example = [ "--docker-helper-image my/gitlab-runner-helper" ];
+            description = ''
+              Extra command-line flags passed to
+              <literal>gitlab-runner register</literal>.
+              Execute <literal>gitlab-runner register --help</literal>
+              for a list of supported flags.
+            '';
+          };
+          environmentVariables = mkOption {
+            type = types.attrsOf types.str;
+            default = { };
+            example = { NAME = "value"; };
+            description = ''
+              Custom environment variables injected to build environment.
+              For secrets you can use <option>registrationConfigFile</option>
+              with <literal>RUNNER_ENV</literal> variable set.
+            '';
+          };
+          executor = mkOption {
+            type = types.str;
+            default = "docker";
+            description = ''
+              Select executor, eg. shell, docker, etc.
+              See <link xlink:href="https://docs.gitlab.com/runner/executors/README.html">runner documentation</link> for more information.
+            '';
+          };
+          buildsDir = mkOption {
+            type = types.nullOr types.path;
+            default = null;
+            example = "/var/lib/gitlab-runner/builds";
+            description = ''
+              Absolute path to a directory where builds will be stored
+              in context of selected executor (Locally, Docker, SSH).
+            '';
+          };
+          dockerImage = mkOption {
+            type = types.nullOr types.str;
+            default = null;
+            description = ''
+              Docker image to be used.
+            '';
+          };
+          dockerVolumes = mkOption {
+            type = types.listOf types.str;
+            default = [ ];
+            example = [ "/var/run/docker.sock:/var/run/docker.sock" ];
+            description = ''
+              Bind-mount a volume and create it
+              if it doesn't exist prior to mounting.
+            '';
+          };
+          dockerDisableCache = mkOption {
+            type = types.bool;
+            default = false;
+            description = ''
+              Disable all container caching.
+            '';
+          };
+          dockerPrivileged = mkOption {
+            type = types.bool;
+            default = false;
+            description = ''
+              Give extended privileges to container.
+            '';
+          };
+          dockerExtraHosts = mkOption {
+            type = types.listOf types.str;
+            default = [ ];
+            example = [ "other-host:127.0.0.1" ];
+            description = ''
+              Add a custom host-to-IP mapping.
+            '';
+          };
+          dockerAllowedImages = mkOption {
+            type = types.listOf types.str;
+            default = [ ];
+            example = [ "ruby:*" "python:*" "php:*" "my.registry.tld:5000/*:*" ];
+            description = ''
+              Whitelist allowed images.
+            '';
+          };
+          dockerAllowedServices = mkOption {
+            type = types.listOf types.str;
+            default = [ ];
+            example = [ "postgres:9" "redis:*" "mysql:*" ];
+            description = ''
+              Whitelist allowed services.
+            '';
+          };
+          preCloneScript = mkOption {
+            type = types.nullOr types.path;
+            default = null;
+            description = ''
+              Runner-specific command script executed before code is pulled.
+            '';
+          };
+          preBuildScript = mkOption {
+            type = types.nullOr types.path;
+            default = null;
+            description = ''
+              Runner-specific command script executed after code is pulled,
+              just before build executes.
+            '';
+          };
+          postBuildScript = mkOption {
+            type = types.nullOr types.path;
+            default = null;
+            description = ''
+              Runner-specific command script executed after code is pulled
+              and just after build executes.
+            '';
+          };
+          tagList = mkOption {
+            type = types.listOf types.str;
+            default = [ ];
+            description = ''
+              Tag list.
+            '';
+          };
+          runUntagged = mkOption {
+            type = types.bool;
+            default = false;
+            description = ''
+              Register to run untagged builds; defaults to
+              <literal>true</literal> when <option>tagList</option> is empty.
+            '';
+          };
+          limit = mkOption {
+            type = types.int;
+            default = 0;
+            description = ''
+              Limit how many jobs can be handled concurrently by this service.
+              0 (default) simply means don't limit.
+            '';
+          };
+          requestConcurrency = mkOption {
+            type = types.int;
+            default = 0;
+            description = ''
+              Limit number of concurrent requests for new jobs from GitLab.
+            '';
+          };
+          maximumTimeout = mkOption {
+            type = types.int;
+            default = 0;
+            description = ''
+              What is the maximum timeout (in seconds) that will be set for
+              job when using this Runner. 0 (default) simply means don't limit.
+            '';
+          };
+          protected = mkOption {
+            type = types.bool;
+            default = false;
+            description = ''
+              When set to true Runner will only run on pipelines
+              triggered on protected branches.
+            '';
+          };
+          debugTraceDisabled = mkOption {
+            type = types.bool;
+            default = false;
+            description = ''
+              When set to true Runner will disable the possibility of
+              using the <literal>CI_DEBUG_TRACE</literal> feature.
+            '';
+          };
+        };
+      });
+    };
+  };
   config = mkIf cfg.enable {
+    warnings = optional (cfg.configFile != null) "services.gitlab-runner.`configFile` is deprecated, please use services.gitlab-runner.`services`.";
+    environment.systemPackages = [ cfg.package ];
     systemd.services.gitlab-runner = {
-      path = cfg.packages;
-      environment = config.networking.proxy.envVars // {
-        # Gitlab runner will not start if the HOME variable is not set
-        HOME = cfg.workDir;
-      };
       description = "Gitlab Runner";
+      documentation = [ "https://docs.gitlab.com/runner/" ];
       after = [ "network.target" ]
         ++ optional hasDocker "docker.service";
       requires = optional hasDocker "docker.service";
       wantedBy = [ "multi-user.target" ];
+      environment = config.networking.proxy.envVars // {
+        HOME = "/var/lib/gitlab-runner";
+      };
+      path = with pkgs; [
+        bash
+        gawk
+        jq
+        moreutils
+        remarshal
+        utillinux
+        cfg.package
+      ] ++ cfg.extraPackages;
       reloadIfChanged = true;
-      restartTriggers = [
-         config.environment.etc."gitlab-runner/config.toml".source
-      ];
       serviceConfig = {
+        # Set `DynamicUser` under `systemd.services.gitlab-runner.serviceConfig`
+        # to `lib.mkForce false` in your configuration to run this service as root.
+        # You can also set `User` and `Group` options to run this service as desired user.
+        # Make sure to restart service or changes won't apply.
+        DynamicUser = true;
         StateDirectory = "gitlab-runner";
-        ExecReload= "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
-        ExecStart = ''${cfg.package.bin}/bin/gitlab-runner run \
-          --working-directory ${cfg.workDir} \
-          --config /etc/gitlab-runner/config.toml \
-          --service gitlab-runner \
-          --user gitlab-runner \
-        '';
-
-      } //  optionalAttrs (cfg.gracefulTermination) {
+        SupplementaryGroups = optional hasDocker "docker";
+        ExecStartPre = "!${configureScript}/bin/gitlab-runner-configure";
+        ExecStart = "${startScript}/bin/gitlab-runner-start";
+        ExecReload = "!${configureScript}/bin/gitlab-runner-configure";
+      } // optionalAttrs (cfg.gracefulTermination) {
         TimeoutStopSec = "${cfg.gracefulTimeout}";
         KillSignal = "SIGQUIT";
         KillMode = "process";
       };
     };
-
-    # Make the gitlab-runner command availabe so users can query the runner
-    environment.systemPackages = [ cfg.package ];
-
-    # Make sure the config can be reloaded on change
-    environment.etc."gitlab-runner/config.toml".source = configFile;
-
-    users.users.gitlab-runner = {
-      group = "gitlab-runner";
-      extraGroups = optional hasDocker "docker";
-      uid = config.ids.uids.gitlab-runner;
-      home = cfg.workDir;
-      createHome = true;
-    };
-
-    users.groups.gitlab-runner.gid = config.ids.gids.gitlab-runner;
+    # Enable docker if `docker` executor is used in any service
+    virtualisation.docker.enable = mkIf (
+      any (s: s.executor == "docker") (attrValues cfg.services)
+    ) (mkDefault true);
   };
+  imports = [
+    (mkRenamedOptionModule [ "services" "gitlab-runner" "packages" ] [ "services" "gitlab-runner" "extraPackages" ] )
+    (mkRemovedOptionModule [ "services" "gitlab-runner" "configOptions" ] "Use services.gitlab-runner.services option instead" )
+    (mkRemovedOptionModule [ "services" "gitlab-runner" "workDir" ] "You should move contents of workDir (if any) to /var/lib/gitlab-runner" )
+  ];
 }
diff --git a/nixos/modules/services/databases/openldap.nix b/nixos/modules/services/databases/openldap.nix
index 809f61cfa818..8c2851c37ac2 100644
--- a/nixos/modules/services/databases/openldap.nix
+++ b/nixos/modules/services/databases/openldap.nix
@@ -231,6 +231,10 @@ in
 
   };
 
+  meta = {
+    maintainers = lib.maintainers.mic92;
+  };
+
 
   ###### implementation
 
diff --git a/nixos/modules/services/databases/postgresql.nix b/nixos/modules/services/databases/postgresql.nix
index 0b79a996dc78..93f5c1ca5f55 100644
--- a/nixos/modules/services/databases/postgresql.nix
+++ b/nixos/modules/services/databases/postgresql.nix
@@ -34,13 +34,7 @@ in
 
     services.postgresql = {
 
-      enable = mkOption {
-        type = types.bool;
-        default = false;
-        description = ''
-          Whether to run PostgreSQL.
-        '';
-      };
+      enable = mkEnableOption "PostgreSQL Server";
 
       package = mkOption {
         type = types.package;
diff --git a/nixos/modules/services/editors/emacs.xml b/nixos/modules/services/editors/emacs.xml
index 03483f69fa2f..74c60014dcea 100644
--- a/nixos/modules/services/editors/emacs.xml
+++ b/nixos/modules/services/editors/emacs.xml
@@ -294,7 +294,7 @@ https://nixos.org/nixpkgs/manual/#sec-modify-via-packageOverrides
     If you are not on NixOS or want to install this particular Emacs only for
     yourself, you can do so by adding it to your
     <filename>~/.config/nixpkgs/config.nix</filename> (see
-    <link xlink:href="http://nixos.org/nixpkgs/manual/#sec-modify-via-packageOverrides">Nixpkgs
+    <link xlink:href="https://nixos.org/nixpkgs/manual/#sec-modify-via-packageOverrides">Nixpkgs
     manual</link>):
     <example xml:id="module-services-emacs-config-nix">
      <title>Custom Emacs in <filename>~/.config/nixpkgs/config.nix</filename></title>
diff --git a/nixos/modules/services/mail/postfix.nix b/nixos/modules/services/mail/postfix.nix
index 19e11b31d9ca..608f64a68fb0 100644
--- a/nixos/modules/services/mail/postfix.nix
+++ b/nixos/modules/services/mail/postfix.nix
@@ -269,6 +269,7 @@ in
       };
 
       enableSmtp = mkOption {
+        type = types.bool;
         default = true;
         description = "Whether to enable smtp in master.cf.";
       };
diff --git a/nixos/modules/services/mail/roundcube.nix b/nixos/modules/services/mail/roundcube.nix
index 21e92cfee016..ed1439745ac9 100644
--- a/nixos/modules/services/mail/roundcube.nix
+++ b/nixos/modules/services/mail/roundcube.nix
@@ -7,7 +7,7 @@ let
   fpm = config.services.phpfpm.pools.roundcube;
   localDB = cfg.database.host == "localhost";
   user = cfg.database.username;
-  phpWithPspell = pkgs.php.withExtensions (e: [ e.pspell ] ++ pkgs.php.enabledExtensions);
+  phpWithPspell = pkgs.php.withExtensions ({ enabled, all }: [ all.pspell ] ++ enabled);
 in
 {
   options.services.roundcube = {
diff --git a/nixos/modules/services/mail/spamassassin.nix b/nixos/modules/services/mail/spamassassin.nix
index f6c0b9c794c9..4e642542ec66 100644
--- a/nixos/modules/services/mail/spamassassin.nix
+++ b/nixos/modules/services/mail/spamassassin.nix
@@ -15,6 +15,7 @@ in
       enable = mkEnableOption "the SpamAssassin daemon";
 
       debug = mkOption {
+        type = types.bool;
         default = false;
         description = "Whether to run the SpamAssassin daemon in debug mode";
       };
diff --git a/nixos/modules/services/misc/autofs.nix b/nixos/modules/services/misc/autofs.nix
index 84f49f964b12..5e7c1e668288 100644
--- a/nixos/modules/services/misc/autofs.nix
+++ b/nixos/modules/services/misc/autofs.nix
@@ -57,6 +57,7 @@ in
       };
 
       debug = mkOption {
+        type = types.bool;
         default = false;
         description = ''
           Pass -d and -7 to automount and write log to the system journal.
diff --git a/nixos/modules/services/misc/confd.nix b/nixos/modules/services/misc/confd.nix
index 8e9bec15dd4f..c1ebdb3dde91 100755
--- a/nixos/modules/services/misc/confd.nix
+++ b/nixos/modules/services/misc/confd.nix
@@ -75,7 +75,7 @@ in {
       wantedBy = [ "multi-user.target" ];
       after = [ "network.target" ];
       serviceConfig = {
-        ExecStart = "${cfg.package.bin}/bin/confd";
+        ExecStart = "${cfg.package}/bin/confd";
       };
     };
 
diff --git a/nixos/modules/services/misc/disnix.nix b/nixos/modules/services/misc/disnix.nix
index 0776ac679088..69386cdbb381 100644
--- a/nixos/modules/services/misc/disnix.nix
+++ b/nixos/modules/services/misc/disnix.nix
@@ -25,10 +25,7 @@ in
         description = "Whether to support multi-user mode by enabling the Disnix D-Bus service";
       };
 
-      useWebServiceInterface = mkOption {
-        default = false;
-        description = "Whether to enable the DisnixWebService interface running on Apache Tomcat";
-      };
+      useWebServiceInterface = mkEnableOption "the DisnixWebService interface running on Apache Tomcat";
 
       package = mkOption {
         type = types.path;
diff --git a/nixos/modules/services/misc/etcd.nix b/nixos/modules/services/misc/etcd.nix
index 7322e1c080be..32360d43768a 100644
--- a/nixos/modules/services/misc/etcd.nix
+++ b/nixos/modules/services/misc/etcd.nix
@@ -178,7 +178,7 @@ in {
 
       serviceConfig = {
         Type = "notify";
-        ExecStart = "${pkgs.etcd.bin}/bin/etcd";
+        ExecStart = "${pkgs.etcd}/bin/etcd";
         User = "etcd";
         LimitNOFILE = 40000;
       };
diff --git a/nixos/modules/services/misc/gitea.nix b/nixos/modules/services/misc/gitea.nix
index 38910a5a005d..f8bcedc94fe2 100644
--- a/nixos/modules/services/misc/gitea.nix
+++ b/nixos/modules/services/misc/gitea.nix
@@ -14,53 +14,9 @@ let
     RUN_USER = ${cfg.user}
     RUN_MODE = prod
 
-    [database]
-    DB_TYPE = ${cfg.database.type}
-    ${optionalString (usePostgresql || useMysql) ''
-      HOST = ${if cfg.database.socket != null then cfg.database.socket else cfg.database.host + ":" + toString cfg.database.port}
-      NAME = ${cfg.database.name}
-      USER = ${cfg.database.user}
-      PASSWD = #dbpass#
-    ''}
-    ${optionalString useSqlite ''
-      PATH = ${cfg.database.path}
-    ''}
-    ${optionalString usePostgresql ''
-      SSL_MODE = disable
-    ''}
-
-    [repository]
-    ROOT = ${cfg.repositoryRoot}
-
-    [server]
-    DOMAIN = ${cfg.domain}
-    HTTP_ADDR = ${cfg.httpAddress}
-    HTTP_PORT = ${toString cfg.httpPort}
-    ROOT_URL = ${cfg.rootUrl}
-    STATIC_ROOT_PATH = ${cfg.staticRootPath}
-    LFS_JWT_SECRET = #jwtsecret#
-
-    [session]
-    COOKIE_NAME = session
-    COOKIE_SECURE = ${boolToString cfg.cookieSecure}
-
-    [security]
-    SECRET_KEY = #secretkey#
-    INSTALL_LOCK = true
-
-    [log]
-    ROOT_PATH = ${cfg.log.rootPath}
-    LEVEL = ${cfg.log.level}
-
-    [service]
-    DISABLE_REGISTRATION = ${boolToString cfg.disableRegistration}
-
-    ${optionalString (cfg.mailerPasswordFile != null) ''
-      [mailer]
-      PASSWD = #mailerpass#
-    ''}
-
-    ${cfg.extraConfig}
+    ${generators.toINI {} cfg.settings}
+
+    ${optionalString (cfg.extraConfig != null) cfg.extraConfig}
   '';
 in
 
@@ -279,9 +235,36 @@ in
         '';
       };
 
+      settings = mkOption {
+        type = with types; attrsOf (attrsOf (oneOf [ bool int str ]));
+        default = {};
+        description = ''
+          Gitea configuration. Refer to <link xlink:href="https://docs.gitea.io/en-us/config-cheat-sheet/"/>
+          for details on supported values.
+        '';
+        example = literalExample ''
+          {
+            "cron.sync_external_users" = {
+              RUN_AT_START = true;
+              SCHEDULE = "@every 24h";
+              UPDATE_EXISTING = true;
+            };
+            mailer = {
+              ENABLED = true;
+              MAILER_TYPE = "sendmail";
+              FROM = "do-not-reply@example.org";
+              SENDMAIL_PATH = "${pkgs.system-sendmail}/bin/sendmail";
+            };
+            other = {
+              SHOW_FOOTER_VERSION = false;
+            };
+          }
+        '';
+      };
+
       extraConfig = mkOption {
-        type = types.str;
-        default = "";
+        type = with types; nullOr str;
+        default = null;
         description = "Configuration lines appended to the generated gitea configuration file.";
       };
     };
@@ -294,6 +277,62 @@ in
       }
     ];
 
+    services.gitea.settings = {
+      database = mkMerge [
+        {
+          DB_TYPE = cfg.database.type;
+        }
+        (mkIf (useMysql || usePostgresql) {
+          HOST = if cfg.database.socket != null then cfg.database.socket else cfg.database.host + ":" + toString cfg.database.port;
+          NAME = cfg.database.name;
+          USER = cfg.database.user;
+          PASSWD = "#dbpass#";
+        })
+        (mkIf useSqlite {
+          PATH = cfg.database.path;
+        })
+        (mkIf usePostgresql {
+          SSL_MODE = "disable";
+        })
+      ];
+
+      repository = {
+        ROOT = cfg.repositoryRoot;
+      };
+
+      server = {
+        DOMAIN = cfg.domain;
+        HTTP_ADDR = cfg.httpAddress;
+        HTTP_PORT = cfg.httpPort;
+        ROOT_URL = cfg.rootUrl;
+        STATIC_ROOT_PATH = cfg.staticRootPath;
+        LFS_JWT_SECRET = "#jwtsecret#";
+      };
+
+      session = {
+        COOKIE_NAME = "session";
+        COOKIE_SECURE = cfg.cookieSecure;
+      };
+
+      security = {
+        SECRET_KEY = "#secretkey#";
+        INSTALL_LOCK = true;
+      };
+
+      log = {
+        ROOT_PATH = cfg.log.rootPath;
+        LEVEL = cfg.log.level;
+      };
+
+      service = {
+        DISABLE_REGISTRATION = cfg.disableRegistration;
+      };
+
+      mailer = mkIf (cfg.mailerPasswordFile != null) {
+        PASSWD = "#mailerpass#";
+      };
+    };
+
     services.postgresql = optionalAttrs (usePostgresql && cfg.database.createDatabase) {
       enable = mkDefault true;
 
@@ -335,7 +374,7 @@ in
       description = "gitea";
       after = [ "network.target" ] ++ lib.optional usePostgresql "postgresql.service" ++ lib.optional useMysql "mysql.service";
       wantedBy = [ "multi-user.target" ];
-      path = [ gitea.bin pkgs.gitAndTools.git ];
+      path = [ gitea pkgs.gitAndTools.git ];
 
       preStart = let
         runConfig = "${cfg.stateDir}/custom/conf/app.ini";
@@ -347,11 +386,11 @@ in
           cp -f ${configFile} ${runConfig}
 
           if [ ! -e ${secretKey} ]; then
-              ${gitea.bin}/bin/gitea generate secret SECRET_KEY > ${secretKey}
+              ${gitea}/bin/gitea generate secret SECRET_KEY > ${secretKey}
           fi
 
           if [ ! -e ${jwtSecret} ]; then
-              ${gitea.bin}/bin/gitea generate secret LFS_JWT_SECRET > ${jwtSecret}
+              ${gitea}/bin/gitea generate secret LFS_JWT_SECRET > ${jwtSecret}
           fi
 
           KEY="$(head -n1 ${secretKey})"
@@ -374,7 +413,7 @@ in
         HOOKS=$(find ${cfg.repositoryRoot} -mindepth 4 -maxdepth 6 -type f -wholename "*git/hooks/*")
         if [ "$HOOKS" ]
         then
-          sed -ri 's,/nix/store/[a-z0-9.-]+/bin/gitea,${gitea.bin}/bin/gitea,g' $HOOKS
+          sed -ri 's,/nix/store/[a-z0-9.-]+/bin/gitea,${gitea}/bin/gitea,g' $HOOKS
           sed -ri 's,/nix/store/[a-z0-9.-]+/bin/env,${pkgs.coreutils}/bin/env,g' $HOOKS
           sed -ri 's,/nix/store/[a-z0-9.-]+/bin/bash,${pkgs.bash}/bin/bash,g' $HOOKS
           sed -ri 's,/nix/store/[a-z0-9.-]+/bin/perl,${pkgs.perl}/bin/perl,g' $HOOKS
@@ -383,7 +422,7 @@ in
         # update command option in authorized_keys
         if [ -r ${cfg.stateDir}/.ssh/authorized_keys ]
         then
-          sed -ri 's,/nix/store/[a-z0-9.-]+/bin/gitea,${gitea.bin}/bin/gitea,g' ${cfg.stateDir}/.ssh/authorized_keys
+          sed -ri 's,/nix/store/[a-z0-9.-]+/bin/gitea,${gitea}/bin/gitea,g' ${cfg.stateDir}/.ssh/authorized_keys
         fi
       '';
 
@@ -392,7 +431,7 @@ in
         User = cfg.user;
         Group = "gitea";
         WorkingDirectory = cfg.stateDir;
-        ExecStart = "${gitea.bin}/bin/gitea web";
+        ExecStart = "${gitea}/bin/gitea web";
         Restart = "always";
 
         # Filesystem
@@ -435,9 +474,12 @@ in
 
     users.groups.gitea = {};
 
-    warnings = optional (cfg.database.password != "")
-      ''config.services.gitea.database.password will be stored as plaintext
-        in the Nix store. Use database.passwordFile instead.'';
+    warnings =
+      optional (cfg.database.password != "") ''
+        config.services.gitea.database.password will be stored as plaintext in the Nix store. Use database.passwordFile instead.'' ++
+      optional (cfg.extraConfig != null) ''
+        services.gitea.`extraConfig` is deprecated, please use services.gitea.`settings`.
+      '';
 
     # Create database passwordFile default when password is configured.
     services.gitea.database.passwordFile =
@@ -450,7 +492,7 @@ in
        description = "gitea dump";
        after = [ "gitea.service" ];
        wantedBy = [ "default.target" ];
-       path = [ gitea.bin ];
+       path = [ gitea ];
 
        environment = {
          USER = cfg.user;
@@ -461,7 +503,7 @@ in
        serviceConfig = {
          Type = "oneshot";
          User = cfg.user;
-         ExecStart = "${gitea.bin}/bin/gitea dump";
+         ExecStart = "${gitea}/bin/gitea dump";
          WorkingDirectory = cfg.stateDir;
        };
     };
diff --git a/nixos/modules/services/misc/gitlab.nix b/nixos/modules/services/misc/gitlab.nix
index aa9589853797..730166b04d20 100644
--- a/nixos/modules/services/misc/gitlab.nix
+++ b/nixos/modules/services/misc/gitlab.nix
@@ -180,7 +180,7 @@ let
         ${optionalString (cfg.smtp.passwordFile != null) ''password: "@smtpPassword@",''}
         domain: "${cfg.smtp.domain}",
         ${optionalString (cfg.smtp.authentication != null) "authentication: :${cfg.smtp.authentication},"}
-        enable_starttls_auto: ${toString cfg.smtp.enableStartTLSAuto},
+        enable_starttls_auto: ${boolToString cfg.smtp.enableStartTLSAuto},
         ca_file: "/etc/ssl/certs/ca-certificates.crt",
         openssl_verify_mode: '${cfg.smtp.opensslVerifyMode}'
       }
diff --git a/nixos/modules/services/misc/gogs.nix b/nixos/modules/services/misc/gogs.nix
index ee99967c261b..c5070aaa356a 100644
--- a/nixos/modules/services/misc/gogs.nix
+++ b/nixos/modules/services/misc/gogs.nix
@@ -200,7 +200,7 @@ in
       description = "Gogs (Go Git Service)";
       after = [ "network.target" ];
       wantedBy = [ "multi-user.target" ];
-      path = [ pkgs.gogs.bin ];
+      path = [ pkgs.gogs ];
 
       preStart = let
         runConfig = "${cfg.stateDir}/custom/conf/app.ini";
@@ -230,7 +230,7 @@ in
         HOOKS=$(find ${cfg.repositoryRoot} -mindepth 4 -maxdepth 4 -type f -wholename "*git/hooks/*")
         if [ "$HOOKS" ]
         then
-          sed -ri 's,/nix/store/[a-z0-9.-]+/bin/gogs,${pkgs.gogs.bin}/bin/gogs,g' $HOOKS
+          sed -ri 's,/nix/store/[a-z0-9.-]+/bin/gogs,${pkgs.gogs}/bin/gogs,g' $HOOKS
           sed -ri 's,/nix/store/[a-z0-9.-]+/bin/env,${pkgs.coreutils}/bin/env,g' $HOOKS
           sed -ri 's,/nix/store/[a-z0-9.-]+/bin/bash,${pkgs.bash}/bin/bash,g' $HOOKS
           sed -ri 's,/nix/store/[a-z0-9.-]+/bin/perl,${pkgs.perl}/bin/perl,g' $HOOKS
@@ -242,7 +242,7 @@ in
         User = cfg.user;
         Group = cfg.group;
         WorkingDirectory = cfg.stateDir;
-        ExecStart = "${pkgs.gogs.bin}/bin/gogs web";
+        ExecStart = "${pkgs.gogs}/bin/gogs web";
         Restart = "always";
       };
 
diff --git a/nixos/modules/services/misc/leaps.nix b/nixos/modules/services/misc/leaps.nix
index d4e88ecbebdb..ef89d3e64d0c 100644
--- a/nixos/modules/services/misc/leaps.nix
+++ b/nixos/modules/services/misc/leaps.nix
@@ -55,7 +55,7 @@ in
         Restart = "on-failure";
         WorkingDirectory = stateDir;
         PrivateTmp = true;
-        ExecStart = "${pkgs.leaps.bin}/bin/leaps -path ${toString cfg.path} -address ${cfg.address}:${toString cfg.port}";
+        ExecStart = "${pkgs.leaps}/bin/leaps -path ${toString cfg.path} -address ${cfg.address}:${toString cfg.port}";
       };
     };
   };
diff --git a/nixos/modules/services/misc/nix-daemon.nix b/nixos/modules/services/misc/nix-daemon.nix
index 0c2407e1dd2f..2577cb78e96e 100644
--- a/nixos/modules/services/misc/nix-daemon.nix
+++ b/nixos/modules/services/misc/nix-daemon.nix
@@ -283,7 +283,7 @@ in
       trustedBinaryCaches = mkOption {
         type = types.listOf types.str;
         default = [ ];
-        example = [ "http://hydra.nixos.org/" ];
+        example = [ "https://hydra.nixos.org/" ];
         description = ''
           List of binary cache URLs that non-root users can use (in
           addition to those specified using
@@ -510,8 +510,7 @@ in
 
     system.activationScripts.nix = stringAfter [ "etc" "users" ]
       ''
-        # Create directories in /nix.
-        ${nix}/bin/nix ping-store --no-net
+        install -m 0755 -d /nix/var/nix/{gcroots,profiles}/per-user
 
         # Subscribe the root user to the NixOS channel by default.
         if [ ! -e "/root/.nix-channels" ]; then
diff --git a/nixos/modules/services/misc/octoprint.nix b/nixos/modules/services/misc/octoprint.nix
index 651ed3743884..7a71d2c8c6aa 100644
--- a/nixos/modules/services/misc/octoprint.nix
+++ b/nixos/modules/services/misc/octoprint.nix
@@ -17,9 +17,9 @@ let
 
   cfgUpdate = pkgs.writeText "octoprint-config.yaml" (builtins.toJSON fullConfig);
 
-  pluginsEnv = pkgs.python.buildEnv.override {
-    extraLibs = cfg.plugins pkgs.octoprint-plugins;
-  };
+  pluginsEnv = package.python.withPackages (ps: [ps.octoprint] ++ (cfg.plugins ps));
+
+  package = pkgs.octoprint;
 
 in
 {
@@ -106,7 +106,6 @@ in
       wantedBy = [ "multi-user.target" ];
       after = [ "network.target" ];
       path = [ pluginsEnv ];
-      environment.PYTHONPATH = makeSearchPathOutput "lib" pkgs.python.sitePackages [ pluginsEnv ];
 
       preStart = ''
         if [ -e "${cfg.stateDir}/config.yaml" ]; then
@@ -119,7 +118,7 @@ in
       '';
 
       serviceConfig = {
-        ExecStart = "${pkgs.octoprint}/bin/octoprint serve -b ${cfg.stateDir}";
+        ExecStart = "${pluginsEnv}/bin/octoprint serve -b ${cfg.stateDir}";
         User = cfg.user;
         Group = cfg.group;
       };
diff --git a/nixos/modules/services/misc/pykms.nix b/nixos/modules/services/misc/pykms.nix
index 25aa27ae7673..d6aeae48ccb6 100644
--- a/nixos/modules/services/misc/pykms.nix
+++ b/nixos/modules/services/misc/pykms.nix
@@ -82,6 +82,7 @@ in {
         ]);
         ProtectHome = "tmpfs";
         WorkingDirectory = libDir;
+        SyslogIdentifier = "pykms";
         Restart = "on-failure";
         MemoryLimit = cfg.memoryLimit;
       };
diff --git a/nixos/modules/services/misc/ssm-agent.nix b/nixos/modules/services/misc/ssm-agent.nix
index e951a4c7ffa8..f7c05deeecb5 100644
--- a/nixos/modules/services/misc/ssm-agent.nix
+++ b/nixos/modules/services/misc/ssm-agent.nix
@@ -35,7 +35,7 @@ in {
 
       path = [ fake-lsb-release ];
       serviceConfig = {
-        ExecStart = "${cfg.package.bin}/bin/agent";
+        ExecStart = "${cfg.package}/bin/agent";
         KillMode = "process";
         Restart = "on-failure";
         RestartSec = "15min";
@@ -43,4 +43,3 @@ in {
     };
   };
 }
-
diff --git a/nixos/modules/services/misc/sssd.nix b/nixos/modules/services/misc/sssd.nix
index 36008d257410..77f6ccfe64f0 100644
--- a/nixos/modules/services/misc/sssd.nix
+++ b/nixos/modules/services/misc/sssd.nix
@@ -75,6 +75,11 @@ in {
       };
 
       system.nssModules = optional cfg.enable pkgs.sssd;
+      system.nssDatabases = {
+        passwd = [ "sss" ];
+        shadow = [ "sss" ];
+        services = [ "sss" ];
+      };
       services.dbus.packages = [ pkgs.sssd ];
     })
 
diff --git a/nixos/modules/services/monitoring/bosun.nix b/nixos/modules/services/monitoring/bosun.nix
index b1c12cce1f80..04e9da1c81a3 100644
--- a/nixos/modules/services/monitoring/bosun.nix
+++ b/nixos/modules/services/monitoring/bosun.nix
@@ -148,7 +148,7 @@ in {
         User = cfg.user;
         Group = cfg.group;
         ExecStart = ''
-          ${cfg.package.bin}/bin/bosun -c ${configFile}
+          ${cfg.package}/bin/bosun -c ${configFile}
         '';
       };
     };
diff --git a/nixos/modules/services/monitoring/grafana-reporter.nix b/nixos/modules/services/monitoring/grafana-reporter.nix
index b5a78e4583e1..893c15d568bd 100644
--- a/nixos/modules/services/monitoring/grafana-reporter.nix
+++ b/nixos/modules/services/monitoring/grafana-reporter.nix
@@ -59,7 +59,7 @@ in {
           "-templates ${cfg.templateDir}"
         ];
       in {
-        ExecStart = "${pkgs.grafana_reporter.bin}/bin/grafana-reporter ${args}";
+        ExecStart = "${pkgs.grafana_reporter}/bin/grafana-reporter ${args}";
       };
     };
   };
diff --git a/nixos/modules/services/monitoring/grafana.nix b/nixos/modules/services/monitoring/grafana.nix
index 0f8bc2471e33..b0c81a46d4d8 100644
--- a/nixos/modules/services/monitoring/grafana.nix
+++ b/nixos/modules/services/monitoring/grafana.nix
@@ -535,7 +535,7 @@ in {
         ${optionalString cfg.provision.enable ''
           export GF_PATHS_PROVISIONING=${provisionConfDir};
         ''}
-        exec ${cfg.package.bin}/bin/grafana-server -homepath ${cfg.dataDir}
+        exec ${cfg.package}/bin/grafana-server -homepath ${cfg.dataDir}
       '';
       serviceConfig = {
         WorkingDirectory = cfg.dataDir;
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/snmp.nix b/nixos/modules/services/monitoring/prometheus/exporters/snmp.nix
index 045e48a3d0f8..01276366e97b 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/snmp.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/snmp.nix
@@ -58,7 +58,7 @@ in
     in {
     serviceConfig = {
       ExecStart = ''
-        ${pkgs.prometheus-snmp-exporter.bin}/bin/snmp_exporter \
+        ${pkgs.prometheus-snmp-exporter}/bin/snmp_exporter \
           --config.file=${escapeShellArg configFile} \
           --log.format=${escapeShellArg cfg.logFormat} \
           --log.level=${cfg.logLevel} \
diff --git a/nixos/modules/services/monitoring/scollector.nix b/nixos/modules/services/monitoring/scollector.nix
index 38cd2213de76..6f13ce889cba 100644
--- a/nixos/modules/services/monitoring/scollector.nix
+++ b/nixos/modules/services/monitoring/scollector.nix
@@ -118,7 +118,7 @@ in {
       serviceConfig = {
         User = cfg.user;
         Group = cfg.group;
-        ExecStart = "${cfg.package.bin}/bin/scollector -conf=${conf} ${lib.concatStringsSep " " cfg.extraOpts}";
+        ExecStart = "${cfg.package}/bin/scollector -conf=${conf} ${lib.concatStringsSep " " cfg.extraOpts}";
       };
     };
 
diff --git a/nixos/modules/services/network-filesystems/ipfs.nix b/nixos/modules/services/network-filesystems/ipfs.nix
index b6d881afd7bd..880f70ae1410 100644
--- a/nixos/modules/services/network-filesystems/ipfs.nix
+++ b/nixos/modules/services/network-filesystems/ipfs.nix
@@ -37,9 +37,7 @@ let
   baseService = recursiveUpdate commonEnv {
     wants = [ "ipfs-init.service" ];
     # NB: migration must be performed prior to pre-start, else we get the failure message!
-    preStart = ''
-      ipfs repo fsck # workaround for BUG #4212 (https://github.com/ipfs/go-ipfs/issues/4214)
-    '' + optionalString cfg.autoMount ''
+    preStart = optionalString cfg.autoMount ''
       ipfs --local config Mounts.FuseAllowOther --json true
       ipfs --local config Mounts.IPFS ${cfg.ipfsMountDir}
       ipfs --local config Mounts.IPNS ${cfg.ipnsMountDir}
diff --git a/nixos/modules/services/networking/consul.nix b/nixos/modules/services/networking/consul.nix
index 689cbc8a986d..f7d2afead06c 100644
--- a/nixos/modules/services/networking/consul.nix
+++ b/nixos/modules/services/networking/consul.nix
@@ -179,15 +179,15 @@ in
             (filterAttrs (n: _: hasPrefix "consul.d/" n) config.environment.etc);
 
         serviceConfig = {
-          ExecStart = "@${cfg.package.bin}/bin/consul consul agent -config-dir /etc/consul.d"
+          ExecStart = "@${cfg.package}/bin/consul consul agent -config-dir /etc/consul.d"
             + concatMapStrings (n: " -config-file ${n}") configFiles;
-          ExecReload = "${cfg.package.bin}/bin/consul reload";
+          ExecReload = "${cfg.package}/bin/consul reload";
           PermissionsStartOnly = true;
           User = if cfg.dropPrivileges then "consul" else null;
           Restart = "on-failure";
           TimeoutStartSec = "infinity";
         } // (optionalAttrs (cfg.leaveOnStop) {
-          ExecStop = "${cfg.package.bin}/bin/consul leave";
+          ExecStop = "${cfg.package}/bin/consul leave";
         });
 
         path = with pkgs; [ iproute gnugrep gawk consul ];
@@ -238,7 +238,7 @@ in
 
         serviceConfig = {
           ExecStart = ''
-            ${cfg.alerts.package.bin}/bin/consul-alerts start \
+            ${cfg.alerts.package}/bin/consul-alerts start \
               --alert-addr=${cfg.alerts.listenAddr} \
               --consul-addr=${cfg.alerts.consulAddr} \
               ${optionalString cfg.alerts.watchChecks "--watch-checks"} \
diff --git a/nixos/modules/services/networking/flannel.nix b/nixos/modules/services/networking/flannel.nix
index dd2f6454e954..4c040112d28d 100644
--- a/nixos/modules/services/networking/flannel.nix
+++ b/nixos/modules/services/networking/flannel.nix
@@ -19,8 +19,8 @@ in {
     package = mkOption {
       description = "Package to use for flannel";
       type = types.package;
-      default = pkgs.flannel.bin;
-      defaultText = "pkgs.flannel.bin";
+      default = pkgs.flannel;
+      defaultText = "pkgs.flannel";
     };
 
     publicIp = mkOption {
@@ -167,7 +167,7 @@ in {
         touch /run/flannel/docker
       '' + optionalString (cfg.storageBackend == "etcd") ''
         echo "setting network configuration"
-        until ${pkgs.etcdctl.bin}/bin/etcdctl set /coreos.com/network/config '${builtins.toJSON networkConfig}'
+        until ${pkgs.etcdctl}/bin/etcdctl set /coreos.com/network/config '${builtins.toJSON networkConfig}'
         do
           echo "setting network configuration, retry"
           sleep 1
diff --git a/nixos/modules/services/networking/gogoclient.nix b/nixos/modules/services/networking/gogoclient.nix
index c9b03bca7112..99455b183144 100644
--- a/nixos/modules/services/networking/gogoclient.nix
+++ b/nixos/modules/services/networking/gogoclient.nix
@@ -19,6 +19,7 @@ in
         '';
       };
       autorun = mkOption {
+        type = types.bool;
         default = true;
         description = ''
           Whether to automatically start the tunnel.
diff --git a/nixos/modules/services/networking/hostapd.nix b/nixos/modules/services/networking/hostapd.nix
index 12c0626a9414..5d73038363a9 100644
--- a/nixos/modules/services/networking/hostapd.nix
+++ b/nixos/modules/services/networking/hostapd.nix
@@ -20,12 +20,14 @@ let
     ssid=${cfg.ssid}
     hw_mode=${cfg.hwMode}
     channel=${toString cfg.channel}
+    ${optionalString (cfg.countryCode != null) ''country_code=${cfg.countryCode}''}
+    ${optionalString (cfg.countryCode != null) ''ieee80211d=1''}
 
     # logging (debug level)
     logger_syslog=-1
-    logger_syslog_level=2
+    logger_syslog_level=${toString cfg.logLevel}
     logger_stdout=-1
-    logger_stdout_level=2
+    logger_stdout_level=${toString cfg.logLevel}
 
     ctrl_interface=/run/hostapd
     ctrl_interface_group=${cfg.group}
@@ -72,6 +74,7 @@ in
       };
 
       noScan = mkOption {
+        type = types.bool;
         default = false;
         description = ''
           Do not scan for overlapping BSSs in HT40+/- mode.
@@ -127,6 +130,7 @@ in
       };
 
       wpa = mkOption {
+        type = types.bool;
         default = true;
         description = ''
           Enable WPA (IEEE 802.11i/D3.0) to authenticate with the access point.
@@ -145,6 +149,35 @@ in
         '';
       };
 
+      logLevel = mkOption {
+        default = 2;
+        type = types.int;
+        description = ''
+          Levels (minimum value for logged events):
+          0 = verbose debugging
+          1 = debugging
+          2 = informational messages
+          3 = notification
+          4 = warning
+        '';
+      };
+
+      countryCode = mkOption {
+        default = null;
+        example = "US";
+        type = with types; nullOr str;
+        description = ''
+          Country code (ISO/IEC 3166-1). Used to set regulatory domain.
+          Set as needed to indicate country in which device is operating.
+          This can limit available channels and transmit power.
+          These two octets are used as the first two octets of the Country String
+          (dot11CountryString).
+          If set this enables IEEE 802.11d. This advertises the countryCode and
+          the set of allowed channels and transmit power levels based on the
+          regulatory limits.
+        '';
+      };
+
       extraConfig = mkOption {
         default = "";
         example = ''
@@ -165,6 +198,8 @@ in
 
     environment.systemPackages =  [ pkgs.hostapd ];
 
+    services.udev.packages = optional (cfg.countryCode != null) [ pkgs.crda ];
+
     systemd.services.hostapd =
       { description = "hostapd wireless AP";
 
diff --git a/nixos/modules/services/networking/openfire.nix b/nixos/modules/services/networking/openfire.nix
index c74f3611f79e..fe0499d52323 100644
--- a/nixos/modules/services/networking/openfire.nix
+++ b/nixos/modules/services/networking/openfire.nix
@@ -12,6 +12,7 @@ with lib;
       enable = mkEnableOption "OpenFire XMPP server";
 
       usePostgreSQL = mkOption {
+        type = types.bool;
         default = true;
         description = "
           Whether you use PostgreSQL service for your storage back-end.
diff --git a/nixos/modules/services/networking/prosody.nix b/nixos/modules/services/networking/prosody.nix
index 7a503e711665..cdd341c9fb62 100644
--- a/nixos/modules/services/networking/prosody.nix
+++ b/nixos/modules/services/networking/prosody.nix
@@ -1,9 +1,7 @@
 { config, lib, pkgs, ... }:
 
 with lib;
-
 let
-
   cfg = config.services.prosody;
 
   sslOpts = { ... }: {
@@ -30,8 +28,21 @@ let
     };
   };
 
+  discoOpts = {
+    options = {
+      url = mkOption {
+        type = types.str;
+        description = "URL of the endpoint you want to make discoverable";
+      };
+      description = mkOption {
+        type = types.str;
+        description = "A short description of the endpoint you want to advertise";
+      };
+    };
+  };
+
   moduleOpts = {
-    # Generally required
+    # Required for compliance with https://compliance.conversations.im/about/
     roster = mkOption {
       type = types.bool;
       default = true;
@@ -69,6 +80,18 @@ let
       description = "Keep multiple clients in sync";
     };
 
+    csi = mkOption {
+      type = types.bool;
+      default = true;
+      description = "Implements the CSI protocol that allows clients to report their active/inactive state to the server";
+    };
+
+    cloud_notify = mkOption {
+      type = types.bool;
+      default = true;
+      description = "Push notifications to inform users of new messages or other pertinent information even when they have no XMPP clients online";
+    };
+
     pep = mkOption {
       type = types.bool;
       default = true;
@@ -89,10 +112,22 @@ let
 
     vcard = mkOption {
       type = types.bool;
-      default = true;
+      default = false;
       description = "Allow users to set vCards";
     };
 
+    vcard_legacy = mkOption {
+      type = types.bool;
+      default = true;
+      description = "Converts users profiles and Avatars between old and new formats";
+    };
+
+    bookmarks = mkOption {
+      type = types.bool;
+      default = true;
+      description = "Allows interop between older clients that use XEP-0048: Bookmarks in its 1.0 version and recent clients which use it in PEP";
+    };
+
     # Nice to have
     version = mkOption {
       type = types.bool;
@@ -126,10 +161,16 @@ let
 
     mam = mkOption {
       type = types.bool;
-      default = false;
+      default = true;
       description = "Store messages in an archive and allow users to access it";
     };
 
+    smacks = mkOption {
+      type = types.bool;
+      default = true;
+      description = "Allow a client to resume a disconnected session, and prevent message loss";
+    };
+
     # Admin interfaces
     admin_adhoc = mkOption {
       type = types.bool;
@@ -137,6 +178,18 @@ let
       description = "Allows administration via an XMPP client that supports ad-hoc commands";
     };
 
+    http_files = mkOption {
+      type = types.bool;
+      default = true;
+      description = "Serve static files from a directory over HTTP";
+    };
+
+    proxy65 = mkOption {
+      type = types.bool;
+      default = true;
+      description = "Enables a file transfer proxy service which clients behind NAT can use";
+    };
+
     admin_telnet = mkOption {
       type = types.bool;
       default = false;
@@ -156,12 +209,6 @@ let
       description = "Enable WebSocket support";
     };
 
-    http_files = mkOption {
-      type = types.bool;
-      default = false;
-      description = "Serve static files from a directory over HTTP";
-    };
-
     # Other specific functionality
     limits = mkOption {
       type = types.bool;
@@ -210,13 +257,6 @@ let
       default = false;
       description = "Legacy authentication. Only used by some old clients and bots";
     };
-
-    proxy65 = mkOption {
-      type = types.bool;
-      default = false;
-      description = "Enables a file transfer proxy service which clients behind NAT can use";
-    };
-
   };
 
   toLua = x:
@@ -235,6 +275,158 @@ let
     };
   '';
 
+  mucOpts = { ... }: {
+    options = {
+      domain = mkOption {
+        type = types.str;
+        description = "Domain name of the MUC";
+      };
+      name = mkOption {
+        type = types.str;
+        description = "The name to return in service discovery responses for the MUC service itself";
+        default = "Prosody Chatrooms";
+      };
+      restrictRoomCreation = mkOption {
+        type = types.enum [ true false "admin" "local" ];
+        default = false;
+        description = "Restrict room creation to server admins";
+      };
+      maxHistoryMessages = mkOption {
+        type = types.int;
+        default = 20;
+        description = "Specifies a limit on what each room can be configured to keep";
+      };
+      roomLocking = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Enables room locking, which means that a room must be
+          configured before it can be used. Locked rooms are invisible
+          and cannot be entered by anyone but the creator
+        '';
+      };
+      roomLockTimeout = mkOption {
+        type = types.int;
+        default = 300;
+        description = ''
+          Timout after which the room is destroyed or unlocked if not
+          configured, in seconds
+       '';
+      };
+      tombstones = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          When a room is destroyed, it leaves behind a tombstone which
+          prevents the room being entered or recreated. It also allows
+          anyone who was not in the room at the time it was destroyed
+          to learn about it, and to update their bookmarks. Tombstones
+          prevents the case where someone could recreate a previously
+          semi-anonymous room in order to learn the real JIDs of those
+          who often join there.
+        '';
+      };
+      tombstoneExpiry = mkOption {
+        type = types.int;
+        default = 2678400;
+        description = ''
+          This settings controls how long a tombstone is considered
+          valid. It defaults to 31 days. After this time, the room in
+          question can be created again.
+        '';
+      };
+
+      vcard_muc = mkOption {
+        type = types.bool;
+        default = true;
+      description = "Adds the ability to set vCard for Multi User Chat rooms";
+      };
+
+      # Extra parameters. Defaulting to prosody default values.
+      # Adding them explicitly to make them visible from the options
+      # documentation.
+      #
+      # See https://prosody.im/doc/modules/mod_muc for more details.
+      roomDefaultPublic = mkOption {
+        type = types.bool;
+        default = true;
+        description = "If set, the MUC rooms will be public by default.";
+      };
+      roomDefaultMembersOnly = mkOption {
+        type = types.bool;
+        default = false;
+        description = "If set, the MUC rooms will only be accessible to the members by default.";
+      };
+      roomDefaultModerated = mkOption {
+        type = types.bool;
+        default = false;
+        description = "If set, the MUC rooms will be moderated by default.";
+      };
+      roomDefaultPublicJids = mkOption {
+        type = types.bool;
+        default = false;
+        description = "If set, the MUC rooms will display the public JIDs by default.";
+      };
+      roomDefaultChangeSubject = mkOption {
+        type = types.bool;
+        default = false;
+        description = "If set, the rooms will display the public JIDs by default.";
+      };
+      roomDefaultHistoryLength = mkOption {
+        type = types.int;
+        default = 20;
+        description = "Number of history message sent to participants by default.";
+      };
+      roomDefaultLanguage = mkOption {
+        type = types.str;
+        default = "en";
+        description = "Default room language.";
+      };
+      extraConfig = mkOption {
+        type = types.lines;
+        default = "";
+        description = "Additional MUC specific configuration";
+      };
+    };
+  };
+
+  uploadHttpOpts = { ... }: {
+    options = {
+      domain = mkOption {
+        type = types.nullOr types.str;
+        description = "Domain name for the http-upload service";
+      };
+      uploadFileSizeLimit = mkOption {
+        type = types.str;
+        default = "50 * 1024 * 1024";
+        description = "Maximum file size, in bytes. Defaults to 50MB.";
+      };
+      uploadExpireAfter = mkOption {
+        type = types.str;
+        default = "60 * 60 * 24 * 7";
+        description = "Max age of a file before it gets deleted, in seconds.";
+      };
+      userQuota = mkOption {
+        type = types.nullOr types.int;
+        default = null;
+        example = 1234;
+        description = ''
+          Maximum size of all uploaded files per user, in bytes. There
+          will be no quota if this option is set to null.
+        '';
+      };
+      httpUploadPath = mkOption {
+        type = types.str;
+        description = ''
+          Directory where the uploaded files will be stored. By
+          default, uploaded files are put in a sub-directory of the
+          default Prosody storage path (usually /var/lib/prosody).
+        '';
+        default = "/var/lib/prosody";
+      };
+    };
+  };
+
   vHostOpts = { ... }: {
 
     options = {
@@ -283,6 +475,27 @@ in
         description = "Whether to enable the prosody server";
       };
 
+      xmppComplianceSuite = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          The XEP-0423 defines a set of recommended XEPs to implement
+          for a server. It's generally a good idea to implement this
+          set of extensions if you want to provide your users with a
+          good XMPP experience.
+
+          This NixOS module aims to provide a "advanced server"
+          experience as per defined in the XEP-0423[1] specification.
+
+          Setting this option to true will prevent you from building a
+          NixOS configuration which won't comply with this standard.
+          You can explicitely decide to ignore this standard if you
+          know what you are doing by setting this option to false.
+
+          [1] https://xmpp.org/extensions/xep-0423.html
+        '';
+      };
+
       package = mkOption {
         type = types.package;
         description = "Prosody package to use";
@@ -302,6 +515,12 @@ in
         default = "/var/lib/prosody";
       };
 
+      disco_items = mkOption {
+        type = types.listOf (types.submodule discoOpts);
+        default = [];
+        description = "List of discoverable items you want to advertise.";
+      };
+
       user = mkOption {
         type = types.str;
         default = "prosody";
@@ -320,6 +539,31 @@ in
         description = "Allow account creation";
       };
 
+      # HTTP server-related options
+      httpPorts = mkOption {
+        type = types.listOf types.int;
+        description = "Listening HTTP ports list for this service.";
+        default = [ 5280 ];
+      };
+
+      httpInterfaces = mkOption {
+        type = types.listOf types.str;
+        default = [ "*" "::" ];
+        description = "Interfaces on which the HTTP server will listen on.";
+      };
+
+      httpsPorts = mkOption {
+        type = types.listOf types.int;
+        description = "Listening HTTPS ports list for this service.";
+        default = [ 5281 ];
+      };
+
+      httpsInterfaces = mkOption {
+        type = types.listOf types.str;
+        default = [ "*" "::" ];
+        description = "Interfaces on which the HTTPS server will listen on.";
+      };
+
       c2sRequireEncryption = mkOption {
         type = types.bool;
         default = true;
@@ -387,6 +631,26 @@ in
         description = "Addtional path in which to look find plugins/modules";
       };
 
+      uploadHttp = mkOption {
+        description = ''
+          Configures the Prosody builtin HTTP server to handle user uploads.
+        '';
+        type = types.nullOr (types.submodule uploadHttpOpts);
+        default = null;
+        example = {
+          domain = "uploads.my-xmpp-example-host.org";
+        };
+      };
+
+      muc = mkOption {
+        type = types.listOf (types.submodule mucOpts);
+        default = [ ];
+        example = [ {
+          domain = "conference.my-xmpp-example-host.org";
+        } ];
+        description = "Multi User Chat (MUC) configuration";
+      };
+
       virtualHosts = mkOption {
 
         description = "Define the virtual hosts";
@@ -443,9 +707,44 @@ in
 
   config = mkIf cfg.enable {
 
+    assertions = let
+      genericErrMsg = ''
+
+          Having a server not XEP-0423-compliant might make your XMPP
+          experience terrible. See the NixOS manual for further
+          informations.
+
+          If you know what you're doing, you can disable this warning by
+          setting config.services.prosody.xmppComplianceSuite to false.
+      '';
+      errors = [
+        { assertion = (builtins.length cfg.muc > 0) || !cfg.xmppComplianceSuite;
+          message = ''
+            You need to setup at least a MUC domain to comply with
+            XEP-0423.
+          '' + genericErrMsg;}
+        { assertion = cfg.uploadHttp != null || !cfg.xmppComplianceSuite;
+          message = ''
+            You need to setup the uploadHttp module through
+            config.services.prosody.uploadHttp to comply with
+            XEP-0423.
+          '' + genericErrMsg;}
+      ];
+    in errors;
+
     environment.systemPackages = [ cfg.package ];
 
-    environment.etc."prosody/prosody.cfg.lua".text = ''
+    environment.etc."prosody/prosody.cfg.lua".text =
+      let
+        httpDiscoItems = if (cfg.uploadHttp != null)
+            then [{ url = cfg.uploadHttp.domain; description = "HTTP upload endpoint";}]
+            else [];
+        mucDiscoItems = builtins.foldl'
+            (acc: muc: [{ url = muc.domain; description = "${muc.domain} MUC endpoint";}] ++ acc)
+            []
+            cfg.muc;
+        discoItems = cfg.disco_items ++ httpDiscoItems ++ mucDiscoItems;
+      in ''
 
       pidfile = "/run/prosody/prosody.pid"
 
@@ -472,6 +771,10 @@ in
         ${ lib.concatStringsSep "\n" (map (x: "${toLua x};") cfg.extraModules)}
       };
 
+      disco_items = {
+      ${ lib.concatStringsSep "\n" (builtins.map (x: ''{ "${x.url}", "${x.description}"};'') discoItems)} 
+      };
+
       allow_registration = ${toLua cfg.allowRegistration}
 
       c2s_require_encryption = ${toLua cfg.c2sRequireEncryption}
@@ -486,8 +789,44 @@ in
 
       authentication = ${toLua cfg.authentication}
 
+      http_interfaces = ${toLua cfg.httpInterfaces}
+
+      https_interfaces = ${toLua cfg.httpsInterfaces}
+
+      http_ports = ${toLua cfg.httpPorts}
+
+      https_ports = ${toLua cfg.httpsPorts}
+
       ${ cfg.extraConfig }
 
+      ${lib.concatMapStrings (muc: ''
+        Component ${toLua muc.domain} "muc"
+            modules_enabled = { "muc_mam"; ${optionalString muc.vcard_muc ''"vcard_muc";'' } }
+            name = ${toLua muc.name}
+            restrict_room_creation = ${toLua muc.restrictRoomCreation}
+            max_history_messages = ${toLua muc.maxHistoryMessages}
+            muc_room_locking = ${toLua muc.roomLocking}
+            muc_room_lock_timeout = ${toLua muc.roomLockTimeout}
+            muc_tombstones = ${toLua muc.tombstones}
+            muc_tombstone_expiry = ${toLua muc.tombstoneExpiry}
+            muc_room_default_public = ${toLua muc.roomDefaultPublic}
+            muc_room_default_members_only = ${toLua muc.roomDefaultMembersOnly}
+            muc_room_default_moderated = ${toLua muc.roomDefaultModerated}
+            muc_room_default_public_jids = ${toLua muc.roomDefaultPublicJids}
+            muc_room_default_change_subject = ${toLua muc.roomDefaultChangeSubject}
+            muc_room_default_history_length = ${toLua muc.roomDefaultHistoryLength}
+            muc_room_default_language = ${toLua muc.roomDefaultLanguage}
+            ${ muc.extraConfig }
+        '') cfg.muc}
+
+      ${ lib.optionalString (cfg.uploadHttp != null) ''
+        Component ${toLua cfg.uploadHttp.domain} "http_upload"
+            http_upload_file_size_limit = ${cfg.uploadHttp.uploadFileSizeLimit}
+            http_upload_expire_after = ${cfg.uploadHttp.uploadExpireAfter}
+            ${lib.optionalString (cfg.uploadHttp.userQuota != null) "http_upload_quota = ${toLua cfg.uploadHttp.userQuota}"}
+            http_upload_path = ${toLua cfg.uploadHttp.httpUploadPath}
+      ''}
+
       ${ lib.concatStringsSep "\n" (lib.mapAttrsToList (n: v: ''
         VirtualHost "${v.domain}"
           enabled = ${boolToString v.enabled};
@@ -522,9 +861,22 @@ in
         PIDFile = "/run/prosody/prosody.pid";
         ExecStart = "${cfg.package}/bin/prosodyctl start";
         ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+
+        MemoryDenyWriteExecute = true;
+        PrivateDevices = true;
+        PrivateMounts = true;
+        PrivateTmp = true;
+        ProtectControlGroups = true;
+        ProtectHome = true;
+        ProtectHostname = true;
+        ProtectKernelModules = true;
+        ProtectKernelTunables = true;
+        RestrictNamespaces = true;
+        RestrictRealtime = true;
+        RestrictSUIDSGID = true;
       };
     };
 
   };
-
+  meta.doc = ./prosody.xml;
 }
diff --git a/nixos/modules/services/networking/prosody.xml b/nixos/modules/services/networking/prosody.xml
new file mode 100644
index 000000000000..7859cb1578b7
--- /dev/null
+++ b/nixos/modules/services/networking/prosody.xml
@@ -0,0 +1,88 @@
+<chapter xmlns="http://docbook.org/ns/docbook"
+         xmlns:xlink="http://www.w3.org/1999/xlink"
+         xmlns:xi="http://www.w3.org/2001/XInclude"
+         version="5.0"
+         xml:id="module-services-prosody">
+ <title>Prosody</title>
+ <para>
+  <link xlink:href="https://prosody.im/">Prosody</link> is an open-source, modern XMPP server.
+ </para>
+ <section xml:id="module-services-prosody-basic-usage">
+  <title>Basic usage</title>
+
+  <para>
+    A common struggle for most XMPP newcomers is to find the right set
+    of XMPP Extensions (XEPs) to setup. Forget to activate a few of
+    those and your XMPP experience might turn into a nightmare!
+  </para>
+
+  <para>
+    The XMPP community tackles this problem by creating a meta-XEP
+    listing a decent set of XEPs you should implement. This meta-XEP
+    is issued every year, the 2020 edition being
+    <link xlink:href="https://xmpp.org/extensions/xep-0423.html">XEP-0423</link>.
+  </para>
+  <para>
+    The NixOS Prosody module will implement most of these recommendend XEPs out of
+    the box. That being said, two components still require some
+    manual configuration: the
+    <link xlink:href="https://xmpp.org/extensions/xep-0045.html">Multi User Chat (MUC)</link>
+    and the <link xlink:href="https://xmpp.org/extensions/xep-0363.html">HTTP File Upload</link> ones.
+    You'll need to create a DNS subdomain for each of those. The current convention is to name your
+    MUC endpoint <literal>conference.example.org</literal> and your HTTP upload domain <literal>upload.example.org</literal>.
+  </para>
+  <para>
+    A good configuration to start with, including a
+    <link xlink:href="https://xmpp.org/extensions/xep-0045.html">Multi User Chat (MUC)</link>
+    endpoint as well as a <link xlink:href="https://xmpp.org/extensions/xep-0363.html">HTTP File Upload</link>
+    endpoint will look like this:
+    <programlisting>
+services.prosody = {
+  <link linkend="opt-services.prosody.enable">enable</link> = true;
+  <link linkend="opt-services.prosody.admins">admins</link> = [ "root@example.org" ];
+  <link linkend="opt-services.prosody.ssl.cert">ssl.cert</link> = "/var/lib/acme/example.org/fullchain.pem";
+  <link linkend="opt-services.prosody.ssl.key">ssl.key</link> = "/var/lib/acme/example.org/key.pem";
+  <link linkend="opt-services.prosody.virtualHosts">virtualHosts</link>."example.org" = {
+      <link linkend="opt-services.prosody.virtualHosts._name__.enabled">enabled</link> = true;
+      <link linkend="opt-services.prosody.virtualHosts._name__.domain">domain</link> = "example.org";
+      <link linkend="opt-services.prosody.virtualHosts._name__.ssl.cert">ssl.cert</link> = "/var/lib/acme/example.org/fullchain.pem";
+      <link linkend="opt-services.prosody.virtualHosts._name__.ssl.key">ssl.key</link> = "/var/lib/acme/example.org/key.pem";
+  };
+  <link linkend="opt-services.prosody.muc">muc</link> = [ {
+      <link linkend="opt-services.prosody.muc">domain</link> = "conference.example.org";
+  } ];
+  <link linkend="opt-services.prosody.uploadHttp">uploadHttp</link> = {
+      <link linkend="opt-services.prosody.uploadHttp.domain">domain</link> = "upload.example.org";
+  };
+};</programlisting>
+  </para>
+ </section>
+ <section xml:id="module-services-prosody-letsencrypt">
+  <title>Let's Encrypt Configuration</title>
+ <para>
+   As you can see in the code snippet from the
+   <link linkend="module-services-prosody-basic-usage">previous section</link>,
+   you'll need a single TLS certificate covering your main endpoint,
+   the MUC one as well as the HTTP Upload one. We can generate such a
+   certificate by leveraging the ACME
+   <link linkend="opt-security.acme.certs._name_.extraDomains">extraDomains</link> module option.
+ </para>
+ <para>
+   Provided the setup detailed in the previous section, you'll need the following acme configuration to generate
+   a TLS certificate for the three endponits:
+    <programlisting>
+security.acme = {
+  <link linkend="opt-security.acme.email">email</link> = "root@example.org";
+  <link linkend="opt-security.acme.acceptTerms">acceptTerms</link> = true;
+  <link linkend="opt-security.acme.certs">certs</link> = {
+    "example.org" = {
+      <link linkend="opt-security.acme.certs._name_.webroot">webroot</link> = "/var/www/example.org";
+      <link linkend="opt-security.acme.certs._name_.email">email</link> = "root@example.org";
+      <link linkend="opt-security.acme.certs._name_.extraDomains">extraDomains."conference.example.org"</link> = null;
+      <link linkend="opt-security.acme.certs._name_.extraDomains">extraDomains."upload.example.org"</link> = null;
+    };
+  };
+};</programlisting>
+ </para>
+</section>
+</chapter>
diff --git a/nixos/modules/services/networking/skydns.nix b/nixos/modules/services/networking/skydns.nix
index 6ad18bb22403..e79d6de92644 100644
--- a/nixos/modules/services/networking/skydns.nix
+++ b/nixos/modules/services/networking/skydns.nix
@@ -83,7 +83,7 @@ in {
         SKYDNS_NAMESERVERS = concatStringsSep "," cfg.nameservers;
       };
       serviceConfig = {
-        ExecStart = "${cfg.package.bin}/bin/skydns";
+        ExecStart = "${cfg.package}/bin/skydns";
       };
     };
 
diff --git a/nixos/modules/services/networking/ssh/lshd.nix b/nixos/modules/services/networking/ssh/lshd.nix
index 892e59778c34..41d0584080e4 100644
--- a/nixos/modules/services/networking/ssh/lshd.nix
+++ b/nixos/modules/services/networking/ssh/lshd.nix
@@ -54,21 +54,25 @@ in
       };
 
       syslog = mkOption {
+        type = types.bool;
         default = true;
         description = ''Whether to enable syslog output.'';
       };
 
       passwordAuthentication = mkOption {
+        type = types.bool;
         default = true;
         description = ''Whether to enable password authentication.'';
       };
 
       publicKeyAuthentication = mkOption {
+        type = types.bool;
         default = true;
         description = ''Whether to enable public key authentication.'';
       };
 
       rootLogin = mkOption {
+        type = types.bool;
         default = false;
         description = ''Whether to enable remote root login.'';
       };
@@ -90,11 +94,13 @@ in
       };
 
       tcpForwarding = mkOption {
+        type = types.bool;
         default = true;
         description = ''Whether to enable TCP/IP forwarding.'';
       };
 
       x11Forwarding = mkOption {
+        type = types.bool;
         default = true;
         description = ''Whether to enable X11 forwarding.'';
       };
diff --git a/nixos/modules/services/networking/tcpcrypt.nix b/nixos/modules/services/networking/tcpcrypt.nix
index 18f2e135124b..5a91054e1668 100644
--- a/nixos/modules/services/networking/tcpcrypt.nix
+++ b/nixos/modules/services/networking/tcpcrypt.nix
@@ -15,6 +15,7 @@ in
   options = {
 
     networking.tcpcrypt.enable = mkOption {
+      type = types.bool;
       default = false;
       description = ''
         Whether to enable opportunistic TCP encryption. If the other end
diff --git a/nixos/modules/services/networking/thelounge.nix b/nixos/modules/services/networking/thelounge.nix
index 875d8f661697..a1b06703484b 100644
--- a/nixos/modules/services/networking/thelounge.nix
+++ b/nixos/modules/services/networking/thelounge.nix
@@ -62,7 +62,6 @@ in {
     systemd.services.thelounge = {
       description = "The Lounge web IRC client";
       wantedBy = [ "multi-user.target" ];
-      environment = { THELOUNGE_HOME = dataDir; };
       preStart = "ln -sf ${pkgs.writeText "config.js" configJsData} ${dataDir}/config.js";
       serviceConfig = {
         User = "thelounge";
diff --git a/nixos/modules/services/networking/wicd.nix b/nixos/modules/services/networking/wicd.nix
index 03c6bd28aaba..aa10a50f876a 100644
--- a/nixos/modules/services/networking/wicd.nix
+++ b/nixos/modules/services/networking/wicd.nix
@@ -9,6 +9,7 @@ with lib;
   options = {
 
     networking.wicd.enable = mkOption {
+      type = types.bool;
       default = false;
       description = ''
         Whether to start <command>wicd</command>. Wired and
diff --git a/nixos/modules/services/networking/yggdrasil.nix b/nixos/modules/services/networking/yggdrasil.nix
index 9e675ecd6f4b..ecd1406b4832 100644
--- a/nixos/modules/services/networking/yggdrasil.nix
+++ b/nixos/modules/services/networking/yggdrasil.nix
@@ -83,6 +83,14 @@ in {
         '';
       };
 
+      group = mkOption {
+        type = types.str;
+        default = "root";
+        example = "wheel";
+        description =
+          "Group to grant acces to the Yggdrasil control socket.";
+      };
+
       openMulticastPort = mkOption {
         type = bool;
         default = false;
@@ -144,8 +152,9 @@ in {
         ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
         Restart = "always";
 
+        Group = cfg.group;
         RuntimeDirectory = "yggdrasil";
-        RuntimeDirectoryMode = "0700";
+        RuntimeDirectoryMode = "0750";
         BindReadOnlyPaths = mkIf configFileProvided
           [ "${cfg.configFile}" ];
 
diff --git a/nixos/modules/services/printing/cupsd.nix b/nixos/modules/services/printing/cupsd.nix
index 59306d625e6b..e67badfcd29e 100644
--- a/nixos/modules/services/printing/cupsd.nix
+++ b/nixos/modules/services/printing/cupsd.nix
@@ -153,6 +153,16 @@ in
         '';
       };
 
+      allowFrom = mkOption {
+        type = types.listOf types.str;
+        default = [ "localhost" ];
+        example = [ "all" ];
+        apply = concatMapStringsSep "\n" (x: "Allow ${x}");
+        description = ''
+          From which hosts to allow unconditional access.
+        '';
+      };
+
       bindirCmds = mkOption {
         type = types.lines;
         internal = true;
@@ -403,19 +413,19 @@ in
 
         <Location />
           Order allow,deny
-          Allow localhost
+          ${cfg.allowFrom}
         </Location>
 
         <Location /admin>
           Order allow,deny
-          Allow localhost
+          ${cfg.allowFrom}
         </Location>
 
         <Location /admin/conf>
           AuthType Basic
           Require user @SYSTEM
           Order allow,deny
-          Allow localhost
+          ${cfg.allowFrom}
         </Location>
 
         <Policy default>
diff --git a/nixos/modules/services/security/hologram-agent.nix b/nixos/modules/services/security/hologram-agent.nix
index a5087b0a99b4..e37334b3cf5e 100644
--- a/nixos/modules/services/security/hologram-agent.nix
+++ b/nixos/modules/services/security/hologram-agent.nix
@@ -43,12 +43,12 @@ in {
       description = "Provide EC2 instance credentials to machines outside of EC2";
       after       = [ "network.target" ];
       wantedBy    = [ "multi-user.target" ];
-      requires    = [ "network-link-dummy0.service" "network-addresses-dummy0.service" ]; 
+      requires    = [ "network-link-dummy0.service" "network-addresses-dummy0.service" ];
       preStart = ''
         /run/current-system/sw/bin/rm -fv /run/hologram.sock
       '';
       serviceConfig = {
-        ExecStart = "${pkgs.hologram.bin}/bin/hologram-agent -debug -conf ${cfgFile} -port ${cfg.httpPort}";
+        ExecStart = "${pkgs.hologram}/bin/hologram-agent -debug -conf ${cfgFile} -port ${cfg.httpPort}";
       };
     };
 
diff --git a/nixos/modules/services/security/hologram-server.nix b/nixos/modules/services/security/hologram-server.nix
index bad02c7440ba..4acf6ae0e218 100644
--- a/nixos/modules/services/security/hologram-server.nix
+++ b/nixos/modules/services/security/hologram-server.nix
@@ -123,7 +123,7 @@ in {
       wantedBy    = [ "multi-user.target" ];
 
       serviceConfig = {
-        ExecStart = "${pkgs.hologram.bin}/bin/hologram-server --debug --conf ${cfgFile}";
+        ExecStart = "${pkgs.hologram}/bin/hologram-server --debug --conf ${cfgFile}";
       };
     };
   };
diff --git a/nixos/modules/services/security/oauth2_proxy.nix b/nixos/modules/services/security/oauth2_proxy.nix
index 46caadee2042..d5c5437329ea 100644
--- a/nixos/modules/services/security/oauth2_proxy.nix
+++ b/nixos/modules/services/security/oauth2_proxy.nix
@@ -577,7 +577,7 @@ in
       serviceConfig = {
         User = "oauth2_proxy";
         Restart = "always";
-        ExecStart = "${cfg.package.bin}/bin/oauth2_proxy ${configString}";
+        ExecStart = "${cfg.package}/bin/oauth2_proxy ${configString}";
         EnvironmentFile = mkIf (cfg.keyFile != null) cfg.keyFile;
       };
     };
diff --git a/nixos/modules/services/torrent/deluge.nix b/nixos/modules/services/torrent/deluge.nix
index 2f1e7300ca38..45398cb26138 100644
--- a/nixos/modules/services/torrent/deluge.nix
+++ b/nixos/modules/services/torrent/deluge.nix
@@ -142,7 +142,7 @@ in {
           description = ''
             Extra packages available at runtime to enable Deluge's plugins. For example,
             extraction utilities are required for the built-in "Extractor" plugin.
-            This always contains unzip, gnutar, xz, p7zip and bzip2.
+            This always contains unzip, gnutar, xz and bzip2.
           '';
         };
 
@@ -187,7 +187,7 @@ in {
     );
 
     # Provide a default set of `extraPackages`.
-    services.deluge.extraPackages = with pkgs; [ unzip gnutar xz p7zip bzip2 ];
+    services.deluge.extraPackages = with pkgs; [ unzip gnutar xz bzip2 ];
 
     systemd.tmpfiles.rules = [
       "d '${cfg.dataDir}' 0770 ${cfg.user} ${cfg.group}"
diff --git a/nixos/modules/services/web-apps/mattermost.nix b/nixos/modules/services/web-apps/mattermost.nix
index 853347bf86e2..f5c2c356afce 100644
--- a/nixos/modules/services/web-apps/mattermost.nix
+++ b/nixos/modules/services/web-apps/mattermost.nix
@@ -224,7 +224,7 @@ in
         serviceConfig = {
           User = "nobody";
           Group = "nogroup";
-          ExecStart = "${pkgs.matterircd.bin}/bin/matterircd ${concatStringsSep " " cfg.matterircd.parameters}";
+          ExecStart = "${pkgs.matterircd}/bin/matterircd ${concatStringsSep " " cfg.matterircd.parameters}";
           WorkingDirectory = "/tmp";
           PrivateTmp = true;
           Restart = "always";
diff --git a/nixos/modules/services/web-apps/mediawiki.nix b/nixos/modules/services/web-apps/mediawiki.nix
index e9ed53857d81..0a5b6047bb58 100644
--- a/nixos/modules/services/web-apps/mediawiki.nix
+++ b/nixos/modules/services/web-apps/mediawiki.nix
@@ -29,7 +29,7 @@ let
       '') cfg.skins)}
 
       ${concatStringsSep "\n" (mapAttrsToList (k: v: ''
-        ln -s ${v} $out/share/mediawiki/extensions/${k}
+        ln -s ${if v != null then v else "$src/share/mediawiki/extensions/${k}"} $out/share/mediawiki/extensions/${k}
       '') cfg.extensions)}
     '';
   };
@@ -204,17 +204,28 @@ in
         default = {};
         type = types.attrsOf types.path;
         description = ''
-          List of paths whose content is copied to the 'skins'
-          subdirectory of the MediaWiki installation.
+          Attribute set of paths whose content is copied to the <filename>skins</filename>
+          subdirectory of the MediaWiki installation in addition to the default skins.
         '';
       };
 
       extensions = mkOption {
         default = {};
-        type = types.attrsOf types.path;
+        type = types.attrsOf (types.nullOr types.path);
         description = ''
-          List of paths whose content is copied to the 'extensions'
-          subdirectory of the MediaWiki installation.
+          Attribute set of paths whose content is copied to the <filename>extensions</filename>
+          subdirectory of the MediaWiki installation and enabled in configuration.
+
+          Use <literal>null</literal> instead of path to enable extensions that are part of MediaWiki.
+        '';
+        example = literalExample ''
+          {
+            Matomo = pkgs.fetchzip {
+              url = "https://github.com/DaSchTour/matomo-mediawiki-extension/archive/v4.0.1.tar.gz";
+              sha256 = "0g5rd3zp0avwlmqagc59cg9bbkn3r7wx7p6yr80s644mj6dlvs1b";
+            };
+            ParserFunctions = null;
+          }
         '';
       };
 
diff --git a/nixos/modules/services/web-apps/nextcloud.nix b/nixos/modules/services/web-apps/nextcloud.nix
index 5f6f2bc7a16d..f826096bf608 100644
--- a/nixos/modules/services/web-apps/nextcloud.nix
+++ b/nixos/modules/services/web-apps/nextcloud.nix
@@ -11,8 +11,8 @@ let
       base = pkgs.php74;
     in
       base.buildEnv {
-        extensions = e: with e;
-          base.enabledExtensions ++ [
+        extensions = { enabled, all }: with all;
+          enabled ++ [
             apcu redis memcached imagick
           ];
         extraConfig = phpOptionsStr;
diff --git a/nixos/modules/services/web-servers/apache-httpd/default.nix b/nixos/modules/services/web-servers/apache-httpd/default.nix
index 653c17068346..8abee7130d7c 100644
--- a/nixos/modules/services/web-servers/apache-httpd/default.nix
+++ b/nixos/modules/services/web-servers/apache-httpd/default.nix
@@ -338,7 +338,7 @@ let
     }
     ''
       cat ${php}/etc/php.ini > $out
-      cat ${php}/lib/custom-php.ini > $out
+      cat ${php.phpIni} > $out
       echo "$options" >> $out
     '';
 
diff --git a/nixos/modules/services/web-servers/jboss/default.nix b/nixos/modules/services/web-servers/jboss/default.nix
index 3a125982831b..ca5b8635fc00 100644
--- a/nixos/modules/services/web-servers/jboss/default.nix
+++ b/nixos/modules/services/web-servers/jboss/default.nix
@@ -60,6 +60,7 @@ in
       };
 
       useJK = mkOption {
+        type = types.bool;
         default = false;
         description = "Whether to use to connector to the Apache HTTP server";
       };
diff --git a/nixos/modules/services/web-servers/unit/default.nix b/nixos/modules/services/web-servers/unit/default.nix
index f8a18954fc99..989866144e1e 100644
--- a/nixos/modules/services/web-servers/unit/default.nix
+++ b/nixos/modules/services/web-servers/unit/default.nix
@@ -91,41 +91,47 @@ in {
       description = "Unit App Server";
       after = [ "network.target" ];
       wantedBy = [ "multi-user.target" ];
-      path = with pkgs; [ curl ];
       preStart = ''
-        test -f '${cfg.stateDir}/conf.json' || rm -f '${cfg.stateDir}/conf.json'
+        [ ! -e '${cfg.stateDir}/conf.json' ] || rm -f '${cfg.stateDir}/conf.json'
       '';
       postStart = ''
-        curl -X PUT --data-binary '@${configFile}' --unix-socket '/run/unit/control.unit.sock' 'http://localhost/config'
+        ${pkgs.curl}/bin/curl -X PUT --data-binary '@${configFile}' --unix-socket '/run/unit/control.unit.sock' 'http://localhost/config'
       '';
       serviceConfig = {
+        Type = "forking";
+        PIDFile = "/run/unit/unit.pid";
         ExecStart = ''
           ${cfg.package}/bin/unitd --control 'unix:/run/unit/control.unit.sock' --pid '/run/unit/unit.pid' \
-                                   --log '${cfg.logDir}/unit.log' --state '${cfg.stateDir}' --no-daemon \
+                                   --log '${cfg.logDir}/unit.log' --state '${cfg.stateDir}' \
                                    --user ${cfg.user} --group ${cfg.group}
         '';
-        # User and group
-        User = cfg.user;
-        Group = cfg.group;
-        # Capabilities
-        AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" "CAP_SETGID" "CAP_SETUID" ];
+        ExecStop = ''
+          ${pkgs.curl}/bin/curl -X DELETE --unix-socket '/run/unit/control.unit.sock' 'http://localhost/config'
+        '';
+        # Runtime directory and mode
+        RuntimeDirectory = "unit";
+        RuntimeDirectoryMode = "0750";
+        # Access write directories
+        ReadWritePaths = [ cfg.stateDir cfg.logDir ];
         # Security
         NoNewPrivileges = true;
         # Sandboxing
-        ProtectSystem = "full";
+        ProtectSystem = "strict";
         ProtectHome = true;
-        RuntimeDirectory = "unit";
-        RuntimeDirectoryMode = "0750";
         PrivateTmp = true;
         PrivateDevices = true;
         ProtectHostname = true;
         ProtectKernelTunables = true;
         ProtectKernelModules = true;
         ProtectControlGroups = true;
+        RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" ];
         LockPersonality = true;
         MemoryDenyWriteExecute = true;
         RestrictRealtime = true;
+        RestrictSUIDSGID = true;
         PrivateMounts = true;
+        # System Call Filtering
+        SystemCallArchitectures = "native";
       };
     };
 
diff --git a/nixos/modules/services/x11/desktop-managers/pantheon.nix b/nixos/modules/services/x11/desktop-managers/pantheon.nix
index 01fe230b8a43..5fcc8590232a 100644
--- a/nixos/modules/services/x11/desktop-managers/pantheon.nix
+++ b/nixos/modules/services/x11/desktop-managers/pantheon.nix
@@ -109,7 +109,7 @@ in
 
       # Without this, elementary LightDM greeter will pre-select non-existent `default` session
       # https://github.com/elementary/greeter/issues/368
-      services.xserver.displayManager.defaultSession = "pantheon";
+      services.xserver.displayManager.defaultSession = mkDefault "pantheon";
 
       services.xserver.displayManager.sessionCommands = ''
         if test "$XDG_CURRENT_DESKTOP" = "Pantheon"; then
diff --git a/nixos/modules/services/x11/desktop-managers/pantheon.xml b/nixos/modules/services/x11/desktop-managers/pantheon.xml
index 9541f2cfd4ee..7905ceebd9aa 100644
--- a/nixos/modules/services/x11/desktop-managers/pantheon.xml
+++ b/nixos/modules/services/x11/desktop-managers/pantheon.xml
@@ -1,7 +1,7 @@
 <chapter xmlns="http://docbook.org/ns/docbook"
          xmlns:xlink="http://www.w3.org/1999/xlink"
          xml:id="chap-pantheon">
- <title>Pantheon Destkop</title>
+ <title>Pantheon Desktop</title>
  <para>
   Pantheon is the desktop environment created for the elementary OS distribution. It is written from scratch in Vala, utilizing GNOME technologies with GTK 3 and Granite.
  </para>
diff --git a/nixos/modules/services/x11/display-managers/gdm.nix b/nixos/modules/services/x11/display-managers/gdm.nix
index d7bef68e5bcb..622ea62f3a91 100644
--- a/nixos/modules/services/x11/display-managers/gdm.nix
+++ b/nixos/modules/services/x11/display-managers/gdm.nix
@@ -93,16 +93,17 @@ in
       };
 
       wayland = mkOption {
+        type = types.bool;
         default = true;
         description = ''
           Allow GDM to run on Wayland instead of Xserver.
           Note to enable Wayland with Nvidia you need to
           enable the <option>nvidiaWayland</option>.
         '';
-        type = types.bool;
       };
 
       nvidiaWayland = mkOption {
+        type = types.bool;
         default = false;
         description = ''
           Whether to allow wayland to be used with the proprietary
diff --git a/nixos/modules/services/x11/hardware/digimend.nix b/nixos/modules/services/x11/hardware/digimend.nix
index a9f5640905aa..b1b1682f00b2 100644
--- a/nixos/modules/services/x11/hardware/digimend.nix
+++ b/nixos/modules/services/x11/hardware/digimend.nix
@@ -16,12 +16,7 @@ in
 
     services.xserver.digimend = {
 
-      enable = mkOption {
-        default = false;
-        description = ''
-          Whether to enable the digimend drivers for Huion/XP-Pen/etc. tablets.
-        '';
-      };
+      enable = mkEnableOption "the digimend drivers for Huion/XP-Pen/etc. tablets";
 
     };
 
diff --git a/nixos/modules/services/x11/picom.nix b/nixos/modules/services/x11/picom.nix
index e3bd21be73e4..1289edd2904a 100644
--- a/nixos/modules/services/x11/picom.nix
+++ b/nixos/modules/services/x11/picom.nix
@@ -1,39 +1,48 @@
 { config, lib, pkgs, ... }:
 
 with lib;
-with builtins;
 
 let
 
   cfg = config.services.picom;
 
-  pairOf = x: with types; addCheck (listOf x) (y: length y == 2);
-
-  floatBetween = a: b: with lib; with types;
-    addCheck str (x: versionAtLeast x a && versionOlder x b);
-
-  toConf = attrs: concatStringsSep "\n"
-    (mapAttrsToList
-      (k: v: let
-        sep = if isAttrs v then ":" else "=";
-        # Basically a tinkered lib.generators.mkKeyValueDefault
-        mkValueString = v:
-          if isBool v        then boolToString v
-          else if isInt v    then toString v
-          else if isFloat v  then toString v
-          else if isString v then ''"${escape [ ''"'' ] v}"''
-          else if isList v   then "[ "
-            + concatMapStringsSep " , " mkValueString v
-            + " ]"
-          else if isAttrs v  then "{ "
-            + concatStringsSep " "
-              (mapAttrsToList
-                (key: value: "${toString key}=${mkValueString value};")
-                v)
-            + " }"
-          else abort "picom.mkValueString: unexpected type (v = ${v})";
-      in "${escape [ sep ] k}${sep}${mkValueString v};")
-      attrs);
+  pairOf = x: with types;
+    addCheck (listOf x) (y: length y == 2)
+    // { description = "pair of ${x.description}"; };
+
+  floatBetween = a: b: with types;
+    let
+      # toString prints floats with hardcoded high precision
+      floatToString = f: builtins.toJSON f;
+    in
+      addCheck float (x: x <= b && x >= a)
+      // { description = "a floating point number in " +
+                         "range [${floatToString a}, ${floatToString b}]"; };
+
+  mkDefaultAttrs = mapAttrs (n: v: mkDefault v);
+
+  # Basically a tinkered lib.generators.mkKeyValueDefault
+  # It either serializes a top-level definition "key: { values };"
+  # or an expression "key = { values };"
+  mkAttrsString = top:
+    mapAttrsToList (k: v:
+      let sep = if (top && isAttrs v) then ":" else "=";
+      in "${escape [ sep ] k}${sep}${mkValueString v};");
+
+  # This serializes a Nix expression to the libconfig format.
+  mkValueString = v:
+         if types.bool.check  v then boolToString v
+    else if types.int.check   v then toString v
+    else if types.float.check v then toString v
+    else if types.str.check   v then "\"${escape [ "\"" ] v}\""
+    else if builtins.isList   v then "[ ${concatMapStringsSep " , " mkValueString v} ]"
+    else if types.attrs.check v then "{ ${concatStringsSep " " (mkAttrsString false v) } }"
+    else throw ''
+                 invalid expression used in option services.picom.settings:
+                 ${v}
+               '';
+
+  toConf = attrs: concatStringsSep "\n" (mkAttrsString true cfg.settings);
 
   configFile = pkgs.writeText "picom.conf" (toConf cfg.settings);
 
@@ -61,7 +70,7 @@ in {
     };
 
     fadeDelta = mkOption {
-      type = types.addCheck types.int (x: x > 0);
+      type = types.ints.positive;
       default = 10;
       example = 5;
       description = ''
@@ -70,12 +79,11 @@ in {
     };
 
     fadeSteps = mkOption {
-      type = pairOf (floatBetween "0.01" "1.01");
-      default = [ "0.028" "0.03" ];
-      example = [ "0.04" "0.04" ];
+      type = pairOf (floatBetween 0.01 1);
+      default = [ 0.028 0.03 ];
+      example = [ 0.04 0.04 ];
       description = ''
         Opacity change between fade steps (in and out).
-        (numbers in range 0.01 - 1.0)
       '';
     };
 
@@ -111,11 +119,11 @@ in {
     };
 
     shadowOpacity = mkOption {
-      type = floatBetween "0.0" "1.01";
-      default = "0.75";
-      example = "0.8";
+      type = floatBetween 0 1;
+      default = 0.75;
+      example = 0.8;
       description = ''
-        Window shadows opacity (number in range 0.0 - 1.0).
+        Window shadows opacity.
       '';
     };
 
@@ -134,29 +142,29 @@ in {
     };
 
     activeOpacity = mkOption {
-      type = floatBetween "0.0" "1.01";
-      default = "1.0";
-      example = "0.8";
+      type = floatBetween 0 1;
+      default = 1.0;
+      example = 0.8;
       description = ''
-        Opacity of active windows (number in range 0.0 - 1.0).
+        Opacity of active windows.
       '';
     };
 
     inactiveOpacity = mkOption {
-      type = floatBetween "0.1" "1.01";
-      default = "1.0";
-      example = "0.8";
+      type = floatBetween 0.1 1;
+      default = 1.0;
+      example = 0.8;
       description = ''
-        Opacity of inactive windows (number in range 0.1 - 1.0).
+        Opacity of inactive windows.
       '';
     };
 
     menuOpacity = mkOption {
-      type = floatBetween "0.0" "1.01";
-      default = "1.0";
-      example = "0.8";
+      type = floatBetween 0 1;
+      default = 1.0;
+      example = 0.8;
       description = ''
-        Opacity of dropdown and popup menu (number in range 0.0 - 1.0).
+        Opacity of dropdown and popup menu.
       '';
     };
 
@@ -210,7 +218,7 @@ in {
     };
 
     refreshRate = mkOption {
-      type = types.addCheck types.int (x: x >= 0);
+      type = types.ints.unsigned;
       default = 0;
       example = 60;
       description = ''
@@ -218,54 +226,69 @@ in {
       '';
     };
 
-    settings = let
-      configTypes = with types; oneOf [ bool int float str ];
-      # types.loaOf converts lists to sets
-      loaOf = t: with types; either (listOf t) (attrsOf t);
+    settings = with types;
+    let
+      scalar = oneOf [ bool int float str ]
+        // { description = "scalar types"; };
+
+      libConfig = oneOf [ scalar (listOf libConfig) (attrsOf libConfig) ]
+        // { description = "libconfig type"; };
+
+      topLevel = attrsOf libConfig
+        // { description = ''
+               libconfig configuration. The format consists of an attributes
+               set (called a group) of settings. Each setting can be a scalar type
+               (boolean, integer, floating point number or string), a list of
+               scalars or a group itself
+             '';
+           };
+
     in mkOption {
-      type = loaOf (types.either configTypes (loaOf (types.either configTypes (loaOf configTypes))));
-      default = {};
+      type = topLevel;
+      default = { };
+      example = literalExample ''
+        blur =
+          { method = "gaussian";
+            size = 10;
+            deviation = 5.0;
+          };
+      '';
       description = ''
-        Additional Picom configuration.
+        Picom settings. Use this option to configure Picom settings not exposed
+        in a NixOS option or to bypass one.  For the available options see the
+        CONFIGURATION FILES section at <literal>picom(1)</literal>.
       '';
     };
   };
 
   config = mkIf cfg.enable {
-    services.picom.settings = let
-      # Hard conversion to float, literally lib.toInt but toFloat
-      toFloat = str: let
-        may_be_float = builtins.fromJSON str;
-      in if builtins.isFloat may_be_float
-        then may_be_float
-        else throw "Could not convert ${str} to float.";
-    in {
+    services.picom.settings = mkDefaultAttrs {
       # fading
-      fading           = mkDefault cfg.fade;
-      fade-delta       = mkDefault cfg.fadeDelta;
-      fade-in-step     = mkDefault (toFloat (elemAt cfg.fadeSteps 0));
-      fade-out-step    = mkDefault (toFloat (elemAt cfg.fadeSteps 1));
-      fade-exclude     = mkDefault cfg.fadeExclude;
+      fading           = cfg.fade;
+      fade-delta       = cfg.fadeDelta;
+      fade-in-step     = elemAt cfg.fadeSteps 0;
+      fade-out-step    = elemAt cfg.fadeSteps 1;
+      fade-exclude     = cfg.fadeExclude;
 
       # shadows
-      shadow           = mkDefault cfg.shadow;
-      shadow-offset-x  = mkDefault (elemAt cfg.shadowOffsets 0);
-      shadow-offset-y  = mkDefault (elemAt cfg.shadowOffsets 1);
-      shadow-opacity   = mkDefault (toFloat cfg.shadowOpacity);
-      shadow-exclude   = mkDefault cfg.shadowExclude;
+      shadow           = cfg.shadow;
+      shadow-offset-x  = elemAt cfg.shadowOffsets 0;
+      shadow-offset-y  = elemAt cfg.shadowOffsets 1;
+      shadow-opacity   = cfg.shadowOpacity;
+      shadow-exclude   = cfg.shadowExclude;
 
       # opacity
-      active-opacity   = mkDefault (toFloat cfg.activeOpacity);
-      inactive-opacity = mkDefault (toFloat cfg.inactiveOpacity);
+      active-opacity   = cfg.activeOpacity;
+      inactive-opacity = cfg.inactiveOpacity;
 
-      wintypes         = mkDefault cfg.wintypes;
+      wintypes         = cfg.wintypes;
 
-      opacity-rule     = mkDefault cfg.opacityRules;
+      opacity-rule     = cfg.opacityRules;
 
       # other options
-      backend          = mkDefault cfg.backend;
-      vsync            = mkDefault cfg.vSync;
-      refresh-rate     = mkDefault cfg.refreshRate;
+      backend          = cfg.backend;
+      vsync            = cfg.vSync;
+      refresh-rate     = cfg.refreshRate;
     };
 
     systemd.user.services.picom = {
diff --git a/nixos/modules/system/boot/initrd-ssh.nix b/nixos/modules/system/boot/initrd-ssh.nix
index 60760487a1d2..f7ef26103709 100644
--- a/nixos/modules/system/boot/initrd-ssh.nix
+++ b/nixos/modules/system/boot/initrd-ssh.nix
@@ -83,6 +83,12 @@ in
         Authorized keys for the root user on initrd.
       '';
     };
+
+    extraConfig = mkOption {
+      type = types.lines;
+      default = "";
+      description = "Verbatim contents of <filename>sshd_config</filename>.";
+    };
   };
 
   imports =
@@ -126,6 +132,8 @@ in
       '' else ''
         UseDNS no
       ''}
+
+      ${cfg.extraConfig}
     '';
   in mkIf (config.boot.initrd.network.enable && cfg.enable) {
     assertions = [
diff --git a/nixos/modules/system/boot/networkd.nix b/nixos/modules/system/boot/networkd.nix
index a7580fb19978..9b34b12e73a5 100644
--- a/nixos/modules/system/boot/networkd.nix
+++ b/nixos/modules/system/boot/networkd.nix
@@ -205,7 +205,7 @@ let
       "IPv6HopLimit" "IPv4ProxyARP" "IPv6ProxyNDP" "IPv6ProxyNDPAddress"
       "IPv6PrefixDelegation" "IPv6MTUBytes" "Bridge" "Bond" "VRF" "VLAN"
       "IPVLAN" "MACVLAN" "VXLAN" "Tunnel" "ActiveSlave" "PrimarySlave"
-      "ConfigureWithoutCarrier" "Xfrm"
+      "ConfigureWithoutCarrier" "Xfrm" "KeepConfiguration"
     ])
     # Note: For DHCP the values both, none, v4, v6 are deprecated
     (assertValueOneOf "DHCP" ["yes" "no" "ipv4" "ipv6" "both" "none" "v4" "v6"])
@@ -228,6 +228,7 @@ let
     (assertValueOneOf "ActiveSlave" boolValues)
     (assertValueOneOf "PrimarySlave" boolValues)
     (assertValueOneOf "ConfigureWithoutCarrier" boolValues)
+    (assertValueOneOf "KeepConfiguration" (boolValues ++ ["static" "dhcp-on-stop" "dhcp"]))
   ];
 
   checkAddress = checkUnitConfig "Address" [
@@ -274,15 +275,16 @@ let
     ])
   ];
 
-  checkDhcp = checkUnitConfig "DHCP" [
+  checkDhcpV4 = checkUnitConfig "DHCPv4" [
     (assertOnlyFields [
-      "UseDNS" "UseNTP" "UseMTU" "Anonymize" "SendHostname" "UseHostname"
-      "Hostname" "UseDomains" "UseRoutes" "UseTimezone" "CriticalConnection"
-      "ClientIdentifier" "VendorClassIdentifier" "UserClass" "DUIDType"
-      "DUIDRawData" "IAID" "RequestBroadcast" "RouteMetric" "RouteTable"
-      "ListenPort" "RapidCommit"
+      "UseDNS" "RoutesToDNS" "UseNTP" "UseMTU" "Anonymize" "SendHostname" "UseHostname"
+      "Hostname" "UseDomains" "UseRoutes" "UseTimezone"
+      "ClientIdentifier" "VendorClassIdentifier" "UserClass" "MaxAttempts"
+      "DUIDType" "DUIDRawData" "IAID" "RequestBroadcast" "RouteMetric" "RouteTable"
+      "ListenPort" "SendRelease"
     ])
     (assertValueOneOf "UseDNS" boolValues)
+    (assertValueOneOf "RoutesToDNS" boolValues)
     (assertValueOneOf "UseNTP" boolValues)
     (assertValueOneOf "UseMTU" boolValues)
     (assertValueOneOf "Anonymize" boolValues)
@@ -291,13 +293,50 @@ let
     (assertValueOneOf "UseDomains" ["yes" "no" "route"])
     (assertValueOneOf "UseRoutes" boolValues)
     (assertValueOneOf "UseTimezone" boolValues)
-    (assertValueOneOf "CriticalConnection" boolValues)
+    (assertMinimum "MaxAttempts" 0)
     (assertValueOneOf "RequestBroadcast" boolValues)
     (assertInt "RouteTable")
     (assertMinimum "RouteTable" 0)
+    (assertValueOneOf "SendRelease" boolValues)
+  ];
+
+  checkDhcpV6 = checkUnitConfig "DHCPv6" [
+    (assertOnlyFields [
+      "UseDns" "UseNTP" "RapidCommit" "ForceDHCPv6PDOtherInformation"
+      "PrefixDelegationHint"
+    ])
+    (assertValueOneOf "UseDNS" boolValues)
+    (assertValueOneOf "UseNTP" boolValues)
     (assertValueOneOf "RapidCommit" boolValues)
+    (assertValueOneOf "ForceDHCPv6PDOtherInformation" boolValues)
+  ];
+
+  checkIpv6PrefixDelegation = checkUnitConfig "IPv6PrefixDelegation" [
+    (assertOnlyFields [
+      "Managed"  "OtherInformation"  "RouterLifetimeSec"
+      "RouterPreference"  "EmitDNS"  "DNS"  "EmitDomains"  "Domains"
+      "DNSLifetimeSec"
+    ])
+    (assertValueOneOf "Managed" boolValues)
+    (assertValueOneOf "OtherInformation" boolValues)
+    (assertValueOneOf "RouterPreference" ["high" "medium" "low" "normal" "default"])
+    (assertValueOneOf "EmitDNS" boolValues)
+    (assertValueOneOf "EmitDomains" boolValues)
+    (assertMinimum "DNSLifetimeSec" 0)
+  ];
+
+  checkIpv6Prefix = checkUnitConfig "IPv6Prefix" [
+    (assertOnlyFields [
+      "AddressAutoconfiguration"  "OnLink"  "Prefix"
+      "PreferredLifetimeSec" "ValidLifetimeSec"
+    ])
+    (assertValueOneOf "AddressAutoconfiguration" boolValues)
+    (assertValueOneOf "OnLink" boolValues)
+    (assertMinimum "PreferredLifetimeSec" 0)
+    (assertMinimum "ValidLifetimeSec" 0)
   ];
 
+
   checkDhcpServer = checkUnitConfig "DHCPServer" [
     (assertOnlyFields [
       "PoolOffset" "PoolSize" "DefaultLeaseTimeSec" "MaxLeaseTimeSec"
@@ -621,6 +660,22 @@ let
     };
   };
 
+  ipv6PrefixOptions = {
+    options = {
+      ipv6PrefixConfig = mkOption {
+        default = {};
+        example = { Prefix = "fd00::/64"; };
+        type = types.addCheck (types.attrsOf unitOption) checkIpv6Prefix;
+        description = ''
+          Each attribute in this set specifies an option in the
+          <literal>[IPv6Prefix]</literal> section of the unit.  See
+          <citerefentry><refentrytitle>systemd.network</refentrytitle>
+          <manvolnum>5</manvolnum></citerefentry> for details.
+        '';
+      };
+    };
+  };
+
 
   networkOptions = commonNetworkOptions // {
 
@@ -636,13 +691,55 @@ let
       '';
     };
 
+    # systemd.network.networks.*.dhcpConfig has been deprecated in favor of ….dhcpV4Config
+    # Produce a nice warning message so users know it is gone.
     dhcpConfig = mkOption {
+      visible = false;
+      apply = _: throw "The option `systemd.network.networks.*.dhcpConfig` can no longer be used since it's been removed. Please use `systemd.network.networks.*.dhcpV4Config` instead.";
+    };
+
+    dhcpV4Config = mkOption {
+      default = {};
+      example = { UseDNS = true; UseRoutes = true; };
+      type = types.addCheck (types.attrsOf unitOption) checkDhcpV4;
+      description = ''
+        Each attribute in this set specifies an option in the
+        <literal>[DHCPv4]</literal> section of the unit.  See
+        <citerefentry><refentrytitle>systemd.network</refentrytitle>
+        <manvolnum>5</manvolnum></citerefentry> for details.
+      '';
+    };
+
+    dhcpV6Config = mkOption {
       default = {};
       example = { UseDNS = true; UseRoutes = true; };
-      type = types.addCheck (types.attrsOf unitOption) checkDhcp;
+      type = types.addCheck (types.attrsOf unitOption) checkDhcpV6;
+      description = ''
+        Each attribute in this set specifies an option in the
+        <literal>[DHCPv6]</literal> section of the unit.  See
+        <citerefentry><refentrytitle>systemd.network</refentrytitle>
+        <manvolnum>5</manvolnum></citerefentry> for details.
+      '';
+    };
+
+    ipv6PrefixDelegationConfig = mkOption {
+      default = {};
+      example = { EmitDNS = true; Managed = true; OtherInformation = true; };
+      type = types.addCheck (types.attrsOf unitOption) checkIpv6PrefixDelegation;
       description = ''
         Each attribute in this set specifies an option in the
-        <literal>[DHCP]</literal> section of the unit.  See
+        <literal>[IPv6PrefixDelegation]</literal> section of the unit.  See
+        <citerefentry><refentrytitle>systemd.network</refentrytitle>
+        <manvolnum>5</manvolnum></citerefentry> for details.
+      '';
+    };
+
+    ipv6Prefixes = mkOption {
+      default = [];
+      example = { AddressAutoconfiguration = true; OnLink = true; };
+      type = with types; listOf (submodule ipv6PrefixOptions);
+      description = ''
+        A list of ipv6Prefix sections to be added to the unit.  See
         <citerefentry><refentrytitle>systemd.network</refentrytitle>
         <manvolnum>5</manvolnum></citerefentry> for details.
       '';
@@ -973,11 +1070,26 @@ let
           ${concatStringsSep "\n" (map (s: "Tunnel=${s}") def.tunnel)}
           ${concatStringsSep "\n" (map (s: "Xfrm=${s}") def.xfrm)}
 
-          ${optionalString (def.dhcpConfig != { }) ''
-            [DHCP]
-            ${attrsToSection def.dhcpConfig}
+          ${optionalString (def.dhcpV4Config != { }) ''
+            [DHCPv4]
+            ${attrsToSection def.dhcpV4Config}
 
           ''}
+          ${optionalString (def.dhcpV6Config != {}) ''
+            [DHCPv6]
+            ${attrsToSection def.dhcpV6Config}
+
+          ''}
+          ${optionalString (def.ipv6PrefixDelegationConfig != {}) ''
+            [IPv6PrefixDelegation]
+            ${attrsToSection def.ipv6PrefixDelegationConfig}
+
+          ''}
+          ${flip concatMapStrings def.ipv6Prefixes (x: ''
+            [IPv6Prefix]
+            ${attrsToSection x.ipv6PrefixConfig}
+
+          '')}
           ${optionalString (def.dhcpServerConfig != { }) ''
             [DHCPServer]
             ${attrsToSection def.dhcpServerConfig}
@@ -1054,6 +1166,7 @@ in
   };
 
   config = mkMerge [
+
     # .link units are honored by udev, no matter if systemd-networkd is enabled or not.
     {
       systemd.network.units = mapAttrs' (n: v: nameValuePair "${n}.link" (linkToUnit n v)) cfg.links;
diff --git a/nixos/modules/system/boot/resolved.nix b/nixos/modules/system/boot/resolved.nix
index da61c64faf8b..b7aaef575ac1 100644
--- a/nixos/modules/system/boot/resolved.nix
+++ b/nixos/modules/system/boot/resolved.nix
@@ -138,6 +138,10 @@ in
 
     users.users.resolved.group = "systemd-resolve";
 
+    # add resolve to nss hosts database if enabled and nscd enabled
+    # system.nssModules is configured in nixos/modules/system/boot/systemd.nix
+    system.nssDatabases.hosts = optional config.services.nscd.enable "resolve [!UNAVAIL=return]";
+
     systemd.additionalUpstreamSystemUnits = [
       "systemd-resolved.service"
     ];
diff --git a/nixos/modules/system/boot/systemd.nix b/nixos/modules/system/boot/systemd.nix
index 2167df60bc9a..ffc5387e8102 100644
--- a/nixos/modules/system/boot/systemd.nix
+++ b/nixos/modules/system/boot/systemd.nix
@@ -405,6 +405,8 @@ let
     "hibernate" "hybrid-sleep" "suspend-then-hibernate" "lock"
   ];
 
+  proxy_env = config.networking.proxy.envVars;
+
 in
 
 {
@@ -827,6 +829,27 @@ in
 
     system.build.units = cfg.units;
 
+    # Systemd provides various NSS modules to look up dynamic users, locally
+    # configured IP adresses and local container hostnames.
+    # On NixOS, these can only be passed to the NSS system via nscd (and its
+    # LD_LIBRARY_PATH), which is why it's usually a very good idea to have nscd
+    # enabled (also see the config.nscd.enable description).
+    # While there is already an assertion in place complaining loudly about
+    # having nssModules configured and nscd disabled, for some reason we still
+    # check for nscd being enabled before adding to nssModules.
+    system.nssModules = optional config.services.nscd.enable systemd.out;
+    system.nssDatabases = mkIf config.services.nscd.enable {
+      hosts = (mkMerge [
+        [ "mymachines" ]
+        (mkOrder 1600 [ "myhostname" ] # 1600 to ensure it's always the last
+      )
+      ]);
+      passwd = (mkMerge [
+        [ "mymachines" ]
+        (mkAfter [ "systemd" ])
+      ]);
+    };
+
     environment.systemPackages = [ systemd ];
 
     environment.etc = let
@@ -1035,6 +1058,7 @@ in
     systemd.targets.remote-fs.unitConfig.X-StopOnReconfiguration = true;
     systemd.targets.network-online.wantedBy = [ "multi-user.target" ];
     systemd.services.systemd-binfmt.wants = [ "proc-sys-fs-binfmt_misc.mount" ];
+    systemd.services.systemd-importd.environment = proxy_env;
 
     # Don't bother with certain units in containers.
     systemd.services.systemd-remount-fs.unitConfig.ConditionVirtualization = "!container";
diff --git a/nixos/modules/virtualisation/cri-o.nix b/nixos/modules/virtualisation/cri-o.nix
index 89ba9c3c6636..2af4214302d6 100644
--- a/nixos/modules/virtualisation/cri-o.nix
+++ b/nixos/modules/virtualisation/cri-o.nix
@@ -4,6 +4,11 @@ with lib;
 
 let
   cfg = config.virtualisation.cri-o;
+
+  # Copy configuration files to avoid having the entire sources in the system closure
+  copyFile = filePath: pkgs.runCommandNoCC (builtins.unsafeDiscardStringContext (builtins.baseNameOf filePath)) {} ''
+    cp ${filePath} $out
+  '';
 in
 {
   imports = [
@@ -45,9 +50,9 @@ in
   config = mkIf cfg.enable {
     environment.systemPackages = with pkgs;
       [ cri-o cri-tools conmon iptables runc utillinux ];
-    environment.etc."crictl.yaml".text = ''
-      runtime-endpoint: unix:///var/run/crio/crio.sock
-    '';
+
+    environment.etc."crictl.yaml".source = copyFile "${pkgs.cri-o.src}/crictl.yaml";
+
     environment.etc."crio/crio.conf".text = ''
       [crio]
       storage_driver = "${cfg.storageDriver}"
@@ -66,23 +71,7 @@ in
       manage_network_ns_lifecycle = true
     '';
 
-    environment.etc."cni/net.d/20-cri-o-bridge.conf".text = ''
-      {
-        "cniVersion": "0.3.1",
-        "name": "crio-bridge",
-        "type": "bridge",
-        "bridge": "cni0",
-        "isGateway": true,
-        "ipMasq": true,
-        "ipam": {
-          "type": "host-local",
-          "subnet": "10.88.0.0/16",
-          "routes": [
-              { "dst": "0.0.0.0/0" }
-          ]
-        }
-      }
-    '';
+    environment.etc."cni/net.d/10-crio-bridge.conf".source = copyFile "${pkgs.cri-o.src}/contrib/cni/10-crio-bridge.conf";
 
     # Enable common /etc/containers configuration
     virtualisation.containers.enable = true;
diff --git a/nixos/modules/virtualisation/ecs-agent.nix b/nixos/modules/virtualisation/ecs-agent.nix
index fc51b159579e..93fefe56d1a5 100644
--- a/nixos/modules/virtualisation/ecs-agent.nix
+++ b/nixos/modules/virtualisation/ecs-agent.nix
@@ -38,9 +38,8 @@ in {
         if [ ! -z "$ECS_DATADIR" ]; then
           mkdir -p "$ECS_DATADIR"
         fi
-        ${cfg.package.bin}/bin/agent
+        ${cfg.package}/bin/agent
       '';
     };
   };
 }
-
diff --git a/nixos/modules/virtualisation/lxd.nix b/nixos/modules/virtualisation/lxd.nix
index de48d3a780e2..53b89a9f55b1 100644
--- a/nixos/modules/virtualisation/lxd.nix
+++ b/nixos/modules/virtualisation/lxd.nix
@@ -108,7 +108,7 @@ in
       '';
 
       serviceConfig = {
-        ExecStart = "@${cfg.package.bin}/bin/lxd lxd --group lxd";
+        ExecStart = "@${cfg.package}/bin/lxd lxd --group lxd";
         Type = "simple";
         KillMode = "process"; # when stopping, leave the containers alone
         LimitMEMLOCK = "infinity";
diff --git a/nixos/modules/virtualisation/nixos-containers.nix b/nixos/modules/virtualisation/nixos-containers.nix
index 016d68ea3018..b0fa03917c82 100644
--- a/nixos/modules/virtualisation/nixos-containers.nix
+++ b/nixos/modules/virtualisation/nixos-containers.nix
@@ -546,7 +546,7 @@ in
 
                 Note that this option might require to do some adjustments to the container configuration,
                 e.g. you might want to set
-                <varname>systemd.network.networks.$interface.dhcpConfig.ClientIdentifier</varname> to "mac"
+                <varname>systemd.network.networks.$interface.dhcpV4Config.ClientIdentifier</varname> to "mac"
                 if you use <varname>macvlans</varname> option.
                 This way dhcp client identifier will be stable between the container restarts.
 
diff --git a/nixos/modules/virtualisation/docker-containers.nix b/nixos/modules/virtualisation/oci-containers.nix
index 5ab990a3d7cc..a46dd65eb491 100644
--- a/nixos/modules/virtualisation/docker-containers.nix
+++ b/nixos/modules/virtualisation/oci-containers.nix
@@ -1,17 +1,20 @@
-{ config, lib, pkgs, ... }:
+{ config, options, lib, pkgs, ... }:
 
 with lib;
 let
-  cfg = config.docker-containers;
+  cfg = config.virtualisation.oci-containers;
+  proxy_env = config.networking.proxy.envVars;
 
-  dockerContainer =
+  defaultBackend = options.virtualisation.oci-containers.backend.default;
+
+  containerOptions =
     { ... }: {
 
       options = {
 
         image = mkOption {
           type = with types; str;
-          description = "Docker image to run.";
+          description = "OCI image to run.";
           example = "library/hello-world";
         };
 
@@ -58,18 +61,19 @@ let
 
         log-driver = mkOption {
           type = types.str;
-          default = "none";
+          default = "journald";
           description = ''
             Logging driver for the container.  The default of
-            <literal>"none"</literal> means that the container's logs will be
-            handled as part of the systemd unit.  Setting this to
-            <literal>"journald"</literal> will result in duplicate logging, but
-            the container's logs will be visible to the <command>docker
-            logs</command> command.
-
-            For more details and a full list of logging drivers, refer to the
-            <link xlink:href="https://docs.docker.com/engine/reference/run/#logging-drivers---log-driver">
-            Docker engine documentation</link>
+            <literal>"journald"</literal> means that the container's logs will be
+            handled as part of the systemd unit.
+
+            For more details and a full list of logging drivers, refer to respective backends documentation.
+
+            For Docker:
+            <link xlink:href="https://docs.docker.com/engine/reference/run/#logging-drivers---log-driver">Docker engine documentation</link>
+
+            For Podman:
+            Refer to the docker-run(1) man page.
           '';
         };
 
@@ -172,10 +176,10 @@ let
           description = ''
             Define which other containers this one depends on. They will be added to both After and Requires for the unit.
 
-            Use the same name as the attribute under <literal>services.docker-containers</literal>.
+            Use the same name as the attribute under <literal>virtualisation.oci-containers</literal>.
           '';
           example = literalExample ''
-            services.docker-containers = {
+            virtualisation.oci-containers = {
               node1 = {};
               node2 = {
                 dependsOn = [ "node1" ];
@@ -184,10 +188,10 @@ let
           '';
         };
 
-        extraDockerOptions = mkOption {
+        extraOptions = mkOption {
           type = with types; listOf str;
           default = [];
-          description = "Extra options for <command>docker run</command>.";
+          description = "Extra options for <command>${defaultBackend} run</command>.";
           example = literalExample ''
             ["--network=host"]
           '';
@@ -205,24 +209,31 @@ let
     };
 
   mkService = name: container: let
-    mkAfter = map (x: "docker-${x}.service") container.dependsOn;
-  in rec {
+    dependsOn = map (x: "${cfg.backend}-${x}.service") container.dependsOn;
+  in {
     wantedBy = [] ++ optional (container.autoStart) "multi-user.target";
-    after = [ "docker.service" "docker.socket" ] ++ mkAfter;
-    requires = after;
-    path = [ pkgs.docker ];
+    after = lib.optionals (cfg.backend == "docker") [ "docker.service" "docker.socket" ] ++ dependsOn;
+    requires = dependsOn;
+    environment = proxy_env;
+
+    path =
+      if cfg.backend == "docker" then [ pkgs.docker ]
+      else if cfg.backend == "podman" then [ config.virtualisation.podman.package ]
+      else throw "Unhandled backend: ${cfg.backend}";
 
     preStart = ''
-      docker rm -f ${name} || true
+      ${cfg.backend} rm -f ${name} || true
       ${optionalString (container.imageFile != null) ''
-        docker load -i ${container.imageFile}
+        ${cfg.backend} load -i ${container.imageFile}
         ''}
       '';
-    postStop = "docker rm -f ${name} || true";
-        
+    postStop = "${cfg.backend} rm -f ${name} || true";
+
     serviceConfig = {
+      StandardOutput = "null";
+      StandardError = "null";
       ExecStart = concatStringsSep " \\\n  " ([
-        "${pkgs.docker}/bin/docker run"
+        "${config.system.path}/bin/${cfg.backend} run"
         "--rm"
         "--name=${name}"
         "--log-driver=${container.log-driver}"
@@ -233,12 +244,12 @@ let
         ++ optional (container.user != null) "-u ${escapeShellArg container.user}"
         ++ map (v: "-v ${escapeShellArg v}") container.volumes
         ++ optional (container.workdir != null) "-w ${escapeShellArg container.workdir}"
-        ++ map escapeShellArg container.extraDockerOptions
+        ++ map escapeShellArg container.extraOptions
         ++ [container.image]
         ++ map escapeShellArg container.cmd
       );
 
-      ExecStop = ''${pkgs.bash}/bin/sh -c "[ $SERVICE_RESULT = success ] || docker stop ${name}"'';
+      ExecStop = ''${pkgs.bash}/bin/sh -c "[ $SERVICE_RESULT = success ] || ${cfg.backend} stop ${name}"'';
 
       ### There is no generalized way of supporting `reload` for docker
       ### containers. Some containers may respond well to SIGHUP sent to their
@@ -263,19 +274,50 @@ let
   };
 
 in {
+  imports = [
+    (
+      lib.mkChangedOptionModule
+      [ "docker-containers"  ]
+      [ "virtualisation" "oci-containers" ]
+      (oldcfg: {
+        backend = "docker";
+        containers = lib.mapAttrs (n: v: builtins.removeAttrs (v // {
+          extraOptions = v.extraDockerOptions or [];
+        }) [ "extraDockerOptions" ]) oldcfg.docker-containers;
+      })
+    )
+  ];
+
+  options.virtualisation.oci-containers = {
+
+    backend = mkOption {
+      type = types.enum [ "podman" "docker" ];
+      default =
+        # TODO: Once https://github.com/NixOS/nixpkgs/issues/77925 is resolved default to podman
+        # if versionAtLeast config.system.stateVersion "20.09" then "podman"
+        # else "docker";
+        "docker";
+      description = "The underlying Docker implementation to use.";
+    };
 
-  options.docker-containers = mkOption {
-    default = {};
-    type = types.attrsOf (types.submodule dockerContainer);
-    description = "Docker containers to run as systemd services.";
-  };
-
-  config = mkIf (cfg != {}) {
-
-    systemd.services = mapAttrs' (n: v: nameValuePair "docker-${n}" (mkService n v)) cfg;
-
-    virtualisation.docker.enable = true;
+    containers = mkOption {
+      default = {};
+      type = types.attrsOf (types.submodule containerOptions);
+      description = "OCI (Docker) containers to run as systemd services.";
+    };
 
   };
 
+  config = lib.mkIf (cfg.containers != {}) (lib.mkMerge [
+    {
+      systemd.services = mapAttrs' (n: v: nameValuePair "${cfg.backend}-${n}" (mkService n v)) cfg.containers;
+    }
+    (lib.mkIf (cfg.backend == "podman") {
+      virtualisation.podman.enable = true;
+    })
+    (lib.mkIf (cfg.backend == "docker") {
+      virtualisation.docker.enable = true;
+    })
+  ]);
+
 }
diff --git a/nixos/modules/virtualisation/podman.nix b/nixos/modules/virtualisation/podman.nix
index 41d50dc73084..652850bf5006 100644
--- a/nixos/modules/virtualisation/podman.nix
+++ b/nixos/modules/virtualisation/podman.nix
@@ -4,18 +4,18 @@ let
 
   inherit (lib) mkOption types;
 
+  podmanPackage = (pkgs.podman.override { inherit (cfg) extraPackages; });
+
   # Provides a fake "docker" binary mapping to podman
-  dockerCompat = pkgs.runCommandNoCC "${pkgs.podman.pname}-docker-compat-${pkgs.podman.version}" {
-    outputs = [ "out" "bin" "man" ];
-    inherit (pkgs.podman) meta;
+  dockerCompat = pkgs.runCommandNoCC "${podmanPackage.pname}-docker-compat-${podmanPackage.version}" {
+    outputs = [ "out" "man" ];
+    inherit (podmanPackage) meta;
   } ''
-    mkdir $out
-
-    mkdir -p $bin/bin
-    ln -s ${pkgs.podman.bin}/bin/podman $bin/bin/docker
+    mkdir -p $out/bin
+    ln -s ${podmanPackage}/bin/podman $out/bin/docker
 
     mkdir -p $man/share/man/man1
-    for f in ${pkgs.podman.man}/share/man/man1/*; do
+    for f in ${podmanPackage.man}/share/man/man1/*; do
       basename=$(basename $f | sed s/podman/docker/g)
       ln -s $f $man/share/man/man1/$basename
     done
@@ -54,6 +54,19 @@ in
       '';
     };
 
+    extraPackages = mkOption {
+      type = with types; listOf package;
+      default = [ ];
+      example = lib.literalExample ''
+        [
+          pkgs.gvisor
+        ]
+      '';
+      description = ''
+        Extra packages to be installed in the Podman wrapper.
+      '';
+    };
+
     libpod = mkOption {
       default = {};
       description = "Libpod configuration";
@@ -73,33 +86,38 @@ in
       };
     };
 
+    package = lib.mkOption {
+      type = types.package;
+      default = podmanPackage;
+      internal = true;
+      description = ''
+        The final Podman package (including extra packages).
+      '';
+    };
+
+
   };
 
   config = lib.mkIf cfg.enable {
 
-    environment.systemPackages = [
-      pkgs.podman # Docker compat
-      pkgs.runc # Default container runtime
-      pkgs.crun # Default container runtime (cgroups v2)
-      pkgs.conmon # Container runtime monitor
-      pkgs.slirp4netns # User-mode networking for unprivileged namespaces
-      pkgs.fuse-overlayfs # CoW for images, much faster than default vfs
-      pkgs.utillinux # nsenter
-      pkgs.iptables
-    ]
-    ++ lib.optional cfg.dockerCompat dockerCompat;
+    environment.systemPackages = [ cfg.package ]
+      ++ lib.optional cfg.dockerCompat dockerCompat;
 
     environment.etc."containers/libpod.conf".text = ''
       cni_plugin_dir = ["${pkgs.cni-plugins}/bin/"]
-      cni_config_dir = "/etc/cni/net.d/"
 
     '' + cfg.libpod.extraConfig;
 
-    environment.etc."cni/net.d/87-podman-bridge.conflist".source = copyFile "${pkgs.podman.src}/cni/87-podman-bridge.conflist";
+    environment.etc."cni/net.d/87-podman-bridge.conflist".source = copyFile "${pkgs.podman-unwrapped.src}/cni/87-podman-bridge.conflist";
 
     # Enable common /etc/containers configuration
     virtualisation.containers.enable = true;
 
+    assertions = [{
+      assertion = cfg.dockerCompat -> !config.virtualisation.docker.enable;
+      message = "Option dockerCompat conflicts with docker";
+    }];
+
   };
 
 }
diff --git a/nixos/modules/virtualisation/qemu-vm.nix b/nixos/modules/virtualisation/qemu-vm.nix
index 31d332e9f07a..ac86330c098b 100644
--- a/nixos/modules/virtualisation/qemu-vm.nix
+++ b/nixos/modules/virtualisation/qemu-vm.nix
@@ -189,9 +189,18 @@ let
           mkdir /boot/grub
           echo '(hd0) /dev/vda' > /boot/grub/device.map
 
-          # Install GRUB and generate the GRUB boot menu.
-          touch /etc/NIXOS
+          # This is needed for systemd-boot to find ESP, and udev is not available here to create this
+          mkdir -p /dev/block
+          ln -s /dev/vda2 /dev/block/254:2
+
+          # Set up system profile (normally done by nixos-rebuild / nix-env --set)
           mkdir -p /nix/var/nix/profiles
+          ln -s ${config.system.build.toplevel} /nix/var/nix/profiles/system-1-link
+          ln -s /nix/var/nix/profiles/system-1-link /nix/var/nix/profiles/system
+
+          # Install bootloader
+          touch /etc/NIXOS
+          export NIXOS_INSTALL_BOOTLOADER=1
           ${config.system.build.toplevel}/bin/switch-to-configuration boot
 
           umount /boot
@@ -499,7 +508,7 @@ in
     # FIXME: Consolidate this one day.
     virtualisation.qemu.options = mkMerge [
       (mkIf (pkgs.stdenv.isi686 || pkgs.stdenv.isx86_64) [
-        "-vga std" "-usb" "-device usb-tablet,bus=usb-bus.0"
+        "-usb" "-device usb-tablet,bus=usb-bus.0"
       ])
       (mkIf (pkgs.stdenv.isAarch32 || pkgs.stdenv.isAarch64) [
         "-device virtio-gpu-pci" "-device usb-ehci,id=usb0" "-device usb-kbd" "-device usb-tablet"
diff --git a/nixos/modules/virtualisation/xen-dom0.nix b/nixos/modules/virtualisation/xen-dom0.nix
index 7f0af9901b9b..7b2a66c43489 100644
--- a/nixos/modules/virtualisation/xen-dom0.nix
+++ b/nixos/modules/virtualisation/xen-dom0.nix
@@ -103,6 +103,7 @@ in
         };
 
         forwardDns = mkOption {
+          type = types.bool;
           default = false;
           description = ''
             If set to <literal>true</literal>, the DNS queries from the
@@ -135,14 +136,8 @@ in
           };
       };
 
-    virtualisation.xen.trace =
-      mkOption {
-        default = false;
-        description =
-          ''
-            Enable Xen tracing.
-          '';
-      };
+    virtualisation.xen.trace = mkEnableOption "Xen tracing";
+
   };