about summary refs log tree commit diff
path: root/nixos
diff options
context:
space:
mode:
Diffstat (limited to 'nixos')
-rw-r--r--nixos/doc/manual/configuration/config-file.xml7
-rw-r--r--nixos/doc/manual/configuration/networking.xml1
-rw-r--r--nixos/doc/manual/configuration/profiles/clone-config.xml2
-rw-r--r--nixos/doc/manual/configuration/renaming-interfaces.xml67
-rw-r--r--nixos/doc/manual/configuration/x-windows.xml16
-rw-r--r--nixos/doc/manual/development/writing-modules.xml5
-rw-r--r--nixos/doc/manual/development/writing-nixos-tests.xml11
-rw-r--r--nixos/doc/manual/installation/installing-virtualbox-guest.xml7
-rw-r--r--nixos/doc/manual/release-notes/rl-2105.xml141
-rw-r--r--nixos/lib/make-squashfs.nix2
-rw-r--r--nixos/lib/qemu-flags.nix4
-rw-r--r--nixos/modules/config/console.nix13
-rw-r--r--nixos/modules/config/swap.nix2
-rw-r--r--nixos/modules/config/users-groups.nix2
-rw-r--r--nixos/modules/hardware/ksm.nix12
-rw-r--r--nixos/modules/installer/cd-dvd/sd-image-aarch64-new-kernel.nix17
-rw-r--r--nixos/modules/installer/cd-dvd/sd-image-aarch64.nix84
-rw-r--r--nixos/modules/installer/cd-dvd/sd-image-armv7l-multiplatform.nix61
-rw-r--r--nixos/modules/installer/cd-dvd/sd-image-raspberrypi.nix50
-rw-r--r--nixos/modules/installer/cd-dvd/sd-image-raspberrypi4.nix18
-rw-r--r--nixos/modules/installer/cd-dvd/sd-image.nix247
-rw-r--r--nixos/modules/installer/cd-dvd/system-tarball-fuloong2f.nix2
-rw-r--r--nixos/modules/installer/cd-dvd/system-tarball-pc.nix4
-rw-r--r--nixos/modules/installer/sd-card/sd-image-aarch64-installer.nix10
-rw-r--r--nixos/modules/installer/sd-card/sd-image-aarch64-new-kernel-installer.nix10
-rw-r--r--nixos/modules/installer/sd-card/sd-image-aarch64-new-kernel.nix7
-rw-r--r--nixos/modules/installer/sd-card/sd-image-aarch64.nix75
-rw-r--r--nixos/modules/installer/sd-card/sd-image-armv7l-multiplatform-installer.nix10
-rw-r--r--nixos/modules/installer/sd-card/sd-image-armv7l-multiplatform.nix52
-rw-r--r--nixos/modules/installer/sd-card/sd-image-raspberrypi-installer.nix10
-rw-r--r--nixos/modules/installer/sd-card/sd-image-raspberrypi.nix41
-rw-r--r--nixos/modules/installer/sd-card/sd-image-raspberrypi4-installer.nix10
-rw-r--r--nixos/modules/installer/sd-card/sd-image-raspberrypi4.nix8
-rw-r--r--nixos/modules/installer/sd-card/sd-image.nix245
-rw-r--r--nixos/modules/installer/tools/nixos-generate-config.pl18
-rw-r--r--nixos/modules/installer/tools/tools.nix7
-rw-r--r--nixos/modules/misc/crashdump.nix2
-rw-r--r--nixos/modules/module-list.nix13
-rw-r--r--nixos/modules/profiles/hardened.nix2
-rw-r--r--nixos/modules/programs/fish_completion-generator.patch19
-rw-r--r--nixos/modules/programs/steam.nix42
-rw-r--r--nixos/modules/rename.nix5
-rw-r--r--nixos/modules/security/hidepid.nix31
-rw-r--r--nixos/modules/security/hidepid.xml28
-rw-r--r--nixos/modules/security/rngd.nix64
-rw-r--r--nixos/modules/services/audio/spotifyd.nix1
-rw-r--r--nixos/modules/services/backup/borgmatic.nix57
-rw-r--r--nixos/modules/services/backup/zrepl.nix54
-rw-r--r--nixos/modules/services/blockchain/ethereum/geth.nix178
-rw-r--r--nixos/modules/services/cluster/kubernetes/addons/dns.nix7
-rw-r--r--nixos/modules/services/cluster/kubernetes/apiserver.nix42
-rw-r--r--nixos/modules/services/cluster/kubernetes/default.nix33
-rw-r--r--nixos/modules/services/cluster/kubernetes/flannel.nix40
-rw-r--r--nixos/modules/services/cluster/kubernetes/kubelet.nix39
-rw-r--r--nixos/modules/services/cluster/kubernetes/pki.nix1
-rw-r--r--nixos/modules/services/continuous-integration/hydra/default.nix27
-rw-r--r--nixos/modules/services/databases/mysql.nix15
-rw-r--r--nixos/modules/services/desktops/pipewire.nix183
-rw-r--r--nixos/modules/services/desktops/pipewire/alsa-monitor.conf.json34
-rw-r--r--nixos/modules/services/desktops/pipewire/bluez-monitor.conf.json30
-rw-r--r--nixos/modules/services/desktops/pipewire/client-rt.conf.json26
-rw-r--r--nixos/modules/services/desktops/pipewire/client.conf.json19
-rw-r--r--nixos/modules/services/desktops/pipewire/jack.conf.json21
-rw-r--r--nixos/modules/services/desktops/pipewire/media-session.conf.json53
-rw-r--r--nixos/modules/services/desktops/pipewire/pipewire-media-session.nix129
-rw-r--r--nixos/modules/services/desktops/pipewire/pipewire-pulse.conf.json28
-rw-r--r--nixos/modules/services/desktops/pipewire/pipewire.conf.json55
-rw-r--r--nixos/modules/services/desktops/pipewire/pipewire.nix232
-rw-r--r--nixos/modules/services/desktops/pipewire/v4l2-monitor.conf.json30
-rw-r--r--nixos/modules/services/games/minetest-server.nix2
-rw-r--r--nixos/modules/services/hardware/acpid.nix31
-rw-r--r--nixos/modules/services/hardware/bluetooth.nix152
-rw-r--r--nixos/modules/services/hardware/spacenavd.nix26
-rw-r--r--nixos/modules/services/hardware/trezord.nix2
-rw-r--r--nixos/modules/services/hardware/udev.nix23
-rw-r--r--nixos/modules/services/hardware/xow.nix3
-rw-r--r--nixos/modules/services/mail/mlmmj.nix43
-rw-r--r--nixos/modules/services/misc/apache-kafka.nix23
-rw-r--r--nixos/modules/services/misc/defaultUnicornConfig.rb69
-rw-r--r--nixos/modules/services/misc/etesync-dav.nix92
-rw-r--r--nixos/modules/services/misc/gitlab.nix324
-rw-r--r--nixos/modules/services/misc/gollum.nix2
-rw-r--r--nixos/modules/services/misc/home-assistant.nix12
-rw-r--r--nixos/modules/services/misc/lifecycled.nix164
-rw-r--r--nixos/modules/services/misc/nix-gc.nix53
-rw-r--r--nixos/modules/services/misc/plikd.nix82
-rw-r--r--nixos/modules/services/monitoring/alerta.nix4
-rw-r--r--nixos/modules/services/monitoring/grafana.nix143
-rw-r--r--nixos/modules/services/monitoring/prometheus/default.nix2
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters.nix25
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/artifactory.nix59
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/knot.nix50
-rw-r--r--nixos/modules/services/network-filesystems/ceph.nix2
-rw-r--r--nixos/modules/services/networking/dhcpcd.nix3
-rw-r--r--nixos/modules/services/networking/dnscrypt-proxy2.nix1
-rw-r--r--nixos/modules/services/networking/flannel.nix6
-rw-r--r--nixos/modules/services/networking/kresd.nix6
-rw-r--r--nixos/modules/services/networking/privoxy.nix304
-rw-r--r--nixos/modules/services/printing/cupsd.nix2
-rw-r--r--nixos/modules/services/security/clamav.nix67
-rw-r--r--nixos/modules/services/ttys/kmscon.nix7
-rw-r--r--nixos/modules/services/web-apps/dokuwiki.nix4
-rw-r--r--nixos/modules/services/web-apps/mastodon.nix110
-rw-r--r--nixos/modules/services/web-apps/miniflux.nix2
-rw-r--r--nixos/modules/services/web-apps/moinmoin.nix4
-rw-r--r--nixos/modules/services/web-apps/nextcloud.nix70
-rw-r--r--nixos/modules/services/web-apps/nextcloud.xml2
-rw-r--r--nixos/modules/services/web-apps/whitebophir.nix9
-rw-r--r--nixos/modules/services/web-servers/apache-httpd/default.nix6
-rw-r--r--nixos/modules/services/web-servers/nginx/default.nix2
-rw-r--r--nixos/modules/services/x11/desktop-managers/gnome3.nix5
-rw-r--r--nixos/modules/services/x11/desktop-managers/plasma5.nix5
-rw-r--r--nixos/modules/services/x11/desktop-managers/xfce.nix7
-rw-r--r--nixos/modules/services/x11/display-managers/default.nix22
-rw-r--r--nixos/modules/services/x11/display-managers/gdm.nix12
-rw-r--r--nixos/modules/services/x11/xserver.nix7
-rw-r--r--nixos/modules/system/boot/kernel_config.nix22
-rw-r--r--nixos/modules/system/boot/loader/generic-extlinux-compatible/extlinux-conf-builder.sh2
-rw-r--r--nixos/modules/system/boot/loader/grub/install-grub.pl225
-rw-r--r--nixos/modules/system/boot/loader/init-script/init-script-builder.sh1
-rw-r--r--nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py2
-rw-r--r--nixos/modules/system/boot/luksroot.nix56
-rw-r--r--nixos/modules/system/boot/networkd.nix110
-rw-r--r--nixos/modules/system/boot/plymouth.nix89
-rw-r--r--nixos/modules/system/boot/stage-1-init.sh15
-rw-r--r--nixos/modules/system/boot/stage-1.nix13
-rw-r--r--nixos/modules/tasks/network-interfaces-systemd.nix12
-rw-r--r--nixos/modules/virtualisation/amazon-init.nix41
-rw-r--r--nixos/modules/virtualisation/anbox.nix1
-rw-r--r--nixos/modules/virtualisation/containerd.nix60
-rw-r--r--nixos/modules/virtualisation/fetch-instance-ssh-keys.bash36
-rw-r--r--nixos/modules/virtualisation/google-compute-config.nix25
-rw-r--r--nixos/modules/virtualisation/hyperv-guest.nix4
-rw-r--r--nixos/modules/virtualisation/libvirtd.nix2
-rw-r--r--nixos/modules/virtualisation/lxd.nix61
-rw-r--r--nixos/modules/virtualisation/nixos-containers.nix12
-rw-r--r--nixos/modules/virtualisation/podman.nix10
-rw-r--r--nixos/modules/virtualisation/qemu-vm.nix23
-rw-r--r--nixos/release.nix10
-rw-r--r--nixos/tests/all-tests.nix15
-rw-r--r--nixos/tests/bees.nix2
-rw-r--r--nixos/tests/bind.nix1
-rw-r--r--nixos/tests/ceph-single-node-bluestore.nix196
-rw-r--r--nixos/tests/containers-bridge.nix8
-rw-r--r--nixos/tests/containers-custom-pkgs.nix6
-rw-r--r--nixos/tests/containers-ephemeral.nix7
-rw-r--r--nixos/tests/containers-extra_veth.nix8
-rw-r--r--nixos/tests/containers-hosts.nix8
-rw-r--r--nixos/tests/containers-imperative.nix8
-rw-r--r--nixos/tests/containers-ip.nix8
-rw-r--r--nixos/tests/containers-macvlans.nix8
-rw-r--r--nixos/tests/containers-names.nix37
-rw-r--r--nixos/tests/containers-physical_interfaces.nix7
-rw-r--r--nixos/tests/containers-portforward.nix8
-rw-r--r--nixos/tests/containers-reloadable.nix7
-rw-r--r--nixos/tests/containers-restart_networking.nix8
-rw-r--r--nixos/tests/containers-tmpfs.nix8
-rw-r--r--nixos/tests/custom-ca.nix161
-rw-r--r--nixos/tests/fsck.nix2
-rw-r--r--nixos/tests/geth.nix41
-rw-r--r--nixos/tests/gitlab.nix26
-rw-r--r--nixos/tests/glusterfs.nix4
-rw-r--r--nixos/tests/hardened.nix13
-rw-r--r--nixos/tests/home-assistant.nix2
-rw-r--r--nixos/tests/installer.nix15
-rw-r--r--nixos/tests/jq.nix10
-rw-r--r--nixos/tests/kafka.nix6
-rw-r--r--nixos/tests/keepassxc.nix34
-rw-r--r--nixos/tests/ksm.nix22
-rw-r--r--nixos/tests/kubernetes/dns.nix15
-rw-r--r--nixos/tests/kubernetes/rbac.nix6
-rw-r--r--nixos/tests/locate.nix2
-rw-r--r--nixos/tests/misc.nix2
-rw-r--r--nixos/tests/musescore.nix86
-rw-r--r--nixos/tests/networking.nix26
-rw-r--r--nixos/tests/nextcloud/basic.nix24
-rw-r--r--nixos/tests/nfs/kerberos.nix2
-rw-r--r--nixos/tests/nfs/simple.nix2
-rw-r--r--nixos/tests/nixos-generate-config.nix9
-rw-r--r--nixos/tests/openldap.nix19
-rw-r--r--nixos/tests/orangefs.nix2
-rw-r--r--nixos/tests/php/default.nix21
-rw-r--r--nixos/tests/php/fpm.nix14
-rw-r--r--nixos/tests/php/httpd.nix7
-rw-r--r--nixos/tests/php/pcre.nix5
-rw-r--r--nixos/tests/plikd.nix27
-rw-r--r--nixos/tests/privoxy.nix113
-rw-r--r--nixos/tests/prometheus-exporters.nix33
-rw-r--r--nixos/tests/ripgrep.nix13
-rw-r--r--nixos/tests/samba.nix2
-rw-r--r--nixos/tests/sbt-extras.nix16
-rw-r--r--nixos/tests/sbt.nix18
-rw-r--r--nixos/tests/snapper.nix2
-rw-r--r--nixos/tests/systemd-networkd-ipv6-prefix-delegation.nix20
-rw-r--r--nixos/tests/systemd.nix2
-rw-r--r--nixos/tests/traefik.nix4
-rw-r--r--nixos/tests/unbound.nix16
-rw-r--r--nixos/tests/zfs.nix2
198 files changed, 5019 insertions, 1878 deletions
diff --git a/nixos/doc/manual/configuration/config-file.xml b/nixos/doc/manual/configuration/config-file.xml
index 7ccb5b3664ea3..19cfb57920df9 100644
--- a/nixos/doc/manual/configuration/config-file.xml
+++ b/nixos/doc/manual/configuration/config-file.xml
@@ -16,9 +16,10 @@
   The first line (<literal>{ config, pkgs, ... }:</literal>) denotes that this
   is actually a function that takes at least the two arguments
   <varname>config</varname> and <varname>pkgs</varname>. (These are explained
-  later.) The function returns a <emphasis>set</emphasis> of option definitions
-  (<literal>{ <replaceable>...</replaceable> }</literal>). These definitions
-  have the form <literal><replaceable>name</replaceable> =
+  later, in chapter <xref linkend="sec-writing-modules" />) The function returns
+  a <emphasis>set</emphasis> of option definitions (<literal>{
+  <replaceable>...</replaceable> }</literal>). These definitions have the form
+  <literal><replaceable>name</replaceable> =
   <replaceable>value</replaceable></literal>, where
   <replaceable>name</replaceable> is the name of an option and
   <replaceable>value</replaceable> is its value. For example,
diff --git a/nixos/doc/manual/configuration/networking.xml b/nixos/doc/manual/configuration/networking.xml
index 02cf811e0bd3d..8369e9c9c852b 100644
--- a/nixos/doc/manual/configuration/networking.xml
+++ b/nixos/doc/manual/configuration/networking.xml
@@ -15,5 +15,6 @@
  <xi:include href="firewall.xml" />
  <xi:include href="wireless.xml" />
  <xi:include href="ad-hoc-network-config.xml" />
+ <xi:include href="renaming-interfaces.xml" />
 <!-- TODO: OpenVPN, NAT -->
 </chapter>
diff --git a/nixos/doc/manual/configuration/profiles/clone-config.xml b/nixos/doc/manual/configuration/profiles/clone-config.xml
index 04fa1643d0fd0..9c70cf352041a 100644
--- a/nixos/doc/manual/configuration/profiles/clone-config.xml
+++ b/nixos/doc/manual/configuration/profiles/clone-config.xml
@@ -16,6 +16,6 @@
   On images where the installation media also becomes an installation target,
   copying over <literal>configuration.nix</literal> should be disabled by
   setting <literal>installer.cloneConfig</literal> to <literal>false</literal>.
-  For example, this is done in <literal>sd-image-aarch64.nix</literal>.
+  For example, this is done in <literal>sd-image-aarch64-installer.nix</literal>.
  </para>
 </section>
diff --git a/nixos/doc/manual/configuration/renaming-interfaces.xml b/nixos/doc/manual/configuration/renaming-interfaces.xml
new file mode 100644
index 0000000000000..d760bb3a4dace
--- /dev/null
+++ b/nixos/doc/manual/configuration/renaming-interfaces.xml
@@ -0,0 +1,67 @@
+<section 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="sec-rename-ifs">
+ <title>Renaming network interfaces</title>
+
+ <para>
+  NixOS uses the udev
+  <link xlink:href="https://systemd.io/PREDICTABLE_INTERFACE_NAMES/">predictable naming scheme</link>
+  to assign names to network interfaces. This means that by default
+  cards are not given the traditional names like
+  <literal>eth0</literal> or <literal>eth1</literal>, whose order can
+  change unpredictably across reboots. Instead, relying on physical
+  locations and firmware information, the scheme produces names like
+  <literal>ens1</literal>, <literal>enp2s0</literal>, etc.
+ </para>
+
+ <para>
+  These names are predictable but less memorable and not necessarily
+  stable: for example installing new hardware or changing firmware
+  settings can result in a
+  <link xlink:href="https://github.com/systemd/systemd/issues/3715#issue-165347602">name change</link>.
+  If this is undesirable, for example if you have a single ethernet
+  card, you can revert to the traditional scheme by setting
+  <xref linkend="opt-networking.usePredictableInterfaceNames"/> to
+  <literal>false</literal>.
+ </para>
+
+ <section xml:id="sec-custom-ifnames">
+  <title>Assigning custom names</title>
+  <para>
+   In case there are multiple interfaces of the same type, it’s better to
+   assign custom names based on the device hardware address. For
+   example, we assign the name <literal>wan</literal> to the interface
+   with MAC address <literal>52:54:00:12:01:01</literal> using a
+   netword link unit:
+  </para>
+  <programlisting>
+ <link linkend="opt-systemd.network.links">systemd.network.links."10-wan"</link> = {
+   matchConfig.MACAddress = "52:54:00:12:01:01";
+   linkConfig.Name = "wan";
+ };
+  </programlisting>
+  <para>
+   Note that links are directly read by udev, <emphasis>not networkd</emphasis>,
+   and will work even if networkd is disabled.
+  </para>
+  <para>
+   Alternatively, we can use a plain old udev rule:
+  </para>
+  <programlisting>
+ <link linkend="opt-services.udev.initrdRules">services.udev.initrdRules</link> = ''
+  SUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*", \
+  ATTR{address}=="52:54:00:12:01:01", KERNEL=="eth*", NAME="wan"
+ '';
+  </programlisting>
+
+  <warning><para>
+   The rule must be installed in the initrd using
+   <literal>services.udev.initrdRules</literal>, not the usual
+   <literal>services.udev.extraRules</literal> option. This is to avoid race
+   conditions with other programs controlling the interface.
+  </para></warning>
+ </section>
+
+</section>
diff --git a/nixos/doc/manual/configuration/x-windows.xml b/nixos/doc/manual/configuration/x-windows.xml
index dd879702d7dc0..a499f0080d705 100644
--- a/nixos/doc/manual/configuration/x-windows.xml
+++ b/nixos/doc/manual/configuration/x-windows.xml
@@ -150,7 +150,6 @@
 <xref linkend="opt-services.xserver.videoDrivers"/> = [ "nvidiaLegacy390" ];
 <xref linkend="opt-services.xserver.videoDrivers"/> = [ "nvidiaLegacy340" ];
 <xref linkend="opt-services.xserver.videoDrivers"/> = [ "nvidiaLegacy304" ];
-<xref linkend="opt-services.xserver.videoDrivers"/> = [ "nvidiaLegacy173" ];
 </programlisting>
    You may need to reboot after enabling this driver to prevent a clash with
    other kernel modules.
@@ -159,21 +158,16 @@
  <simplesect xml:id="sec-x11--graphics-cards-amd">
   <title>Proprietary AMD drivers</title>
   <para>
-   AMD provides a proprietary driver for its graphics cards that has better 3D
-   performance than the X.org drivers. It is not enabled by default because
-   it’s not free software. You can enable it as follows:
+   AMD provides a proprietary driver for its graphics cards that is not
+   enabled by default because it’s not Free Software, is often broken
+   in nixpkgs and as of this writing doesn't offer more features or
+   performance. If you still want to use it anyway, you need to explicitly set:
 <programlisting>
-<xref linkend="opt-services.xserver.videoDrivers"/> = [ "ati_unfree" ];
+<xref linkend="opt-services.xserver.videoDrivers"/> = [ "amdgpu-pro" ];
 </programlisting>
    You will need to reboot after enabling this driver to prevent a clash with
    other kernel modules.
   </para>
-  <note>
-  <para>
-   For recent AMD GPUs you most likely want to keep either the defaults
-   or <literal>"amdgpu"</literal> (both free).
-  </para>
-  </note>
  </simplesect>
  <simplesect xml:id="sec-x11-touchpads">
   <title>Touchpads</title>
diff --git a/nixos/doc/manual/development/writing-modules.xml b/nixos/doc/manual/development/writing-modules.xml
index d244356dbed19..fad4637f51f09 100644
--- a/nixos/doc/manual/development/writing-modules.xml
+++ b/nixos/doc/manual/development/writing-modules.xml
@@ -74,7 +74,10 @@ linkend="sec-configuration-syntax"/>, we saw the following structure
    <callout arearefs='module-syntax-1'>
     <para>
      This line makes the current Nix expression a function. The variable
-     <varname>pkgs</varname> contains Nixpkgs, while <varname>config</varname>
+     <varname>pkgs</varname> contains Nixpkgs (by default, it takes the
+     <varname>nixpkgs</varname> entry of <envar>NIX_PATH</envar>, see the <link
+     xlink:href="https://nixos.org/manual/nix/stable/#sec-common-env">Nix
+     manual</link> for further details), while <varname>config</varname>
      contains the full system configuration. This line can be omitted if there
      is no reference to <varname>pkgs</varname> and <varname>config</varname>
      inside the module.
diff --git a/nixos/doc/manual/development/writing-nixos-tests.xml b/nixos/doc/manual/development/writing-nixos-tests.xml
index cab4c067e0d35..5f70f74d5d90d 100644
--- a/nixos/doc/manual/development/writing-nixos-tests.xml
+++ b/nixos/doc/manual/development/writing-nixos-tests.xml
@@ -449,5 +449,16 @@ import ./make-test-python.nix {
     '';
 }
 </programlisting>
+  This will produce a Nix warning at evaluation time. To fully disable the
+  linter, wrap the test script in comment directives to disable the Black linter
+  directly (again, don't commit this within the Nixpkgs repository):
+<programlisting>
+  testScript =
+    ''
+      # fmt: off
+      <replaceable>Python code…</replaceable>
+      # fmt: on
+    '';
+</programlisting>
  </para>
 </section>
diff --git a/nixos/doc/manual/installation/installing-virtualbox-guest.xml b/nixos/doc/manual/installation/installing-virtualbox-guest.xml
index 4957b700946ef..019e5098a8e27 100644
--- a/nixos/doc/manual/installation/installing-virtualbox-guest.xml
+++ b/nixos/doc/manual/installation/installing-virtualbox-guest.xml
@@ -83,17 +83,12 @@
   VirtualBox settings (Machine / Settings / Shared Folders, then click on the
   "Add" icon). Add the following to the
   <literal>/etc/nixos/configuration.nix</literal> to auto-mount them. If you do
-  not add <literal>"nofail"</literal>, the system will not boot properly. The
-  same goes for disabling <literal>rngd</literal> which is normally used to get
-  randomness but this does not work in virtual machines.
+  not add <literal>"nofail"</literal>, the system will not boot properly.
  </para>
 
 <programlisting>
 { config, pkgs, ...} :
 {
-  security.rngd.enable = false; // otherwise vm will not boot
-  ...
-
   fileSystems."/virtualboxshare" = {
     fsType = "vboxsf";
     device = "nameofthesharedfolder";
diff --git a/nixos/doc/manual/release-notes/rl-2105.xml b/nixos/doc/manual/release-notes/rl-2105.xml
index 6dd14d6051e40..e052632ecafd9 100644
--- a/nixos/doc/manual/release-notes/rl-2105.xml
+++ b/nixos/doc/manual/release-notes/rl-2105.xml
@@ -39,6 +39,24 @@
      (<link xlink:href="https://github.com/NixOS/nixpkgs/issues/75478">#7547</link>).
     </para>
    </listitem>
+   <listitem>
+    <para>
+     <link xlink:href="https://www.privoxy.org/">Privoxy</link> has been updated
+     to version 3.0.32 (See <link xlink:href="https://lists.privoxy.org/pipermail/privoxy-announce/2021-February/000007.html">announcement</link>).
+     Compared to the previous release, Privoxy has gained support for HTTPS
+     inspection (still experimental), Brotli decompression, several new filters
+     and lots of bug fixes, including security ones. In addition, the package
+     is now built with compression and external filters support, which were
+     previously disabled.
+    </para>
+    <para>
+     Regarding the NixOS module, new options for HTTPS inspection have been added
+     and <option>services.privoxy.extraConfig</option> has been replaced by the new
+     <xref linkend="opt-services.privoxy.settings"/>
+     (See <link xlink:href="https://github.com/NixOS/rfcs/blob/master/rfcs/0042-config-option.md">RFC 0042</link>
+     for the motivation).
+    </para>
+   </listitem>
   </itemizedlist>
  </section>
 
@@ -91,6 +109,27 @@
   </para>
 
   <itemizedlist>
+   <listitem>
+    <para>
+     If you are using <option>services.udev.extraRules</option> to assign
+     custom names to network interfaces, this may stop working due to a change
+     in the initialisation of dhcpcd and systemd networkd. To avoid this, either
+     move them to <option>services.udev.initrdRules</option> or see the new
+     <link linkend="sec-custom-ifnames">Assigning custom names</link> section
+     of the NixOS manual for an example using networkd links.
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     The <option>security.hideProcessInformation</option> module has been removed.
+     It was broken since the switch to cgroups-v2.
+    </para>
+   </listitem>
+    <listitem>
+      <para>
+        The <literal>systemConfig</literal> kernel parameter is no longer added to boot loader entries. It has been unused since September 2010, but if do have a system generation from that era, you will now be unable to boot into them.
+      </para>
+    </listitem>
     <listitem>
       <para>
         <literal>systemd-journal2gelf</literal> no longer parses json and expects the receiving system to handle it. How to achieve this with Graylog is described in this <link xlink:href="https://github.com/parse-nl/SystemdJournal2Gelf/issues/10">GitHub issue</link>.
@@ -494,6 +533,50 @@ self: super:
      <varname>services.flashpolicyd</varname> module.
     </para>
    </listitem>
+   <listitem>
+    <para>
+      The <literal>security.rngd</literal> module has been removed.
+      It was disabled by default in 20.09 as it was functionally redundant
+      with krngd in the linux kernel. It is not necessary for any device that the kernel recognises
+      as an hardware RNG, as it will automatically run the krngd task to periodically collect random
+      data from the device and mix it into the kernel's RNG.
+    </para>
+    <para>
+      The default SMTP port for GitLab has been changed to
+      <literal>25</literal> from its previous default of
+      <literal>465</literal>. If you depended on this default, you
+      should now set the <xref linkend="opt-services.gitlab.smtp.port" />
+      option.
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     The default version of ImageMagick has been updated from 6 to 7.
+     You can use <package>imagemagick6</package>,
+     <package>imagemagick6_light</package>, and
+     <package>imagemagick6Big</package> if you need the older version.
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     <xref linkend="opt-services.xserver.videoDrivers" /> no longer uses the deprecated <literal>cirrus</literal> and <literal>vesa</literal> device dependent X drivers by default. It also enables both <literal>amdgpu</literal> and <literal>nouveau</literal> drivers by default now.
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+      The <package>apacheKafka</package> packages are now built with
+      version-matched JREs. Versions 2.6 and above, the ones that recommend it,
+      use jdk11, while versions below remain on jdk8. The NixOS service has
+      been adjusted to start the service using the same version as the package,
+      adjustable with the new
+      <link linkend="opt-services.apache-kafka.jre">services.apache-kafka.jre</link>
+      option. Furthermore, the default list of
+      <link linkend="opt-services.apache-kafka.jvmOptions">services.apache-kafka.jvmOptions</link>
+      have been removed. You should set your own according to the
+      <link xlink:href="https://kafka.apache.org/documentation/#java">upstream documentation</link>
+      for your Kafka version.
+    </para>
+   </listitem>
   </itemizedlist>
  </section>
 
@@ -528,14 +611,16 @@ self: super:
    </listitem>
    <listitem>
     <para>
-     The default-version of <literal>nextcloud</literal> is <package>nextcloud20</package>.
+     The default-version of <literal>nextcloud</literal> is <package>nextcloud21</package>.
      Please note that it's <emphasis>not</emphasis> possible to upgrade <literal>nextcloud</literal>
      across multiple major versions! This means that it's e.g. not possible to upgrade
-     from <package>nextcloud18</package> to <package>nextcloud20</package> in a single deploy.
+     from <package>nextcloud18</package> to <package>nextcloud20</package> in a single deploy and
+     most <literal>20.09</literal> users will have to upgrade to <package>nextcloud20</package>
+     first.
     </para>
     <para>
      The package can be manually upgraded by setting <xref linkend="opt-services.nextcloud.package" />
-     to <package>nextcloud20</package>.
+     to <package>nextcloud21</package>.
     </para>
    </listitem>
    <listitem>
@@ -700,6 +785,56 @@ self: super:
      terminology has been deprecated and should be replaced with Far/Near in the configuration file.
     </para>
    </listitem>
+   <listitem>
+    <para>
+     The nix-gc service now accepts randomizedDelaySec (default: 0) and persistent (default: true) parameters.
+     By default nix-gc will now run immediately if it would have been triggered at least
+     once during the time when the timer was inactive.
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     The <literal>rustPlatform.buildRustPackage</literal> function is split into several hooks:
+     <package>cargoSetupHook</package> to set up vendoring for Cargo-based projects,
+     <package>cargoBuildHook</package> to build a project using Cargo,
+     <package>cargoInstallHook</package> to install a project using Cargo, and
+     <package>cargoCheckHook</package> to run tests in Cargo-based projects. With this change,
+     mixed-language projects can use the relevant hooks within builders other than
+     <literal>buildRustPackage</literal>. However, these changes also required several API changes to
+     <literal>buildRustPackage</literal> itself:
+
+     <itemizedlist>
+      <listitem>
+       <para>
+        The <literal>target</literal> argument was removed. Instead, <literal>buildRustPackage</literal>
+        will always use the same target as the C/C++ compiler that is used.
+       </para>
+      </listitem>
+      <listitem>
+       <para>
+        The <literal>cargoParallelTestThreads</literal> argument was removed. Parallel tests are
+        now disabled through <literal>dontUseCargoParallelTests</literal>.
+       </para>
+      </listitem>
+     </itemizedlist>
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     The <literal>rustPlatform.maturinBuildHook</literal> hook was added. This hook can be used
+     with <literal>buildPythonPackage</literal> to build Python packages that are written in Rust
+     and use Maturin as their build tool.
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     Kubernetes has <link xlink:href="https://kubernetes.io/blog/2020/12/02/dont-panic-kubernetes-and-docker/">deprecated docker</link> as container runtime.
+     As a consequence, the Kubernetes module now has support for configuration of custom remote container runtimes and enables containerd by default.
+     Note that containerd is more strict regarding container image OCI-compliance.
+     As an example, images with CMD or ENTRYPOINT defined as strings (not lists) will fail on containerd, while working fine on docker.
+     Please test your setup and container images with containerd prior to upgrading.
+    </para>
+   </listitem>
   </itemizedlist>
  </section>
 </section>
diff --git a/nixos/lib/make-squashfs.nix b/nixos/lib/make-squashfs.nix
index ee76c9c5bf240..8690c42e7ac93 100644
--- a/nixos/lib/make-squashfs.nix
+++ b/nixos/lib/make-squashfs.nix
@@ -23,6 +23,6 @@ stdenv.mkDerivation {
 
       # Generate the squashfs image.
       mksquashfs nix-path-registration $(cat $closureInfo/store-paths) $out \
-        -keep-as-directory -all-root -b 1048576 -comp ${comp}
+        -no-hardlinks -keep-as-directory -all-root -b 1048576 -comp ${comp}
     '';
 }
diff --git a/nixos/lib/qemu-flags.nix b/nixos/lib/qemu-flags.nix
index 0f06624589354..f786745ba3247 100644
--- a/nixos/lib/qemu-flags.nix
+++ b/nixos/lib/qemu-flags.nix
@@ -18,13 +18,15 @@ rec {
     ];
 
   qemuSerialDevice = if pkgs.stdenv.isi686 || pkgs.stdenv.isx86_64 then "ttyS0"
-        else if pkgs.stdenv.isAarch32 || pkgs.stdenv.isAarch64 then "ttyAMA0"
+        else if (with pkgs.stdenv.hostPlatform; isAarch32 || isAarch64 || isPower) then "ttyAMA0"
         else throw "Unknown QEMU serial device for system '${pkgs.stdenv.hostPlatform.system}'";
 
   qemuBinary = qemuPkg: {
     x86_64-linux = "${qemuPkg}/bin/qemu-kvm -cpu max";
     armv7l-linux = "${qemuPkg}/bin/qemu-system-arm -enable-kvm -machine virt -cpu host";
     aarch64-linux = "${qemuPkg}/bin/qemu-system-aarch64 -enable-kvm -machine virt,gic-version=host -cpu host";
+    powerpc64le-linux = "${qemuPkg}/bin/qemu-system-ppc64 -machine powernv";
+    powerpc64-linux = "${qemuPkg}/bin/qemu-system-ppc64 -machine powernv";
     x86_64-darwin = "${qemuPkg}/bin/qemu-kvm -cpu max";
   }.${pkgs.stdenv.hostPlatform.system} or "${qemuPkg}/bin/qemu-kvm";
 }
diff --git a/nixos/modules/config/console.nix b/nixos/modules/config/console.nix
index 1339227f1e022..84ad76246fdb7 100644
--- a/nixos/modules/config/console.nix
+++ b/nixos/modules/config/console.nix
@@ -144,11 +144,16 @@ in
           ''}
         '';
 
-        systemd.services.systemd-vconsole-setup =
-          {
-            before = optional config.services.xserver.enable "display-manager.service";
-            after = [ "systemd-udev-settle.service" ];
+        systemd.services.reload-systemd-vconsole-setup =
+          { description = "Reset console on configuration changes";
+            wantedBy = [ "multi-user.target" ];
             restartTriggers = [ vconsoleConf consoleEnv ];
+            reloadIfChanged = true;
+            serviceConfig =
+              { RemainAfterExit = true;
+                ExecStart = "${pkgs.coreutils}/bin/true";
+                ExecReload = "/run/current-system/systemd/bin/systemctl restart systemd-vconsole-setup";
+              };
           };
       }
 
diff --git a/nixos/modules/config/swap.nix b/nixos/modules/config/swap.nix
index 4bb66e9b51449..59bc9e9d11e00 100644
--- a/nixos/modules/config/swap.nix
+++ b/nixos/modules/config/swap.nix
@@ -185,8 +185,6 @@ in
           { description = "Initialisation of swap device ${sw.device}";
             wantedBy = [ "${realDevice'}.swap" ];
             before = [ "${realDevice'}.swap" ];
-            # If swap is encrypted, depending on rngd resolves a possible entropy starvation during boot
-            after = mkIf (config.security.rngd.enable && sw.randomEncryption.enable) [ "rngd.service" ];
             path = [ pkgs.util-linux ] ++ optional sw.randomEncryption.enable pkgs.cryptsetup;
 
             script =
diff --git a/nixos/modules/config/users-groups.nix b/nixos/modules/config/users-groups.nix
index 5b3e9a8ceb7f4..1a530b9f01359 100644
--- a/nixos/modules/config/users-groups.nix
+++ b/nixos/modules/config/users-groups.nix
@@ -568,7 +568,7 @@ in {
     # Install all the user shells
     environment.systemPackages = systemShells;
 
-    environment.etc = (mapAttrs' (name: { packages, ... }: {
+    environment.etc = (mapAttrs' (_: { packages, name, ... }: {
       name = "profiles/per-user/${name}";
       value.source = pkgs.buildEnv {
         name = "user-environment";
diff --git a/nixos/modules/hardware/ksm.nix b/nixos/modules/hardware/ksm.nix
index 0938dbdc11018..829c3532c4597 100644
--- a/nixos/modules/hardware/ksm.nix
+++ b/nixos/modules/hardware/ksm.nix
@@ -26,13 +26,13 @@ in {
     systemd.services.enable-ksm = {
       description = "Enable Kernel Same-Page Merging";
       wantedBy = [ "multi-user.target" ];
-      after = [ "systemd-udev-settle.service" ];
-      script = ''
-        if [ -e /sys/kernel/mm/ksm ]; then
+      script =
+        ''
           echo 1 > /sys/kernel/mm/ksm/run
-          ${optionalString (cfg.sleep != null) ''echo ${toString cfg.sleep} > /sys/kernel/mm/ksm/sleep_millisecs''}
-        fi
-      '';
+        '' + optionalString (cfg.sleep != null)
+        ''
+          echo ${toString cfg.sleep} > /sys/kernel/mm/ksm/sleep_millisecs
+        '';
     };
   };
 }
diff --git a/nixos/modules/installer/cd-dvd/sd-image-aarch64-new-kernel.nix b/nixos/modules/installer/cd-dvd/sd-image-aarch64-new-kernel.nix
index 2882fbcc73052..a669d61571fef 100644
--- a/nixos/modules/installer/cd-dvd/sd-image-aarch64-new-kernel.nix
+++ b/nixos/modules/installer/cd-dvd/sd-image-aarch64-new-kernel.nix
@@ -1,7 +1,14 @@
-{ pkgs, ... }:
-
+{ config, ... }:
 {
-  imports = [ ./sd-image-aarch64.nix ];
-
-  boot.kernelPackages = pkgs.linuxPackages_latest;
+  imports = [
+    ../sd-card/sd-image-aarch64-new-kernel-installer.nix
+  ];
+  config = {
+    warnings = [
+      ''
+      .../cd-dvd/sd-image-aarch64-new-kernel.nix is deprecated and will eventually be removed.
+      Please switch to .../sd-card/sd-image-aarch64-new-kernel-installer.nix, instead.
+      ''
+    ];
+  };
 }
diff --git a/nixos/modules/installer/cd-dvd/sd-image-aarch64.nix b/nixos/modules/installer/cd-dvd/sd-image-aarch64.nix
index e4ec2d6240d02..76c1509b8f7e0 100644
--- a/nixos/modules/installer/cd-dvd/sd-image-aarch64.nix
+++ b/nixos/modules/installer/cd-dvd/sd-image-aarch64.nix
@@ -1,80 +1,14 @@
-# To build, use:
-# nix-build nixos -I nixos-config=nixos/modules/installer/cd-dvd/sd-image-aarch64.nix -A config.system.build.sdImage
-{ config, lib, pkgs, ... }:
-
+{ config, ... }:
 {
   imports = [
-    ../../profiles/base.nix
-    ../../profiles/installation-device.nix
-    ./sd-image.nix
+    ../sd-card/sd-image-aarch64-installer.nix
   ];
-
-  boot.loader.grub.enable = false;
-  boot.loader.generic-extlinux-compatible.enable = true;
-
-  boot.consoleLogLevel = lib.mkDefault 7;
-
-  # The serial ports listed here are:
-  # - ttyS0: for Tegra (Jetson TX1)
-  # - ttyAMA0: for QEMU's -machine virt
-  boot.kernelParams = ["console=ttyS0,115200n8" "console=ttyAMA0,115200n8" "console=tty0"];
-
-  boot.initrd.availableKernelModules = [
-    # Allows early (earlier) modesetting for the Raspberry Pi
-    "vc4" "bcm2835_dma" "i2c_bcm2835"
-    # Allows early (earlier) modesetting for Allwinner SoCs
-    "sun4i_drm" "sun8i_drm_hdmi" "sun8i_mixer"
-  ];
-
-  sdImage = {
-    populateFirmwareCommands = let
-      configTxt = pkgs.writeText "config.txt" ''
-        [pi3]
-        kernel=u-boot-rpi3.bin
-
-        [pi4]
-        kernel=u-boot-rpi4.bin
-        enable_gic=1
-        armstub=armstub8-gic.bin
-
-        # Otherwise the resolution will be weird in most cases, compared to
-        # what the pi3 firmware does by default.
-        disable_overscan=1
-
-        [all]
-        # Boot in 64-bit mode.
-        arm_64bit=1
-
-        # U-Boot needs this to work, regardless of whether UART is actually used or not.
-        # Look in arch/arm/mach-bcm283x/Kconfig in the U-Boot tree to see if this is still
-        # a requirement in the future.
-        enable_uart=1
-
-        # Prevent the firmware from smashing the framebuffer setup done by the mainline kernel
-        # when attempting to show low-voltage or overtemperature warnings.
-        avoid_warnings=1
-      '';
-      in ''
-        (cd ${pkgs.raspberrypifw}/share/raspberrypi/boot && cp bootcode.bin fixup*.dat start*.elf $NIX_BUILD_TOP/firmware/)
-
-        # Add the config
-        cp ${configTxt} firmware/config.txt
-
-        # Add pi3 specific files
-        cp ${pkgs.ubootRaspberryPi3_64bit}/u-boot.bin firmware/u-boot-rpi3.bin
-
-        # Add pi4 specific files
-        cp ${pkgs.ubootRaspberryPi4_64bit}/u-boot.bin firmware/u-boot-rpi4.bin
-        cp ${pkgs.raspberrypi-armstubs}/armstub8-gic.bin firmware/armstub8-gic.bin
-        cp ${pkgs.raspberrypifw}/share/raspberrypi/boot/bcm2711-rpi-4-b.dtb firmware/
-      '';
-    populateRootCommands = ''
-      mkdir -p ./files/boot
-      ${config.boot.loader.generic-extlinux-compatible.populateCmd} -c ${config.system.build.toplevel} -d ./files/boot
-    '';
+  config = {
+    warnings = [
+      ''
+      .../cd-dvd/sd-image-aarch64.nix is deprecated and will eventually be removed.
+      Please switch to .../sd-card/sd-image-aarch64-installer.nix, instead.
+      ''
+    ];
   };
-
-  # the installation media is also the installation target,
-  # so we don't want to provide the installation configuration.nix.
-  installer.cloneConfig = false;
 }
diff --git a/nixos/modules/installer/cd-dvd/sd-image-armv7l-multiplatform.nix b/nixos/modules/installer/cd-dvd/sd-image-armv7l-multiplatform.nix
index d2ba611532e0b..6ee0eb9e9b8db 100644
--- a/nixos/modules/installer/cd-dvd/sd-image-armv7l-multiplatform.nix
+++ b/nixos/modules/installer/cd-dvd/sd-image-armv7l-multiplatform.nix
@@ -1,57 +1,14 @@
-# To build, use:
-# nix-build nixos -I nixos-config=nixos/modules/installer/cd-dvd/sd-image-armv7l-multiplatform.nix -A config.system.build.sdImage
-{ config, lib, pkgs, ... }:
-
+{ config, ... }:
 {
   imports = [
-    ../../profiles/base.nix
-    ../../profiles/installation-device.nix
-    ./sd-image.nix
+    ../sd-card/sd-image-armv7l-multiplatform-installer.nix
   ];
-
-  boot.loader.grub.enable = false;
-  boot.loader.generic-extlinux-compatible.enable = true;
-
-  boot.consoleLogLevel = lib.mkDefault 7;
-  boot.kernelPackages = pkgs.linuxPackages_latest;
-  # The serial ports listed here are:
-  # - ttyS0: for Tegra (Jetson TK1)
-  # - ttymxc0: for i.MX6 (Wandboard)
-  # - ttyAMA0: for Allwinner (pcDuino3 Nano) and QEMU's -machine virt
-  # - ttyO0: for OMAP (BeagleBone Black)
-  # - ttySAC2: for Exynos (ODROID-XU3)
-  boot.kernelParams = ["console=ttyS0,115200n8" "console=ttymxc0,115200n8" "console=ttyAMA0,115200n8" "console=ttyO0,115200n8" "console=ttySAC2,115200n8" "console=tty0"];
-
-  sdImage = {
-    populateFirmwareCommands = let
-      configTxt = pkgs.writeText "config.txt" ''
-        # Prevent the firmware from smashing the framebuffer setup done by the mainline kernel
-        # when attempting to show low-voltage or overtemperature warnings.
-        avoid_warnings=1
-
-        [pi2]
-        kernel=u-boot-rpi2.bin
-
-        [pi3]
-        kernel=u-boot-rpi3.bin
-
-        # U-Boot used to need this to work, regardless of whether UART is actually used or not.
-        # TODO: check when/if this can be removed.
-        enable_uart=1
-      '';
-      in ''
-        (cd ${pkgs.raspberrypifw}/share/raspberrypi/boot && cp bootcode.bin fixup*.dat start*.elf $NIX_BUILD_TOP/firmware/)
-        cp ${pkgs.ubootRaspberryPi2}/u-boot.bin firmware/u-boot-rpi2.bin
-        cp ${pkgs.ubootRaspberryPi3_32bit}/u-boot.bin firmware/u-boot-rpi3.bin
-        cp ${configTxt} firmware/config.txt
-      '';
-    populateRootCommands = ''
-      mkdir -p ./files/boot
-      ${config.boot.loader.generic-extlinux-compatible.populateCmd} -c ${config.system.build.toplevel} -d ./files/boot
-    '';
+  config = {
+    warnings = [
+      ''
+      .../cd-dvd/sd-image-armv7l-multiplatform.nix is deprecated and will eventually be removed.
+      Please switch to .../sd-card/sd-image-armv7l-multiplatform-installer.nix, instead.
+      ''
+    ];
   };
-
-  # the installation media is also the installation target,
-  # so we don't want to provide the installation configuration.nix.
-  installer.cloneConfig = false;
 }
diff --git a/nixos/modules/installer/cd-dvd/sd-image-raspberrypi.nix b/nixos/modules/installer/cd-dvd/sd-image-raspberrypi.nix
index 40a01f9617710..747440ba9c619 100644
--- a/nixos/modules/installer/cd-dvd/sd-image-raspberrypi.nix
+++ b/nixos/modules/installer/cd-dvd/sd-image-raspberrypi.nix
@@ -1,46 +1,14 @@
-# To build, use:
-# nix-build nixos -I nixos-config=nixos/modules/installer/cd-dvd/sd-image-raspberrypi.nix -A config.system.build.sdImage
-{ config, lib, pkgs, ... }:
-
+{ config, ... }:
 {
   imports = [
-    ../../profiles/base.nix
-    ../../profiles/installation-device.nix
-    ./sd-image.nix
+    ../sd-card/sd-image-raspberrypi-installer.nix
   ];
-
-  boot.loader.grub.enable = false;
-  boot.loader.generic-extlinux-compatible.enable = true;
-
-  boot.consoleLogLevel = lib.mkDefault 7;
-  boot.kernelPackages = pkgs.linuxPackages_rpi1;
-
-  sdImage = {
-    populateFirmwareCommands = let
-      configTxt = pkgs.writeText "config.txt" ''
-        # Prevent the firmware from smashing the framebuffer setup done by the mainline kernel
-        # when attempting to show low-voltage or overtemperature warnings.
-        avoid_warnings=1
-
-        [pi0]
-        kernel=u-boot-rpi0.bin
-
-        [pi1]
-        kernel=u-boot-rpi1.bin
-      '';
-      in ''
-        (cd ${pkgs.raspberrypifw}/share/raspberrypi/boot && cp bootcode.bin fixup*.dat start*.elf $NIX_BUILD_TOP/firmware/)
-        cp ${pkgs.ubootRaspberryPiZero}/u-boot.bin firmware/u-boot-rpi0.bin
-        cp ${pkgs.ubootRaspberryPi}/u-boot.bin firmware/u-boot-rpi1.bin
-        cp ${configTxt} firmware/config.txt
-      '';
-    populateRootCommands = ''
-      mkdir -p ./files/boot
-      ${config.boot.loader.generic-extlinux-compatible.populateCmd} -c ${config.system.build.toplevel} -d ./files/boot
-    '';
+  config = {
+    warnings = [
+      ''
+      .../cd-dvd/sd-image-raspberrypi.nix is deprecated and will eventually be removed.
+      Please switch to .../sd-card/sd-image-raspberrypi-installer.nix, instead.
+      ''
+    ];
   };
-
-  # the installation media is also the installation target,
-  # so we don't want to provide the installation configuration.nix.
-  installer.cloneConfig = false;
 }
diff --git a/nixos/modules/installer/cd-dvd/sd-image-raspberrypi4.nix b/nixos/modules/installer/cd-dvd/sd-image-raspberrypi4.nix
index 5bdec7de86e8a..79db1fa29bc43 100644
--- a/nixos/modules/installer/cd-dvd/sd-image-raspberrypi4.nix
+++ b/nixos/modules/installer/cd-dvd/sd-image-raspberrypi4.nix
@@ -1,8 +1,14 @@
-# To build, use:
-# nix-build nixos -I nixos-config=nixos/modules/installer/cd-dvd/sd-image-raspberrypi4.nix -A config.system.build.sdImage
-{ config, lib, pkgs, ... }:
-
+{ config, ... }:
 {
-  imports = [ ./sd-image-aarch64.nix ];
-  boot.kernelPackages = pkgs.linuxPackages_rpi4;
+  imports = [
+    ../sd-card/sd-image-raspberrypi4-installer.nix
+  ];
+  config = {
+    warnings = [
+      ''
+      .../cd-dvd/sd-image-raspberrypi4.nix is deprecated and will eventually be removed.
+      Please switch to .../sd-card/sd-image-raspberrypi4-installer.nix, instead.
+      ''
+    ];
+  };
 }
diff --git a/nixos/modules/installer/cd-dvd/sd-image.nix b/nixos/modules/installer/cd-dvd/sd-image.nix
index b811ae07eb033..e2d6dcb3fe3ad 100644
--- a/nixos/modules/installer/cd-dvd/sd-image.nix
+++ b/nixos/modules/installer/cd-dvd/sd-image.nix
@@ -1,245 +1,14 @@
-# This module creates a bootable SD card image containing the given NixOS
-# configuration. The generated image is MBR partitioned, with a FAT
-# /boot/firmware partition, and ext4 root partition. The generated image
-# is sized to fit its contents, and a boot script automatically resizes
-# the root partition to fit the device on the first boot.
-#
-# The firmware partition is built with expectation to hold the Raspberry
-# Pi firmware and bootloader, and be removed and replaced with a firmware
-# build for the target SoC for other board families.
-#
-# The derivation for the SD image will be placed in
-# config.system.build.sdImage
-
-{ config, lib, pkgs, ... }:
-
-with lib;
-
-let
-  rootfsImage = pkgs.callPackage ../../../lib/make-ext4-fs.nix ({
-    inherit (config.sdImage) storePaths;
-    compressImage = true;
-    populateImageCommands = config.sdImage.populateRootCommands;
-    volumeLabel = "NIXOS_SD";
-  } // optionalAttrs (config.sdImage.rootPartitionUUID != null) {
-    uuid = config.sdImage.rootPartitionUUID;
-  });
-in
+{ config, ... }:
 {
   imports = [
-    (mkRemovedOptionModule [ "sdImage" "bootPartitionID" ] "The FAT partition for SD image now only holds the Raspberry Pi firmware files. Use firmwarePartitionID to configure that partition's ID.")
-    (mkRemovedOptionModule [ "sdImage" "bootSize" ] "The boot files for SD image have been moved to the main ext4 partition. The FAT partition now only holds the Raspberry Pi firmware files. Changing its size may not be required.")
+    ../sd-card/sd-image.nix
   ];
-
-  options.sdImage = {
-    imageName = mkOption {
-      default = "${config.sdImage.imageBaseName}-${config.system.nixos.label}-${pkgs.stdenv.hostPlatform.system}.img";
-      description = ''
-        Name of the generated image file.
-      '';
-    };
-
-    imageBaseName = mkOption {
-      default = "nixos-sd-image";
-      description = ''
-        Prefix of the name of the generated image file.
-      '';
-    };
-
-    storePaths = mkOption {
-      type = with types; listOf package;
-      example = literalExample "[ pkgs.stdenv ]";
-      description = ''
-        Derivations to be included in the Nix store in the generated SD image.
-      '';
-    };
-
-    firmwarePartitionID = mkOption {
-      type = types.str;
-      default = "0x2178694e";
-      description = ''
-        Volume ID for the /boot/firmware partition on the SD card. This value
-        must be a 32-bit hexadecimal number.
-      '';
-    };
-
-    firmwarePartitionName = mkOption {
-      type = types.str;
-      default = "FIRMWARE";
-      description = ''
-        Name of the filesystem which holds the boot firmware.
-      '';
-    };
-
-    rootPartitionUUID = mkOption {
-      type = types.nullOr types.str;
-      default = null;
-      example = "14e19a7b-0ae0-484d-9d54-43bd6fdc20c7";
-      description = ''
-        UUID for the filesystem on the main NixOS partition on the SD card.
-      '';
-    };
-
-    firmwareSize = mkOption {
-      type = types.int;
-      # As of 2019-08-18 the Raspberry pi firmware + u-boot takes ~18MiB
-      default = 30;
-      description = ''
-        Size of the /boot/firmware partition, in megabytes.
-      '';
-    };
-
-    populateFirmwareCommands = mkOption {
-      example = literalExample "'' cp \${pkgs.myBootLoader}/u-boot.bin firmware/ ''";
-      description = ''
-        Shell commands to populate the ./firmware directory.
-        All files in that directory are copied to the
-        /boot/firmware partition on the SD image.
-      '';
-    };
-
-    populateRootCommands = mkOption {
-      example = literalExample "''\${config.boot.loader.generic-extlinux-compatible.populateCmd} -c \${config.system.build.toplevel} -d ./files/boot''";
-      description = ''
-        Shell commands to populate the ./files directory.
-        All files in that directory are copied to the
-        root (/) partition on the SD image. Use this to
-        populate the ./files/boot (/boot) directory.
-      '';
-    };
-
-    postBuildCommands = mkOption {
-      example = literalExample "'' dd if=\${pkgs.myBootLoader}/SPL of=$img bs=1024 seek=1 conv=notrunc ''";
-      default = "";
-      description = ''
-        Shell commands to run after the image is built.
-        Can be used for boards requiring to dd u-boot SPL before actual partitions.
-      '';
-    };
-
-    compressImage = mkOption {
-      type = types.bool;
-      default = true;
-      description = ''
-        Whether the SD image should be compressed using
-        <command>zstd</command>.
-      '';
-    };
-
-  };
-
   config = {
-    fileSystems = {
-      "/boot/firmware" = {
-        device = "/dev/disk/by-label/${config.sdImage.firmwarePartitionName}";
-        fsType = "vfat";
-        # Alternatively, this could be removed from the configuration.
-        # The filesystem is not needed at runtime, it could be treated
-        # as an opaque blob instead of a discrete FAT32 filesystem.
-        options = [ "nofail" "noauto" ];
-      };
-      "/" = {
-        device = "/dev/disk/by-label/NIXOS_SD";
-        fsType = "ext4";
-      };
-    };
-
-    sdImage.storePaths = [ config.system.build.toplevel ];
-
-    system.build.sdImage = pkgs.callPackage ({ stdenv, dosfstools, e2fsprogs,
-    mtools, libfaketime, util-linux, zstd }: stdenv.mkDerivation {
-      name = config.sdImage.imageName;
-
-      nativeBuildInputs = [ dosfstools e2fsprogs mtools libfaketime util-linux zstd ];
-
-      inherit (config.sdImage) compressImage;
-
-      buildCommand = ''
-        mkdir -p $out/nix-support $out/sd-image
-        export img=$out/sd-image/${config.sdImage.imageName}
-
-        echo "${pkgs.stdenv.buildPlatform.system}" > $out/nix-support/system
-        if test -n "$compressImage"; then
-          echo "file sd-image $img.zst" >> $out/nix-support/hydra-build-products
-        else
-          echo "file sd-image $img" >> $out/nix-support/hydra-build-products
-        fi
-
-        echo "Decompressing rootfs image"
-        zstd -d --no-progress "${rootfsImage}" -o ./root-fs.img
-
-        # Gap in front of the first partition, in MiB
-        gap=8
-
-        # Create the image file sized to fit /boot/firmware and /, plus slack for the gap.
-        rootSizeBlocks=$(du -B 512 --apparent-size ./root-fs.img | awk '{ print $1 }')
-        firmwareSizeBlocks=$((${toString config.sdImage.firmwareSize} * 1024 * 1024 / 512))
-        imageSize=$((rootSizeBlocks * 512 + firmwareSizeBlocks * 512 + gap * 1024 * 1024))
-        truncate -s $imageSize $img
-
-        # type=b is 'W95 FAT32', type=83 is 'Linux'.
-        # The "bootable" partition is where u-boot will look file for the bootloader
-        # information (dtbs, extlinux.conf file).
-        sfdisk $img <<EOF
-            label: dos
-            label-id: ${config.sdImage.firmwarePartitionID}
-
-            start=''${gap}M, size=$firmwareSizeBlocks, type=b
-            start=$((gap + ${toString config.sdImage.firmwareSize}))M, type=83, bootable
-        EOF
-
-        # Copy the rootfs into the SD image
-        eval $(partx $img -o START,SECTORS --nr 2 --pairs)
-        dd conv=notrunc if=./root-fs.img of=$img seek=$START count=$SECTORS
-
-        # Create a FAT32 /boot/firmware partition of suitable size into firmware_part.img
-        eval $(partx $img -o START,SECTORS --nr 1 --pairs)
-        truncate -s $((SECTORS * 512)) firmware_part.img
-        faketime "1970-01-01 00:00:00" mkfs.vfat -i ${config.sdImage.firmwarePartitionID} -n ${config.sdImage.firmwarePartitionName} firmware_part.img
-
-        # Populate the files intended for /boot/firmware
-        mkdir firmware
-        ${config.sdImage.populateFirmwareCommands}
-
-        # Copy the populated /boot/firmware into the SD image
-        (cd firmware; mcopy -psvm -i ../firmware_part.img ./* ::)
-        # Verify the FAT partition before copying it.
-        fsck.vfat -vn firmware_part.img
-        dd conv=notrunc if=firmware_part.img of=$img seek=$START count=$SECTORS
-
-        ${config.sdImage.postBuildCommands}
-
-        if test -n "$compressImage"; then
-            zstd -T$NIX_BUILD_CORES --rm $img
-        fi
-      '';
-    }) {};
-
-    boot.postBootCommands = ''
-      # On the first boot do some maintenance tasks
-      if [ -f /nix-path-registration ]; then
-        set -euo pipefail
-        set -x
-        # Figure out device names for the boot device and root filesystem.
-        rootPart=$(${pkgs.util-linux}/bin/findmnt -n -o SOURCE /)
-        bootDevice=$(lsblk -npo PKNAME $rootPart)
-        partNum=$(lsblk -npo MAJ:MIN $rootPart | ${pkgs.gawk}/bin/awk -F: '{print $2}')
-
-        # Resize the root partition and the filesystem to fit the disk
-        echo ",+," | sfdisk -N$partNum --no-reread $bootDevice
-        ${pkgs.parted}/bin/partprobe
-        ${pkgs.e2fsprogs}/bin/resize2fs $rootPart
-
-        # Register the contents of the initial Nix store
-        ${config.nix.package.out}/bin/nix-store --load-db < /nix-path-registration
-
-        # nixos-rebuild also requires a "system" profile and an /etc/NIXOS tag.
-        touch /etc/NIXOS
-        ${config.nix.package.out}/bin/nix-env -p /nix/var/nix/profiles/system --set /run/current-system
-
-        # Prevents this from running on later boots.
-        rm -f /nix-path-registration
-      fi
-    '';
+    warnings = [
+      ''
+      .../cd-dvd/sd-image.nix is deprecated and will eventually be removed.
+      Please switch to .../sd-card/sd-image.nix, instead.
+      ''
+    ];
   };
 }
diff --git a/nixos/modules/installer/cd-dvd/system-tarball-fuloong2f.nix b/nixos/modules/installer/cd-dvd/system-tarball-fuloong2f.nix
index 8159576a62ac7..123f487baf93c 100644
--- a/nixos/modules/installer/cd-dvd/system-tarball-fuloong2f.nix
+++ b/nixos/modules/installer/cd-dvd/system-tarball-fuloong2f.nix
@@ -26,7 +26,7 @@ let
   # A clue for the kernel loading
   kernelParams = pkgs.writeText "kernel-params.txt" ''
     Kernel Parameters:
-      init=/boot/init systemConfig=/boot/init ${toString config.boot.kernelParams}
+      init=/boot/init ${toString config.boot.kernelParams}
   '';
 
   # System wide nixpkgs config
diff --git a/nixos/modules/installer/cd-dvd/system-tarball-pc.nix b/nixos/modules/installer/cd-dvd/system-tarball-pc.nix
index f2af7dcde3d54..a79209d7dfeff 100644
--- a/nixos/modules/installer/cd-dvd/system-tarball-pc.nix
+++ b/nixos/modules/installer/cd-dvd/system-tarball-pc.nix
@@ -23,13 +23,13 @@ let
     label nixos
       MENU LABEL ^NixOS using nfsroot
       KERNEL bzImage
-      append ip=dhcp nfsroot=/home/pcroot systemConfig=${config.system.build.toplevel} init=${config.system.build.toplevel}/init rw
+      append ip=dhcp nfsroot=/home/pcroot init=${config.system.build.toplevel}/init rw
 
     # I don't know how to make this boot with nfsroot (using the initrd)
     label nixos_initrd
       MENU LABEL NixOS booting the poor ^initrd.
       KERNEL bzImage
-      append initrd=initrd ip=dhcp nfsroot=/home/pcroot systemConfig=${config.system.build.toplevel} init=${config.system.build.toplevel}/init rw
+      append initrd=initrd ip=dhcp nfsroot=/home/pcroot init=${config.system.build.toplevel}/init rw
 
     label memtest
       MENU LABEL ^${pkgs.memtest86.name}
diff --git a/nixos/modules/installer/sd-card/sd-image-aarch64-installer.nix b/nixos/modules/installer/sd-card/sd-image-aarch64-installer.nix
new file mode 100644
index 0000000000000..2a6b6abdf913e
--- /dev/null
+++ b/nixos/modules/installer/sd-card/sd-image-aarch64-installer.nix
@@ -0,0 +1,10 @@
+{
+  imports = [
+    ../../profiles/installation-device.nix
+    ./sd-image-aarch64.nix
+  ];
+
+  # the installation media is also the installation target,
+  # so we don't want to provide the installation configuration.nix.
+  installer.cloneConfig = false;
+}
diff --git a/nixos/modules/installer/sd-card/sd-image-aarch64-new-kernel-installer.nix b/nixos/modules/installer/sd-card/sd-image-aarch64-new-kernel-installer.nix
new file mode 100644
index 0000000000000..1b6b55ff29183
--- /dev/null
+++ b/nixos/modules/installer/sd-card/sd-image-aarch64-new-kernel-installer.nix
@@ -0,0 +1,10 @@
+{
+  imports = [
+    ../../profiles/installation-device.nix
+    ./sd-image-aarch64-new-kernel.nix
+  ];
+
+  # the installation media is also the installation target,
+  # so we don't want to provide the installation configuration.nix.
+  installer.cloneConfig = false;
+}
diff --git a/nixos/modules/installer/sd-card/sd-image-aarch64-new-kernel.nix b/nixos/modules/installer/sd-card/sd-image-aarch64-new-kernel.nix
new file mode 100644
index 0000000000000..2882fbcc73052
--- /dev/null
+++ b/nixos/modules/installer/sd-card/sd-image-aarch64-new-kernel.nix
@@ -0,0 +1,7 @@
+{ pkgs, ... }:
+
+{
+  imports = [ ./sd-image-aarch64.nix ];
+
+  boot.kernelPackages = pkgs.linuxPackages_latest;
+}
diff --git a/nixos/modules/installer/sd-card/sd-image-aarch64.nix b/nixos/modules/installer/sd-card/sd-image-aarch64.nix
new file mode 100644
index 0000000000000..96ebb7537da32
--- /dev/null
+++ b/nixos/modules/installer/sd-card/sd-image-aarch64.nix
@@ -0,0 +1,75 @@
+# To build, use:
+# nix-build nixos -I nixos-config=nixos/modules/installer/sd-card/sd-image-aarch64.nix -A config.system.build.sdImage
+{ config, lib, pkgs, ... }:
+
+{
+  imports = [
+    ../../profiles/base.nix
+    ./sd-image.nix
+  ];
+
+  boot.loader.grub.enable = false;
+  boot.loader.generic-extlinux-compatible.enable = true;
+
+  boot.consoleLogLevel = lib.mkDefault 7;
+
+  # The serial ports listed here are:
+  # - ttyS0: for Tegra (Jetson TX1)
+  # - ttyAMA0: for QEMU's -machine virt
+  boot.kernelParams = ["console=ttyS0,115200n8" "console=ttyAMA0,115200n8" "console=tty0"];
+
+  boot.initrd.availableKernelModules = [
+    # Allows early (earlier) modesetting for the Raspberry Pi
+    "vc4" "bcm2835_dma" "i2c_bcm2835"
+    # Allows early (earlier) modesetting for Allwinner SoCs
+    "sun4i_drm" "sun8i_drm_hdmi" "sun8i_mixer"
+  ];
+
+  sdImage = {
+    populateFirmwareCommands = let
+      configTxt = pkgs.writeText "config.txt" ''
+        [pi3]
+        kernel=u-boot-rpi3.bin
+
+        [pi4]
+        kernel=u-boot-rpi4.bin
+        enable_gic=1
+        armstub=armstub8-gic.bin
+
+        # Otherwise the resolution will be weird in most cases, compared to
+        # what the pi3 firmware does by default.
+        disable_overscan=1
+
+        [all]
+        # Boot in 64-bit mode.
+        arm_64bit=1
+
+        # U-Boot needs this to work, regardless of whether UART is actually used or not.
+        # Look in arch/arm/mach-bcm283x/Kconfig in the U-Boot tree to see if this is still
+        # a requirement in the future.
+        enable_uart=1
+
+        # Prevent the firmware from smashing the framebuffer setup done by the mainline kernel
+        # when attempting to show low-voltage or overtemperature warnings.
+        avoid_warnings=1
+      '';
+      in ''
+        (cd ${pkgs.raspberrypifw}/share/raspberrypi/boot && cp bootcode.bin fixup*.dat start*.elf $NIX_BUILD_TOP/firmware/)
+
+        # Add the config
+        cp ${configTxt} firmware/config.txt
+
+        # Add pi3 specific files
+        cp ${pkgs.ubootRaspberryPi3_64bit}/u-boot.bin firmware/u-boot-rpi3.bin
+
+        # Add pi4 specific files
+        cp ${pkgs.ubootRaspberryPi4_64bit}/u-boot.bin firmware/u-boot-rpi4.bin
+        cp ${pkgs.raspberrypi-armstubs}/armstub8-gic.bin firmware/armstub8-gic.bin
+        cp ${pkgs.raspberrypifw}/share/raspberrypi/boot/bcm2711-rpi-4-b.dtb firmware/
+      '';
+    populateRootCommands = ''
+      mkdir -p ./files/boot
+      ${config.boot.loader.generic-extlinux-compatible.populateCmd} -c ${config.system.build.toplevel} -d ./files/boot
+    '';
+  };
+}
diff --git a/nixos/modules/installer/sd-card/sd-image-armv7l-multiplatform-installer.nix b/nixos/modules/installer/sd-card/sd-image-armv7l-multiplatform-installer.nix
new file mode 100644
index 0000000000000..fbe04377d50d6
--- /dev/null
+++ b/nixos/modules/installer/sd-card/sd-image-armv7l-multiplatform-installer.nix
@@ -0,0 +1,10 @@
+{
+  imports = [
+    ../../profiles/installation-device.nix
+    ./sd-image-armv7l-multiplatform.nix
+  ];
+
+  # the installation media is also the installation target,
+  # so we don't want to provide the installation configuration.nix.
+  installer.cloneConfig = false;
+}
diff --git a/nixos/modules/installer/sd-card/sd-image-armv7l-multiplatform.nix b/nixos/modules/installer/sd-card/sd-image-armv7l-multiplatform.nix
new file mode 100644
index 0000000000000..23ed928512966
--- /dev/null
+++ b/nixos/modules/installer/sd-card/sd-image-armv7l-multiplatform.nix
@@ -0,0 +1,52 @@
+# To build, use:
+# nix-build nixos -I nixos-config=nixos/modules/installer/sd-card/sd-image-armv7l-multiplatform.nix -A config.system.build.sdImage
+{ config, lib, pkgs, ... }:
+
+{
+  imports = [
+    ../../profiles/base.nix
+    ./sd-image.nix
+  ];
+
+  boot.loader.grub.enable = false;
+  boot.loader.generic-extlinux-compatible.enable = true;
+
+  boot.consoleLogLevel = lib.mkDefault 7;
+  boot.kernelPackages = pkgs.linuxPackages_latest;
+  # The serial ports listed here are:
+  # - ttyS0: for Tegra (Jetson TK1)
+  # - ttymxc0: for i.MX6 (Wandboard)
+  # - ttyAMA0: for Allwinner (pcDuino3 Nano) and QEMU's -machine virt
+  # - ttyO0: for OMAP (BeagleBone Black)
+  # - ttySAC2: for Exynos (ODROID-XU3)
+  boot.kernelParams = ["console=ttyS0,115200n8" "console=ttymxc0,115200n8" "console=ttyAMA0,115200n8" "console=ttyO0,115200n8" "console=ttySAC2,115200n8" "console=tty0"];
+
+  sdImage = {
+    populateFirmwareCommands = let
+      configTxt = pkgs.writeText "config.txt" ''
+        # Prevent the firmware from smashing the framebuffer setup done by the mainline kernel
+        # when attempting to show low-voltage or overtemperature warnings.
+        avoid_warnings=1
+
+        [pi2]
+        kernel=u-boot-rpi2.bin
+
+        [pi3]
+        kernel=u-boot-rpi3.bin
+
+        # U-Boot used to need this to work, regardless of whether UART is actually used or not.
+        # TODO: check when/if this can be removed.
+        enable_uart=1
+      '';
+      in ''
+        (cd ${pkgs.raspberrypifw}/share/raspberrypi/boot && cp bootcode.bin fixup*.dat start*.elf $NIX_BUILD_TOP/firmware/)
+        cp ${pkgs.ubootRaspberryPi2}/u-boot.bin firmware/u-boot-rpi2.bin
+        cp ${pkgs.ubootRaspberryPi3_32bit}/u-boot.bin firmware/u-boot-rpi3.bin
+        cp ${configTxt} firmware/config.txt
+      '';
+    populateRootCommands = ''
+      mkdir -p ./files/boot
+      ${config.boot.loader.generic-extlinux-compatible.populateCmd} -c ${config.system.build.toplevel} -d ./files/boot
+    '';
+  };
+}
diff --git a/nixos/modules/installer/sd-card/sd-image-raspberrypi-installer.nix b/nixos/modules/installer/sd-card/sd-image-raspberrypi-installer.nix
new file mode 100644
index 0000000000000..72ec7485b528d
--- /dev/null
+++ b/nixos/modules/installer/sd-card/sd-image-raspberrypi-installer.nix
@@ -0,0 +1,10 @@
+{
+  imports = [
+    ../../profiles/installation-device.nix
+    ./sd-image-raspberrypi.nix
+  ];
+
+  # the installation media is also the installation target,
+  # so we don't want to provide the installation configuration.nix.
+  installer.cloneConfig = false;
+}
diff --git a/nixos/modules/installer/sd-card/sd-image-raspberrypi.nix b/nixos/modules/installer/sd-card/sd-image-raspberrypi.nix
new file mode 100644
index 0000000000000..83850f4c11587
--- /dev/null
+++ b/nixos/modules/installer/sd-card/sd-image-raspberrypi.nix
@@ -0,0 +1,41 @@
+# To build, use:
+# nix-build nixos -I nixos-config=nixos/modules/installer/sd-card/sd-image-raspberrypi.nix -A config.system.build.sdImage
+{ config, lib, pkgs, ... }:
+
+{
+  imports = [
+    ../../profiles/base.nix
+    ./sd-image.nix
+  ];
+
+  boot.loader.grub.enable = false;
+  boot.loader.generic-extlinux-compatible.enable = true;
+
+  boot.consoleLogLevel = lib.mkDefault 7;
+  boot.kernelPackages = pkgs.linuxPackages_rpi1;
+
+  sdImage = {
+    populateFirmwareCommands = let
+      configTxt = pkgs.writeText "config.txt" ''
+        # Prevent the firmware from smashing the framebuffer setup done by the mainline kernel
+        # when attempting to show low-voltage or overtemperature warnings.
+        avoid_warnings=1
+
+        [pi0]
+        kernel=u-boot-rpi0.bin
+
+        [pi1]
+        kernel=u-boot-rpi1.bin
+      '';
+      in ''
+        (cd ${pkgs.raspberrypifw}/share/raspberrypi/boot && cp bootcode.bin fixup*.dat start*.elf $NIX_BUILD_TOP/firmware/)
+        cp ${pkgs.ubootRaspberryPiZero}/u-boot.bin firmware/u-boot-rpi0.bin
+        cp ${pkgs.ubootRaspberryPi}/u-boot.bin firmware/u-boot-rpi1.bin
+        cp ${configTxt} firmware/config.txt
+      '';
+    populateRootCommands = ''
+      mkdir -p ./files/boot
+      ${config.boot.loader.generic-extlinux-compatible.populateCmd} -c ${config.system.build.toplevel} -d ./files/boot
+    '';
+  };
+}
diff --git a/nixos/modules/installer/sd-card/sd-image-raspberrypi4-installer.nix b/nixos/modules/installer/sd-card/sd-image-raspberrypi4-installer.nix
new file mode 100644
index 0000000000000..59423e40b64c9
--- /dev/null
+++ b/nixos/modules/installer/sd-card/sd-image-raspberrypi4-installer.nix
@@ -0,0 +1,10 @@
+{
+  imports = [
+    ../../profiles/installation-device.nix
+    ./sd-image-raspberrypi4.nix
+  ];
+
+  # the installation media is also the installation target,
+  # so we don't want to provide the installation configuration.nix.
+  installer.cloneConfig = false;
+}
diff --git a/nixos/modules/installer/sd-card/sd-image-raspberrypi4.nix b/nixos/modules/installer/sd-card/sd-image-raspberrypi4.nix
new file mode 100644
index 0000000000000..35a12c5382f7e
--- /dev/null
+++ b/nixos/modules/installer/sd-card/sd-image-raspberrypi4.nix
@@ -0,0 +1,8 @@
+# To build, use:
+# nix-build nixos -I nixos-config=nixos/modules/installer/sd-card/sd-image-raspberrypi4.nix -A config.system.build.sdImage
+{ config, lib, pkgs, ... }:
+
+{
+  imports = [ ./sd-image-aarch64.nix ];
+  boot.kernelPackages = pkgs.linuxPackages_rpi4;
+}
diff --git a/nixos/modules/installer/sd-card/sd-image.nix b/nixos/modules/installer/sd-card/sd-image.nix
new file mode 100644
index 0000000000000..b811ae07eb033
--- /dev/null
+++ b/nixos/modules/installer/sd-card/sd-image.nix
@@ -0,0 +1,245 @@
+# This module creates a bootable SD card image containing the given NixOS
+# configuration. The generated image is MBR partitioned, with a FAT
+# /boot/firmware partition, and ext4 root partition. The generated image
+# is sized to fit its contents, and a boot script automatically resizes
+# the root partition to fit the device on the first boot.
+#
+# The firmware partition is built with expectation to hold the Raspberry
+# Pi firmware and bootloader, and be removed and replaced with a firmware
+# build for the target SoC for other board families.
+#
+# The derivation for the SD image will be placed in
+# config.system.build.sdImage
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  rootfsImage = pkgs.callPackage ../../../lib/make-ext4-fs.nix ({
+    inherit (config.sdImage) storePaths;
+    compressImage = true;
+    populateImageCommands = config.sdImage.populateRootCommands;
+    volumeLabel = "NIXOS_SD";
+  } // optionalAttrs (config.sdImage.rootPartitionUUID != null) {
+    uuid = config.sdImage.rootPartitionUUID;
+  });
+in
+{
+  imports = [
+    (mkRemovedOptionModule [ "sdImage" "bootPartitionID" ] "The FAT partition for SD image now only holds the Raspberry Pi firmware files. Use firmwarePartitionID to configure that partition's ID.")
+    (mkRemovedOptionModule [ "sdImage" "bootSize" ] "The boot files for SD image have been moved to the main ext4 partition. The FAT partition now only holds the Raspberry Pi firmware files. Changing its size may not be required.")
+  ];
+
+  options.sdImage = {
+    imageName = mkOption {
+      default = "${config.sdImage.imageBaseName}-${config.system.nixos.label}-${pkgs.stdenv.hostPlatform.system}.img";
+      description = ''
+        Name of the generated image file.
+      '';
+    };
+
+    imageBaseName = mkOption {
+      default = "nixos-sd-image";
+      description = ''
+        Prefix of the name of the generated image file.
+      '';
+    };
+
+    storePaths = mkOption {
+      type = with types; listOf package;
+      example = literalExample "[ pkgs.stdenv ]";
+      description = ''
+        Derivations to be included in the Nix store in the generated SD image.
+      '';
+    };
+
+    firmwarePartitionID = mkOption {
+      type = types.str;
+      default = "0x2178694e";
+      description = ''
+        Volume ID for the /boot/firmware partition on the SD card. This value
+        must be a 32-bit hexadecimal number.
+      '';
+    };
+
+    firmwarePartitionName = mkOption {
+      type = types.str;
+      default = "FIRMWARE";
+      description = ''
+        Name of the filesystem which holds the boot firmware.
+      '';
+    };
+
+    rootPartitionUUID = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      example = "14e19a7b-0ae0-484d-9d54-43bd6fdc20c7";
+      description = ''
+        UUID for the filesystem on the main NixOS partition on the SD card.
+      '';
+    };
+
+    firmwareSize = mkOption {
+      type = types.int;
+      # As of 2019-08-18 the Raspberry pi firmware + u-boot takes ~18MiB
+      default = 30;
+      description = ''
+        Size of the /boot/firmware partition, in megabytes.
+      '';
+    };
+
+    populateFirmwareCommands = mkOption {
+      example = literalExample "'' cp \${pkgs.myBootLoader}/u-boot.bin firmware/ ''";
+      description = ''
+        Shell commands to populate the ./firmware directory.
+        All files in that directory are copied to the
+        /boot/firmware partition on the SD image.
+      '';
+    };
+
+    populateRootCommands = mkOption {
+      example = literalExample "''\${config.boot.loader.generic-extlinux-compatible.populateCmd} -c \${config.system.build.toplevel} -d ./files/boot''";
+      description = ''
+        Shell commands to populate the ./files directory.
+        All files in that directory are copied to the
+        root (/) partition on the SD image. Use this to
+        populate the ./files/boot (/boot) directory.
+      '';
+    };
+
+    postBuildCommands = mkOption {
+      example = literalExample "'' dd if=\${pkgs.myBootLoader}/SPL of=$img bs=1024 seek=1 conv=notrunc ''";
+      default = "";
+      description = ''
+        Shell commands to run after the image is built.
+        Can be used for boards requiring to dd u-boot SPL before actual partitions.
+      '';
+    };
+
+    compressImage = mkOption {
+      type = types.bool;
+      default = true;
+      description = ''
+        Whether the SD image should be compressed using
+        <command>zstd</command>.
+      '';
+    };
+
+  };
+
+  config = {
+    fileSystems = {
+      "/boot/firmware" = {
+        device = "/dev/disk/by-label/${config.sdImage.firmwarePartitionName}";
+        fsType = "vfat";
+        # Alternatively, this could be removed from the configuration.
+        # The filesystem is not needed at runtime, it could be treated
+        # as an opaque blob instead of a discrete FAT32 filesystem.
+        options = [ "nofail" "noauto" ];
+      };
+      "/" = {
+        device = "/dev/disk/by-label/NIXOS_SD";
+        fsType = "ext4";
+      };
+    };
+
+    sdImage.storePaths = [ config.system.build.toplevel ];
+
+    system.build.sdImage = pkgs.callPackage ({ stdenv, dosfstools, e2fsprogs,
+    mtools, libfaketime, util-linux, zstd }: stdenv.mkDerivation {
+      name = config.sdImage.imageName;
+
+      nativeBuildInputs = [ dosfstools e2fsprogs mtools libfaketime util-linux zstd ];
+
+      inherit (config.sdImage) compressImage;
+
+      buildCommand = ''
+        mkdir -p $out/nix-support $out/sd-image
+        export img=$out/sd-image/${config.sdImage.imageName}
+
+        echo "${pkgs.stdenv.buildPlatform.system}" > $out/nix-support/system
+        if test -n "$compressImage"; then
+          echo "file sd-image $img.zst" >> $out/nix-support/hydra-build-products
+        else
+          echo "file sd-image $img" >> $out/nix-support/hydra-build-products
+        fi
+
+        echo "Decompressing rootfs image"
+        zstd -d --no-progress "${rootfsImage}" -o ./root-fs.img
+
+        # Gap in front of the first partition, in MiB
+        gap=8
+
+        # Create the image file sized to fit /boot/firmware and /, plus slack for the gap.
+        rootSizeBlocks=$(du -B 512 --apparent-size ./root-fs.img | awk '{ print $1 }')
+        firmwareSizeBlocks=$((${toString config.sdImage.firmwareSize} * 1024 * 1024 / 512))
+        imageSize=$((rootSizeBlocks * 512 + firmwareSizeBlocks * 512 + gap * 1024 * 1024))
+        truncate -s $imageSize $img
+
+        # type=b is 'W95 FAT32', type=83 is 'Linux'.
+        # The "bootable" partition is where u-boot will look file for the bootloader
+        # information (dtbs, extlinux.conf file).
+        sfdisk $img <<EOF
+            label: dos
+            label-id: ${config.sdImage.firmwarePartitionID}
+
+            start=''${gap}M, size=$firmwareSizeBlocks, type=b
+            start=$((gap + ${toString config.sdImage.firmwareSize}))M, type=83, bootable
+        EOF
+
+        # Copy the rootfs into the SD image
+        eval $(partx $img -o START,SECTORS --nr 2 --pairs)
+        dd conv=notrunc if=./root-fs.img of=$img seek=$START count=$SECTORS
+
+        # Create a FAT32 /boot/firmware partition of suitable size into firmware_part.img
+        eval $(partx $img -o START,SECTORS --nr 1 --pairs)
+        truncate -s $((SECTORS * 512)) firmware_part.img
+        faketime "1970-01-01 00:00:00" mkfs.vfat -i ${config.sdImage.firmwarePartitionID} -n ${config.sdImage.firmwarePartitionName} firmware_part.img
+
+        # Populate the files intended for /boot/firmware
+        mkdir firmware
+        ${config.sdImage.populateFirmwareCommands}
+
+        # Copy the populated /boot/firmware into the SD image
+        (cd firmware; mcopy -psvm -i ../firmware_part.img ./* ::)
+        # Verify the FAT partition before copying it.
+        fsck.vfat -vn firmware_part.img
+        dd conv=notrunc if=firmware_part.img of=$img seek=$START count=$SECTORS
+
+        ${config.sdImage.postBuildCommands}
+
+        if test -n "$compressImage"; then
+            zstd -T$NIX_BUILD_CORES --rm $img
+        fi
+      '';
+    }) {};
+
+    boot.postBootCommands = ''
+      # On the first boot do some maintenance tasks
+      if [ -f /nix-path-registration ]; then
+        set -euo pipefail
+        set -x
+        # Figure out device names for the boot device and root filesystem.
+        rootPart=$(${pkgs.util-linux}/bin/findmnt -n -o SOURCE /)
+        bootDevice=$(lsblk -npo PKNAME $rootPart)
+        partNum=$(lsblk -npo MAJ:MIN $rootPart | ${pkgs.gawk}/bin/awk -F: '{print $2}')
+
+        # Resize the root partition and the filesystem to fit the disk
+        echo ",+," | sfdisk -N$partNum --no-reread $bootDevice
+        ${pkgs.parted}/bin/partprobe
+        ${pkgs.e2fsprogs}/bin/resize2fs $rootPart
+
+        # Register the contents of the initial Nix store
+        ${config.nix.package.out}/bin/nix-store --load-db < /nix-path-registration
+
+        # nixos-rebuild also requires a "system" profile and an /etc/NIXOS tag.
+        touch /etc/NIXOS
+        ${config.nix.package.out}/bin/nix-env -p /nix/var/nix/profiles/system --set /run/current-system
+
+        # Prevents this from running on later boots.
+        rm -f /nix-path-registration
+      fi
+    '';
+  };
+}
diff --git a/nixos/modules/installer/tools/nixos-generate-config.pl b/nixos/modules/installer/tools/nixos-generate-config.pl
index 6e3ddb875e1b4..7bc55e67134b6 100644
--- a/nixos/modules/installer/tools/nixos-generate-config.pl
+++ b/nixos/modules/installer/tools/nixos-generate-config.pl
@@ -585,6 +585,22 @@ EOF
     return $config;
 }
 
+sub generateXserverConfig {
+    my $xserverEnabled = "@xserverEnabled@";
+
+    my $config = "";
+    if ($xserverEnabled eq "1") {
+        $config = <<EOF;
+  # Enable the X11 windowing system.
+  services.xserver.enable = true;
+EOF
+    } else {
+        $config = <<EOF;
+  # Enable the X11 windowing system.
+  # services.xserver.enable = true;
+EOF
+    }
+}
 
 if ($showHardwareConfig) {
     print STDOUT $hwConfig;
@@ -630,6 +646,8 @@ EOF
 
         my $networkingDhcpConfig = generateNetworkingDhcpConfig();
 
+        my $xserverConfig = generateXserverConfig();
+
         (my $desktopConfiguration = <<EOF)=~s/^/  /gm;
 @desktopConfiguration@
 EOF
diff --git a/nixos/modules/installer/tools/tools.nix b/nixos/modules/installer/tools/tools.nix
index ada5f57485612..a9e5641b05a2e 100644
--- a/nixos/modules/installer/tools/tools.nix
+++ b/nixos/modules/installer/tools/tools.nix
@@ -36,6 +36,7 @@ let
     path = lib.optionals (lib.elem "btrfs" config.boot.supportedFilesystems) [ pkgs.btrfs-progs ];
     perl = "${pkgs.perl}/bin/perl -I${pkgs.perlPackages.FileSlurp}/${pkgs.perl.libPrefix}";
     inherit (config.system.nixos-generate-config) configuration desktopConfiguration;
+    xserverEnabled = config.services.xserver.enable;
   };
 
   nixos-option =
@@ -87,8 +88,8 @@ in
 
     desktopConfiguration = mkOption {
       internal = true;
-      type = types.str;
-      default = "";
+      type = types.listOf types.lines;
+      default = [];
       description = ''
         Text to preseed the desktop configuration that <literal>nixos-generate-config</literal>
         saves to <literal>/etc/nixos/configuration.nix</literal>.
@@ -136,6 +137,8 @@ in
         #   keyMap = "us";
         # };
 
+      $xserverConfig
+
       $desktopConfiguration
         # Configure keymap in X11
         # services.xserver.layout = "us";
diff --git a/nixos/modules/misc/crashdump.nix b/nixos/modules/misc/crashdump.nix
index 11dec37b3fae1..796078d7ef8c4 100644
--- a/nixos/modules/misc/crashdump.nix
+++ b/nixos/modules/misc/crashdump.nix
@@ -53,7 +53,7 @@ in
         ${pkgs.kexectools}/sbin/kexec -p /run/current-system/kernel \
         --initrd=/run/current-system/initrd \
         --reset-vga --console-vga \
-        --command-line="systemConfig=$(readlink -f /run/current-system) init=$(readlink -f /run/current-system/init) irqpoll maxcpus=1 reset_devices ${kernelParams}"
+        --command-line="init=$(readlink -f /run/current-system/init) irqpoll maxcpus=1 reset_devices ${kernelParams}"
       '';
       kernelParams = [
        "crashkernel=${crashdump.reservedMemory}"
diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix
index 5242ac0535775..f226194efd569 100644
--- a/nixos/modules/module-list.nix
+++ b/nixos/modules/module-list.nix
@@ -80,6 +80,7 @@
   ./hardware/video/displaylink.nix
   ./hardware/video/hidpi.nix
   ./hardware/video/nvidia.nix
+  ./hardware/video/switcheroo-control.nix
   ./hardware/video/uvcvideo/default.nix
   ./hardware/video/webcam/facetimehd.nix
   ./hardware/xpadneo.nix
@@ -206,7 +207,6 @@
   ./security/dhparams.nix
   ./security/duosec.nix
   ./security/google_oslogin.nix
-  ./security/hidepid.nix
   ./security/lock-kernel-modules.nix
   ./security/misc.nix
   ./security/oath.nix
@@ -242,6 +242,7 @@
   ./services/backup/automysqlbackup.nix
   ./services/backup/bacula.nix
   ./services/backup/borgbackup.nix
+  ./services/backup/borgmatic.nix
   ./services/backup/duplicati.nix
   ./services/backup/duplicity.nix
   ./services/backup/mysql-backup.nix
@@ -256,6 +257,8 @@
   ./services/backup/tsm.nix
   ./services/backup/zfs-replication.nix
   ./services/backup/znapzend.nix
+  ./services/blockchain/ethereum/geth.nix
+  ./services/backup/zrepl.nix
   ./services/cluster/hadoop/default.nix
   ./services/cluster/k3s/default.nix
   ./services/cluster/kubernetes/addons/dns.nix
@@ -320,7 +323,8 @@
   ./services/desktops/gsignond.nix
   ./services/desktops/gvfs.nix
   ./services/desktops/malcontent.nix
-  ./services/desktops/pipewire.nix
+  ./services/desktops/pipewire/pipewire.nix
+  ./services/desktops/pipewire/pipewire-media-session.nix
   ./services/desktops/gnome3/at-spi2-core.nix
   ./services/desktops/gnome3/chrome-gnome-shell.nix
   ./services/desktops/gnome3/evolution-data-server.nix
@@ -379,6 +383,7 @@
   ./services/hardware/sane.nix
   ./services/hardware/sane_extra_backends/brscan4.nix
   ./services/hardware/sane_extra_backends/dsseries.nix
+  ./services/hardware/spacenavd.nix
   ./services/hardware/tcsd.nix
   ./services/hardware/tlp.nix
   ./services/hardware/thinkfan.nix
@@ -459,6 +464,7 @@
   ./services/misc/errbot.nix
   ./services/misc/etcd.nix
   ./services/misc/etebase-server.nix
+  ./services/misc/etesync-dav.nix
   ./services/misc/ethminer.nix
   ./services/misc/exhibitor.nix
   ./services/misc/felix.nix
@@ -485,6 +491,7 @@
   ./services/misc/logkeys.nix
   ./services/misc/leaps.nix
   ./services/misc/lidarr.nix
+  ./services/misc/lifecycled.nix
   ./services/misc/mame.nix
   ./services/misc/matrix-appservice-discord.nix
   ./services/misc/matrix-synapse.nix
@@ -507,6 +514,7 @@
   ./services/misc/paperless.nix
   ./services/misc/parsoid.nix
   ./services/misc/plex.nix
+  ./services/misc/plikd.nix
   ./services/misc/tautulli.nix
   ./services/misc/pinnwand.nix
   ./services/misc/pykms.nix
@@ -1046,6 +1054,7 @@
   ./testing/service-runner.nix
   ./virtualisation/anbox.nix
   ./virtualisation/container-config.nix
+  ./virtualisation/containerd.nix
   ./virtualisation/containers.nix
   ./virtualisation/nixos-containers.nix
   ./virtualisation/oci-containers.nix
diff --git a/nixos/modules/profiles/hardened.nix b/nixos/modules/profiles/hardened.nix
index 680fa40b91195..00aafc6831b25 100644
--- a/nixos/modules/profiles/hardened.nix
+++ b/nixos/modules/profiles/hardened.nix
@@ -22,8 +22,6 @@ with lib;
   environment.memoryAllocator.provider = mkDefault "scudo";
   environment.variables.SCUDO_OPTIONS = mkDefault "ZeroContents=1";
 
-  security.hideProcessInformation = mkDefault true;
-
   security.lockKernelModules = mkDefault true;
 
   security.protectKernelImage = mkDefault true;
diff --git a/nixos/modules/programs/fish_completion-generator.patch b/nixos/modules/programs/fish_completion-generator.patch
index 997f38c5066dc..fa207e484c99e 100644
--- a/nixos/modules/programs/fish_completion-generator.patch
+++ b/nixos/modules/programs/fish_completion-generator.patch
@@ -1,13 +1,14 @@
 --- a/create_manpage_completions.py
 +++ b/create_manpage_completions.py
-@@ -844,10 +844,6 @@ def parse_manpage_at_path(manpage_path, output_directory):
+@@ -879,10 +879,6 @@ def parse_manpage_at_path(manpage_path, output_directory):
+                 )
+                 return False
  
-             built_command_output.insert(0, "# " + CMDNAME)
+-        # Output the magic word Autogenerated so we can tell if we can overwrite this
+-        built_command_output.insert(
+-            0, "# " + CMDNAME + "\n# Autogenerated from man page " + manpage_path
+-        )
+         # built_command_output.insert(2, "# using " + parser.__class__.__name__) # XXX MISATTRIBUTES THE CULPABLE PARSER! Was really using Type2 but reporting TypeDeroffManParser
  
--            # Output the magic word Autogenerated so we can tell if we can overwrite this
--            built_command_output.insert(
--                1, "# Autogenerated from man page " + manpage_path
--            )
-             # built_command_output.insert(2, "# using " + parser.__class__.__name__) # XXX MISATTRIBUTES THE CULPABILE PARSER! Was really using Type2 but reporting TypeDeroffManParser
- 
-             for line in built_command_output:
+         for line in built_command_output:
+
diff --git a/nixos/modules/programs/steam.nix b/nixos/modules/programs/steam.nix
index 3c919c47a0c61..ff4deba2bf0ae 100644
--- a/nixos/modules/programs/steam.nix
+++ b/nixos/modules/programs/steam.nix
@@ -4,12 +4,38 @@ with lib;
 
 let
   cfg = config.programs.steam;
+
+  steam = pkgs.steam.override {
+    extraLibraries = pkgs: with config.hardware.opengl;
+      if pkgs.hostPlatform.is64bit
+      then [ package ] ++ extraPackages
+      else [ package32 ] ++ extraPackages32;
+  };
 in {
-  options.programs.steam.enable = mkEnableOption "steam";
+  options.programs.steam = {
+    enable = mkEnableOption "steam";
+
+    remotePlay.openFirewall = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Open ports in the firewall for Steam Remote Play.
+      '';
+    };
+
+    dedicatedServer.openFirewall = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Open ports in the firewall for Source Dedicated Server.
+      '';
+    };
+  };
 
   config = mkIf cfg.enable {
     hardware.opengl = { # this fixes the "glXChooseVisual failed" bug, context: https://github.com/NixOS/nixpkgs/issues/47932
       enable = true;
+      driSupport = true;
       driSupport32Bit = true;
     };
 
@@ -18,7 +44,19 @@ in {
 
     hardware.steam-hardware.enable = true;
 
-    environment.systemPackages = [ pkgs.steam ];
+    environment.systemPackages = [ steam steam.run ];
+
+    networking.firewall = lib.mkMerge [
+      (mkIf cfg.remotePlay.openFirewall {
+        allowedTCPPorts = [ 27036 ];
+        allowedUDPPortRanges = [ { from = 27031; to = 27036; } ];
+      })
+
+      (mkIf cfg.dedicatedServer.openFirewall {
+        allowedTCPPorts = [ 27015 ]; # SRCDS Rcon port
+        allowedUDPPorts = [ 27015 ]; # Gameplay traffic
+      })
+    ];
   };
 
   meta.maintainers = with maintainers; [ mkg20001 ];
diff --git a/nixos/modules/rename.nix b/nixos/modules/rename.nix
index 2d07e421efe45..9f1efc4627984 100644
--- a/nixos/modules/rename.nix
+++ b/nixos/modules/rename.nix
@@ -73,6 +73,11 @@ with lib;
     (mkRemovedOptionModule [ "services" "venus" ] "The corresponding package was removed from nixpkgs.")
     (mkRemovedOptionModule [ "services" "flashpolicyd" ] "The flashpolicyd module has been removed. Adobe Flash Player is deprecated.")
 
+    (mkRemovedOptionModule [ "security" "hideProcessInformation" ] ''
+        The hidepid module was removed, since the underlying machinery
+        is broken when using cgroups-v2.
+    '')
+
     # Do NOT add any option renames here, see top of the file
   ];
 }
diff --git a/nixos/modules/security/hidepid.nix b/nixos/modules/security/hidepid.nix
deleted file mode 100644
index 4953f517e93be..0000000000000
--- a/nixos/modules/security/hidepid.nix
+++ /dev/null
@@ -1,31 +0,0 @@
-{ config, lib, ... }:
-with lib;
-
-{
-  meta = {
-    maintainers = [ maintainers.joachifm ];
-    doc = ./hidepid.xml;
-  };
-
-  options = {
-    security.hideProcessInformation = mkOption {
-      type = types.bool;
-      default = false;
-      description = ''
-        Restrict process information to the owning user.
-      '';
-    };
-  };
-
-  config = mkIf config.security.hideProcessInformation {
-    users.groups.proc.gid = config.ids.gids.proc;
-    users.groups.proc.members = [ "polkituser" ];
-
-    boot.specialFileSystems."/proc".options = [ "hidepid=2" "gid=${toString config.ids.gids.proc}" ];
-    systemd.services.systemd-logind.serviceConfig.SupplementaryGroups = [ "proc" ];
-
-    # Disable cgroupsv2, which doesn't work with hidepid.
-    # https://github.com/NixOS/nixpkgs/pull/104094#issuecomment-729996203
-    systemd.enableUnifiedCgroupHierarchy = false;
-  };
-}
diff --git a/nixos/modules/security/hidepid.xml b/nixos/modules/security/hidepid.xml
deleted file mode 100644
index 5a17cb1da412c..0000000000000
--- a/nixos/modules/security/hidepid.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<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="sec-hidepid">
- <title>Hiding process information</title>
- <para>
-  Setting
-<programlisting>
-<xref linkend="opt-security.hideProcessInformation"/> = true;
-</programlisting>
-  ensures that access to process information is restricted to the owning user.
-  This implies, among other things, that command-line arguments remain private.
-  Unless your deployment relies on unprivileged users being able to inspect the
-  process information of other users, this option should be safe to enable.
- </para>
- <para>
-  Members of the <literal>proc</literal> group are exempt from process
-  information hiding.
- </para>
- <para>
-  To allow a service <replaceable>foo</replaceable> to run without process
-  information hiding, set
-<programlisting>
-<link linkend="opt-systemd.services._name_.serviceConfig">systemd.services.<replaceable>foo</replaceable>.serviceConfig</link>.SupplementaryGroups = [ "proc" ];
-</programlisting>
- </para>
-</chapter>
diff --git a/nixos/modules/security/rngd.nix b/nixos/modules/security/rngd.nix
index cb885c4762d06..8cca1c26d683a 100644
--- a/nixos/modules/security/rngd.nix
+++ b/nixos/modules/security/rngd.nix
@@ -1,56 +1,16 @@
-{ config, lib, pkgs, ... }:
-
-with lib;
-
+{ lib, ... }:
 let
-  cfg = config.security.rngd;
+  removed = k: lib.mkRemovedOptionModule [ "security" "rngd" k ];
 in
 {
-  options = {
-    security.rngd = {
-      enable = mkOption {
-        type = types.bool;
-        default = false;
-        description = ''
-          Whether to enable the rng daemon.  Devices that the kernel recognises
-          as entropy sources are handled automatically by krngd.
-        '';
-      };
-      debug = mkOption {
-        type = types.bool;
-        default = false;
-        description = "Whether to enable debug output (-d).";
-      };
-    };
-  };
-
-  config = mkIf cfg.enable {
-    systemd.services.rngd = {
-      bindsTo = [ "dev-random.device" ];
-
-      after = [ "dev-random.device" ];
-
-      # Clean shutdown without DefaultDependencies
-      conflicts = [ "shutdown.target" ];
-      before = [
-        "sysinit.target"
-        "shutdown.target"
-      ];
-
-      description = "Hardware RNG Entropy Gatherer Daemon";
-
-      # rngd may have to start early to avoid entropy starvation during boot with encrypted swap
-      unitConfig.DefaultDependencies = false;
-      serviceConfig = {
-        ExecStart = "${pkgs.rng-tools}/sbin/rngd -f"
-          + optionalString cfg.debug " -d";
-        # PrivateTmp would introduce a circular dependency if /tmp is on tmpfs and swap is encrypted,
-        # thus depending on rngd before swap, while swap depends on rngd to avoid entropy starvation.
-        NoNewPrivileges = true;
-        PrivateNetwork = true;
-        ProtectSystem = "full";
-        ProtectHome = true;
-      };
-    };
-  };
+  imports = [
+    (removed "enable" ''
+       rngd is not necessary for any device that the kernel recognises
+       as an hardware RNG, as it will automatically run the krngd task
+       to periodically collect random data from the device and mix it
+       into the kernel's RNG.
+    '')
+    (removed "debug"
+      "The rngd module was removed, so its debug option does nothing.")
+  ];
 }
diff --git a/nixos/modules/services/audio/spotifyd.nix b/nixos/modules/services/audio/spotifyd.nix
index a589153248fe9..9279a03aed4e5 100644
--- a/nixos/modules/services/audio/spotifyd.nix
+++ b/nixos/modules/services/audio/spotifyd.nix
@@ -27,6 +27,7 @@ in
       wantedBy = [ "multi-user.target" ];
       after = [ "network-online.target" "sound.target" ];
       description = "spotifyd, a Spotify playing daemon";
+      environment.SHELL = "/bin/sh";
       serviceConfig = {
         ExecStart = "${pkgs.spotifyd}/bin/spotifyd --no-daemon --cache-path /var/cache/spotifyd --config-path ${spotifydConf}";
         Restart = "always";
diff --git a/nixos/modules/services/backup/borgmatic.nix b/nixos/modules/services/backup/borgmatic.nix
new file mode 100644
index 0000000000000..5e5c0bbecccaa
--- /dev/null
+++ b/nixos/modules/services/backup/borgmatic.nix
@@ -0,0 +1,57 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.borgmatic;
+  cfgfile = pkgs.writeText "config.yaml" (builtins.toJSON cfg.settings);
+in {
+  options.services.borgmatic = {
+    enable = mkEnableOption "borgmatic";
+
+    settings = mkOption {
+      description = ''
+        See https://torsion.org/borgmatic/docs/reference/configuration/
+      '';
+      type = types.submodule {
+        freeformType = with lib.types; attrsOf anything;
+        options.location = {
+          source_directories = mkOption {
+            type = types.listOf types.str;
+            description = ''
+              List of source directories to backup (required). Globs and
+              tildes are expanded.
+            '';
+            example = [ "/home" "/etc" "/var/log/syslog*" ];
+          };
+          repositories = mkOption {
+            type = types.listOf types.str;
+            description = ''
+              Paths to local or remote repositories (required). Tildes are
+              expanded. Multiple repositories are backed up to in
+              sequence. Borg placeholders can be used. See the output of
+              "borg help placeholders" for details. See ssh_command for
+              SSH options like identity file or port. If systemd service
+              is used, then add local repository paths in the systemd
+              service file to the ReadWritePaths list.
+            '';
+            example = [
+              "user@backupserver:sourcehostname.borg"
+              "user@backupserver:{fqdn}"
+            ];
+          };
+        };
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+
+    environment.systemPackages = [ pkgs.borgmatic ];
+
+    environment.etc."borgmatic/config.yaml".source = cfgfile;
+
+    systemd.packages = [ pkgs.borgmatic ];
+
+  };
+}
diff --git a/nixos/modules/services/backup/zrepl.nix b/nixos/modules/services/backup/zrepl.nix
new file mode 100644
index 0000000000000..4356479b6635e
--- /dev/null
+++ b/nixos/modules/services/backup/zrepl.nix
@@ -0,0 +1,54 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+let
+  cfg = config.services.zrepl;
+  format = pkgs.formats.yaml { };
+  configFile = format.generate "zrepl.yml" cfg.settings;
+in
+{
+  meta.maintainers = with maintainers; [ cole-h ];
+
+  options = {
+    services.zrepl = {
+      enable = mkEnableOption "zrepl";
+
+      settings = mkOption {
+        default = { };
+        description = ''
+          Configuration for zrepl. See <link
+          xlink:href="https://zrepl.github.io/configuration.html"/>
+          for more information.
+        '';
+        type = types.submodule {
+          freeformType = format.type;
+        };
+      };
+    };
+  };
+
+  ### Implementation ###
+
+  config = mkIf cfg.enable {
+    environment.systemPackages = [ pkgs.zrepl ];
+
+    # zrepl looks for its config in this location by default. This
+    # allows the use of e.g. `zrepl signal wakeup <job>` without having
+    # to specify the storepath of the config.
+    environment.etc."zrepl/zrepl.yml".source = configFile;
+
+    systemd.packages = [ pkgs.zrepl ];
+    systemd.services.zrepl = {
+      requires = [ "local-fs.target" ];
+      wantedBy = [ "zfs.target" ];
+      after = [ "zfs.target" ];
+
+      path = [ config.boot.zfs.package ];
+      restartTriggers = [ configFile ];
+
+      serviceConfig = {
+        Restart = "on-failure";
+      };
+    };
+  };
+}
diff --git a/nixos/modules/services/blockchain/ethereum/geth.nix b/nixos/modules/services/blockchain/ethereum/geth.nix
new file mode 100644
index 0000000000000..be3f40f6bd866
--- /dev/null
+++ b/nixos/modules/services/blockchain/ethereum/geth.nix
@@ -0,0 +1,178 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  eachGeth = config.services.geth;
+
+  gethOpts = { config, lib, name, ...}: {
+
+    options = {
+
+      enable = lib.mkEnableOption "Go Ethereum Node";
+
+      port = mkOption {
+        type = types.port;
+        default = 30303;
+        description = "Port number Go Ethereum will be listening on, both TCP and UDP.";
+      };
+
+      http = {
+        enable = lib.mkEnableOption "Go Ethereum HTTP API";
+        address = mkOption {
+          type = types.str;
+          default = "127.0.0.1";
+          description = "Listen address of Go Ethereum HTTP API.";
+        };
+
+        port = mkOption {
+          type = types.port;
+          default = 8545;
+          description = "Port number of Go Ethereum HTTP API.";
+        };
+
+        apis = mkOption {
+          type = types.nullOr (types.listOf types.str);
+          default = null;
+          description = "APIs to enable over WebSocket";
+          example = ["net" "eth"];
+        };
+      };
+
+      websocket = {
+        enable = lib.mkEnableOption "Go Ethereum WebSocket API";
+        address = mkOption {
+          type = types.str;
+          default = "127.0.0.1";
+          description = "Listen address of Go Ethereum WebSocket API.";
+        };
+
+        port = mkOption {
+          type = types.port;
+          default = 8546;
+          description = "Port number of Go Ethereum WebSocket API.";
+        };
+
+        apis = mkOption {
+          type = types.nullOr (types.listOf types.str);
+          default = null;
+          description = "APIs to enable over WebSocket";
+          example = ["net" "eth"];
+        };
+      };
+
+      metrics = {
+        enable = lib.mkEnableOption "Go Ethereum prometheus metrics";
+        address = mkOption {
+          type = types.str;
+          default = "127.0.0.1";
+          description = "Listen address of Go Ethereum metrics service.";
+        };
+
+        port = mkOption {
+          type = types.port;
+          default = 6060;
+          description = "Port number of Go Ethereum metrics service.";
+        };
+      };
+
+      network = mkOption {
+        type = types.nullOr (types.enum [ "goerli" "rinkeby" "yolov2" "ropsten" ]);
+        default = null;
+        description = "The network to connect to. Mainnet (null) is the default ethereum network.";
+      };
+
+      syncmode = mkOption {
+        type = types.enum [ "fast" "full" "light" ];
+        default = "fast";
+        description = "Blockchain sync mode.";
+      };
+
+      gcmode = mkOption {
+        type = types.enum [ "full" "archive" ];
+        default = "full";
+        description = "Blockchain garbage collection mode.";
+      };
+
+      maxpeers = mkOption {
+        type = types.int;
+        default = 50;
+        description = "Maximum peers to connect to.";
+      };
+
+      extraArgs = mkOption {
+        type = types.listOf types.str;
+        description = "Additional arguments passed to Go Ethereum.";
+        default = [];
+      };
+
+      package = mkOption {
+        default = pkgs.go-ethereum.geth;
+        type = types.package;
+        description = "Package to use as Go Ethereum node.";
+      };
+    };
+  };
+in
+
+{
+
+  ###### interface
+
+  options = {
+    services.geth = mkOption {
+      type = types.attrsOf (types.submodule gethOpts);
+      default = {};
+      description = "Specification of one or more geth instances.";
+    };
+  };
+
+  ###### implementation
+
+  config = mkIf (eachGeth != {}) {
+
+    environment.systemPackages = flatten (mapAttrsToList (gethName: cfg: [
+      cfg.package
+    ]) eachGeth);
+
+    systemd.services = mapAttrs' (gethName: cfg: (
+      nameValuePair "geth-${gethName}" (mkIf cfg.enable {
+      description = "Go Ethereum node (${gethName})";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+
+      serviceConfig = {
+        DynamicUser = true;
+        Restart = "always";
+        StateDirectory = "goethereum/${gethName}/${if (cfg.network == null) then "mainnet" else cfg.network}";
+
+        # Hardening measures
+        PrivateTmp = "true";
+        ProtectSystem = "full";
+        NoNewPrivileges = "true";
+        PrivateDevices = "true";
+        MemoryDenyWriteExecute = "true";
+      };
+
+      script = ''
+        ${cfg.package}/bin/geth \
+          --nousb \
+          --ipcdisable \
+          ${optionalString (cfg.network != null) ''--${cfg.network}''} \
+          --syncmode ${cfg.syncmode} \
+          --gcmode ${cfg.gcmode} \
+          --port ${toString cfg.port} \
+          --maxpeers ${toString cfg.maxpeers} \
+          ${if cfg.http.enable then ''--http --http.addr ${cfg.http.address} --http.port ${toString cfg.http.port}'' else ""} \
+          ${optionalString (cfg.http.apis != null) ''--http.api ${lib.concatStringsSep "," cfg.http.apis}''} \
+          ${if cfg.websocket.enable then ''--ws --ws.addr ${cfg.websocket.address} --ws.port ${toString cfg.websocket.port}'' else ""} \
+          ${optionalString (cfg.websocket.apis != null) ''--ws.api ${lib.concatStringsSep "," cfg.websocket.apis}''} \
+          ${optionalString cfg.metrics.enable ''--metrics --metrics.addr ${cfg.metrics.address} --metrics.port ${toString cfg.metrics.port}''} \
+          ${lib.escapeShellArgs cfg.extraArgs} \
+          --datadir /var/lib/goethereum/${gethName}/${if (cfg.network == null) then "mainnet" else cfg.network}
+      '';
+    }))) eachGeth;
+
+  };
+
+}
diff --git a/nixos/modules/services/cluster/kubernetes/addons/dns.nix b/nixos/modules/services/cluster/kubernetes/addons/dns.nix
index f12e866930dab..24d86628b211d 100644
--- a/nixos/modules/services/cluster/kubernetes/addons/dns.nix
+++ b/nixos/modules/services/cluster/kubernetes/addons/dns.nix
@@ -3,7 +3,7 @@
 with lib;
 
 let
-  version = "1.6.4";
+  version = "1.7.1";
   cfg = config.services.kubernetes.addons.dns;
   ports = {
     dns = 10053;
@@ -55,9 +55,9 @@ in {
       type = types.attrs;
       default = {
         imageName = "coredns/coredns";
-        imageDigest = "sha256:493ee88e1a92abebac67cbd4b5658b4730e0f33512461442d8d9214ea6734a9b";
+        imageDigest = "sha256:4a6e0769130686518325b21b0c1d0688b54e7c79244d48e1b15634e98e40c6ef";
         finalImageTag = version;
-        sha256 = "0fm9zdjavpf5hni8g7fkdd3csjbhd7n7py7llxjc66sbii087028";
+        sha256 = "02r440xcdsgi137k5lmmvp0z5w5fmk8g9mysq5pnysq1wl8sj6mw";
       };
     };
   };
@@ -156,7 +156,6 @@ in {
             health :${toString ports.health}
             kubernetes ${cfg.clusterDomain} in-addr.arpa ip6.arpa {
               pods insecure
-              upstream
               fallthrough in-addr.arpa ip6.arpa
             }
             prometheus :${toString ports.metrics}
diff --git a/nixos/modules/services/cluster/kubernetes/apiserver.nix b/nixos/modules/services/cluster/kubernetes/apiserver.nix
index 95bdb4c0d14e4..a5b1321547668 100644
--- a/nixos/modules/services/cluster/kubernetes/apiserver.nix
+++ b/nixos/modules/services/cluster/kubernetes/apiserver.nix
@@ -238,14 +238,40 @@ in
       type = int;
     };
 
+    apiAudiences = mkOption {
+      description = ''
+        Kubernetes apiserver ServiceAccount issuer.
+      '';
+      default = "api,https://kubernetes.default.svc";
+      type = str;
+    };
+
+    serviceAccountIssuer = mkOption {
+      description = ''
+        Kubernetes apiserver ServiceAccount issuer.
+      '';
+      default = "https://kubernetes.default.svc";
+      type = str;
+    };
+
+    serviceAccountSigningKeyFile = mkOption {
+      description = ''
+        Path to the file that contains the current private key of the service
+        account token issuer. The issuer will sign issued ID tokens with this
+        private key.
+      '';
+      type = path;
+    };
+
     serviceAccountKeyFile = mkOption {
       description = ''
-        Kubernetes apiserver PEM-encoded x509 RSA private or public key file,
-        used to verify ServiceAccount tokens. By default tls private key file
-        is used.
+        File containing PEM-encoded x509 RSA or ECDSA private or public keys,
+        used to verify ServiceAccount tokens. The specified file can contain
+        multiple keys, and the flag can be specified multiple times with
+        different files. If unspecified, --tls-private-key-file is used.
+        Must be specified when --service-account-signing-key is provided
       '';
-      default = null;
-      type = nullOr path;
+      type = path;
     };
 
     serviceClusterIpRange = mkOption {
@@ -357,8 +383,10 @@ in
               ${optionalString (cfg.runtimeConfig != "")
                 "--runtime-config=${cfg.runtimeConfig}"} \
               --secure-port=${toString cfg.securePort} \
-              ${optionalString (cfg.serviceAccountKeyFile!=null)
-                "--service-account-key-file=${cfg.serviceAccountKeyFile}"} \
+              --api-audiences=${toString cfg.apiAudiences} \
+              --service-account-issuer=${toString cfg.serviceAccountIssuer} \
+              --service-account-signing-key-file=${cfg.serviceAccountSigningKeyFile} \
+              --service-account-key-file=${cfg.serviceAccountKeyFile} \
               --service-cluster-ip-range=${cfg.serviceClusterIpRange} \
               --storage-backend=${cfg.storageBackend} \
               ${optionalString (cfg.tlsCertFile != null)
diff --git a/nixos/modules/services/cluster/kubernetes/default.nix b/nixos/modules/services/cluster/kubernetes/default.nix
index 3a11a6513a491..19edc338bba1b 100644
--- a/nixos/modules/services/cluster/kubernetes/default.nix
+++ b/nixos/modules/services/cluster/kubernetes/default.nix
@@ -5,6 +5,29 @@ with lib;
 let
   cfg = config.services.kubernetes;
 
+  defaultContainerdConfigFile = pkgs.writeText "containerd.toml" ''
+    version = 2
+    root = "/var/lib/containerd/daemon"
+    state = "/var/run/containerd/daemon"
+    oom_score = 0
+
+    [grpc]
+      address = "/var/run/containerd/containerd.sock"
+
+    [plugins."io.containerd.grpc.v1.cri"]
+      sandbox_image = "pause:latest"
+
+    [plugins."io.containerd.grpc.v1.cri".cni]
+      bin_dir = "/opt/cni/bin"
+      max_conf_num = 0
+
+    [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc]
+      runtime_type = "io.containerd.runc.v2"
+
+    [plugins."io.containerd.grpc.v1.cri".containerd.runtimes."io.containerd.runc.v2".options]
+      SystemdCgroup = true
+  '';
+
   mkKubeConfig = name: conf: pkgs.writeText "${name}-kubeconfig" (builtins.toJSON {
     apiVersion = "v1";
     kind = "Config";
@@ -222,14 +245,9 @@ in {
     })
 
     (mkIf cfg.kubelet.enable {
-      virtualisation.docker = {
+      virtualisation.containerd = {
         enable = mkDefault true;
-
-        # kubernetes needs access to logs
-        logDriver = mkDefault "json-file";
-
-        # iptables must be disabled for kubernetes
-        extraOptions = "--iptables=false --ip-masq=false";
+        configFile = mkDefault defaultContainerdConfigFile;
       };
     })
 
@@ -269,7 +287,6 @@ in {
       users.users.kubernetes = {
         uid = config.ids.uids.kubernetes;
         description = "Kubernetes user";
-        extraGroups = [ "docker" ];
         group = "kubernetes";
         home = cfg.dataDir;
         createHome = true;
diff --git a/nixos/modules/services/cluster/kubernetes/flannel.nix b/nixos/modules/services/cluster/kubernetes/flannel.nix
index 548ffed1ddb58..3f55719027f05 100644
--- a/nixos/modules/services/cluster/kubernetes/flannel.nix
+++ b/nixos/modules/services/cluster/kubernetes/flannel.nix
@@ -8,16 +8,6 @@ let
 
   # we want flannel to use kubernetes itself as configuration backend, not direct etcd
   storageBackend = "kubernetes";
-
-  # needed for flannel to pass options to docker
-  mkDockerOpts = pkgs.runCommand "mk-docker-opts" {
-    buildInputs = [ pkgs.makeWrapper ];
-  } ''
-    mkdir -p $out
-
-    # bashInteractive needed for `compgen`
-    makeWrapper ${pkgs.bashInteractive}/bin/bash $out/mk-docker-opts --add-flags "${pkgs.kubernetes}/bin/mk-docker-opts.sh"
-  '';
 in
 {
   ###### interface
@@ -43,43 +33,17 @@ in
         cniVersion = "0.3.1";
         delegate = {
           isDefaultGateway = true;
-          bridge = "docker0";
+          bridge = "mynet";
         };
       }];
     };
 
-    systemd.services.mk-docker-opts = {
-      description = "Pre-Docker Actions";
-      path = with pkgs; [ gawk gnugrep ];
-      script = ''
-        ${mkDockerOpts}/mk-docker-opts -d /run/flannel/docker
-        systemctl restart docker
-      '';
-      serviceConfig.Type = "oneshot";
-    };
-
-    systemd.paths.flannel-subnet-env = {
-      wantedBy = [ "flannel.service" ];
-      pathConfig = {
-        PathModified = "/run/flannel/subnet.env";
-        Unit = "mk-docker-opts.service";
-      };
-    };
-
-    systemd.services.docker = {
-      environment.DOCKER_OPTS = "-b none";
-      serviceConfig.EnvironmentFile = "-/run/flannel/docker";
-    };
-
-    # read environment variables generated by mk-docker-opts
-    virtualisation.docker.extraOptions = "$DOCKER_OPTS";
-
     networking = {
       firewall.allowedUDPPorts = [
         8285  # flannel udp
         8472  # flannel vxlan
       ];
-      dhcpcd.denyInterfaces = [ "docker*" "flannel*" ];
+      dhcpcd.denyInterfaces = [ "mynet*" "flannel*" ];
     };
 
     services.kubernetes.pki.certs = {
diff --git a/nixos/modules/services/cluster/kubernetes/kubelet.nix b/nixos/modules/services/cluster/kubernetes/kubelet.nix
index 479027f1b2708..ef6da26a024c6 100644
--- a/nixos/modules/services/cluster/kubernetes/kubelet.nix
+++ b/nixos/modules/services/cluster/kubernetes/kubelet.nix
@@ -23,7 +23,7 @@ let
     name = "pause";
     tag = "latest";
     contents = top.package.pause;
-    config.Cmd = "/bin/pause";
+    config.Cmd = ["/bin/pause"];
   };
 
   kubeconfig = top.lib.mkKubeConfig "kubelet" cfg.kubeconfig;
@@ -125,6 +125,18 @@ in
       };
     };
 
+    containerRuntime = mkOption {
+      description = "Which container runtime type to use";
+      type = enum ["docker" "remote"];
+      default = "remote";
+    };
+
+    containerRuntimeEndpoint = mkOption {
+      description = "Endpoint at which to find the container runtime api interface/socket";
+      type = str;
+      default = "unix:///var/run/containerd/containerd.sock";
+    };
+
     enable = mkEnableOption "Kubernetes kubelet.";
 
     extraOpts = mkOption {
@@ -235,16 +247,24 @@ in
   ###### implementation
   config = mkMerge [
     (mkIf cfg.enable {
+
+      environment.etc."cni/net.d".source = cniConfig;
+
       services.kubernetes.kubelet.seedDockerImages = [infraContainer];
 
+      boot.kernel.sysctl = {
+        "net.bridge.bridge-nf-call-iptables"  = 1;
+        "net.ipv4.ip_forward"                 = 1;
+        "net.bridge.bridge-nf-call-ip6tables" = 1;
+      };
+
       systemd.services.kubelet = {
         description = "Kubernetes Kubelet Service";
         wantedBy = [ "kubernetes.target" ];
-        after = [ "network.target" "docker.service" "kube-apiserver.service" ];
+        after = [ "containerd.service" "network.target" "kube-apiserver.service" ];
         path = with pkgs; [
           gitMinimal
           openssh
-          docker
           util-linux
           iproute
           ethtool
@@ -254,8 +274,12 @@ in
         ] ++ lib.optional config.boot.zfs.enabled config.boot.zfs.package ++ top.path;
         preStart = ''
           ${concatMapStrings (img: ''
-            echo "Seeding docker image: ${img}"
-            docker load <${img}
+            echo "Seeding container image: ${img}"
+            ${if (lib.hasSuffix "gz" img) then
+              ''${pkgs.gzip}/bin/zcat "${img}" | ${pkgs.containerd}/bin/ctr -n k8s.io image import -''
+            else
+              ''${pkgs.coreutils}/bin/cat "${img}" | ${pkgs.containerd}/bin/ctr -n k8s.io image import -''
+            }
           '') cfg.seedDockerImages}
 
           rm /opt/cni/bin/* || true
@@ -306,6 +330,9 @@ in
             ${optionalString (cfg.tlsKeyFile != null)
               "--tls-private-key-file=${cfg.tlsKeyFile}"} \
             ${optionalString (cfg.verbosity != null) "--v=${toString cfg.verbosity}"} \
+            --container-runtime=${cfg.containerRuntime} \
+            --container-runtime-endpoint=${cfg.containerRuntimeEndpoint} \
+            --cgroup-driver=systemd \
             ${cfg.extraOpts}
           '';
           WorkingDirectory = top.dataDir;
@@ -315,7 +342,7 @@ in
       # Allways include cni plugins
       services.kubernetes.kubelet.cni.packages = [pkgs.cni-plugins];
 
-      boot.kernelModules = ["br_netfilter"];
+      boot.kernelModules = ["br_netfilter" "overlay"];
 
       services.kubernetes.kubelet.hostname = with config.networking;
         mkDefault (hostName + optionalString (domain != null) ".${domain}");
diff --git a/nixos/modules/services/cluster/kubernetes/pki.nix b/nixos/modules/services/cluster/kubernetes/pki.nix
index 933ae481e9684..8de6a3ba0d80f 100644
--- a/nixos/modules/services/cluster/kubernetes/pki.nix
+++ b/nixos/modules/services/cluster/kubernetes/pki.nix
@@ -361,6 +361,7 @@ in
           tlsCertFile = mkDefault cert;
           tlsKeyFile = mkDefault key;
           serviceAccountKeyFile = mkDefault cfg.certs.serviceAccount.cert;
+          serviceAccountSigningKeyFile = mkDefault cfg.certs.serviceAccount.key;
           kubeletClientCaFile = mkDefault caCert;
           kubeletClientCertFile = mkDefault cfg.certs.apiserverKubeletClient.cert;
           kubeletClientKeyFile = mkDefault cfg.certs.apiserverKubeletClient.key;
diff --git a/nixos/modules/services/continuous-integration/hydra/default.nix b/nixos/modules/services/continuous-integration/hydra/default.nix
index 887a0cbf9a7ab..2206ac522e4a6 100644
--- a/nixos/modules/services/continuous-integration/hydra/default.nix
+++ b/nixos/modules/services/continuous-integration/hydra/default.nix
@@ -89,6 +89,11 @@ in
         example = "dbi:Pg:dbname=hydra;host=postgres.example.org;user=foo;";
         description = ''
           The DBI string for Hydra database connection.
+
+          NOTE: Attempts to set `application_name` will be overridden by
+          `hydra-TYPE` (where TYPE is e.g. `evaluator`, `queue-runner`,
+          etc.) in all hydra services to more easily distinguish where
+          queries are coming from.
         '';
       };
 
@@ -284,7 +289,9 @@ in
       { wantedBy = [ "multi-user.target" ];
         requires = optional haveLocalDB "postgresql.service";
         after = optional haveLocalDB "postgresql.service";
-        environment = env;
+        environment = env // {
+          HYDRA_DBI = "${env.HYDRA_DBI};application_name=hydra-init";
+        };
         preStart = ''
           mkdir -p ${baseDir}
           chown hydra.hydra ${baseDir}
@@ -339,7 +346,9 @@ in
       { wantedBy = [ "multi-user.target" ];
         requires = [ "hydra-init.service" ];
         after = [ "hydra-init.service" ];
-        environment = serverEnv;
+        environment = serverEnv // {
+          HYDRA_DBI = "${serverEnv.HYDRA_DBI};application_name=hydra-server";
+        };
         restartTriggers = [ hydraConf ];
         serviceConfig =
           { ExecStart =
@@ -361,6 +370,7 @@ in
         environment = env // {
           PGPASSFILE = "${baseDir}/pgpass-queue-runner"; # grrr
           IN_SYSTEMD = "1"; # to get log severity levels
+          HYDRA_DBI = "${env.HYDRA_DBI};application_name=hydra-queue-runner";
         };
         serviceConfig =
           { ExecStart = "@${hydra-package}/bin/hydra-queue-runner hydra-queue-runner -v";
@@ -380,7 +390,9 @@ in
         after = [ "hydra-init.service" "network.target" ];
         path = with pkgs; [ hydra-package nettools jq ];
         restartTriggers = [ hydraConf ];
-        environment = env;
+        environment = env // {
+          HYDRA_DBI = "${env.HYDRA_DBI};application_name=hydra-evaluator";
+        };
         serviceConfig =
           { ExecStart = "@${hydra-package}/bin/hydra-evaluator hydra-evaluator";
             User = "hydra";
@@ -392,7 +404,9 @@ in
     systemd.services.hydra-update-gc-roots =
       { requires = [ "hydra-init.service" ];
         after = [ "hydra-init.service" ];
-        environment = env;
+        environment = env // {
+          HYDRA_DBI = "${env.HYDRA_DBI};application_name=hydra-update-gc-roots";
+        };
         serviceConfig =
           { ExecStart = "@${hydra-package}/bin/hydra-update-gc-roots hydra-update-gc-roots";
             User = "hydra";
@@ -403,7 +417,9 @@ in
     systemd.services.hydra-send-stats =
       { wantedBy = [ "multi-user.target" ];
         after = [ "hydra-init.service" ];
-        environment = env;
+        environment = env // {
+          HYDRA_DBI = "${env.HYDRA_DBI};application_name=hydra-send-stats";
+        };
         serviceConfig =
           { ExecStart = "@${hydra-package}/bin/hydra-send-stats hydra-send-stats";
             User = "hydra";
@@ -417,6 +433,7 @@ in
         restartTriggers = [ hydraConf ];
         environment = env // {
           PGPASSFILE = "${baseDir}/pgpass-queue-runner";
+          HYDRA_DBI = "${env.HYDRA_DBI};application_name=hydra-notify";
         };
         serviceConfig =
           { ExecStart = "@${hydra-package}/bin/hydra-notify hydra-notify";
diff --git a/nixos/modules/services/databases/mysql.nix b/nixos/modules/services/databases/mysql.nix
index 7d0a3f9afc48c..cf105daeb04ed 100644
--- a/nixos/modules/services/databases/mysql.nix
+++ b/nixos/modules/services/databases/mysql.nix
@@ -375,6 +375,18 @@ in
           fi
         '';
 
+        script = ''
+          # https://mariadb.com/kb/en/getting-started-with-mariadb-galera-cluster/#systemd-and-galera-recovery
+          if test -n "''${_WSREP_START_POSITION}"; then
+            if test -e "${cfg.package}/bin/galera_recovery"; then
+              VAR=$(cd ${cfg.package}/bin/..; ${cfg.package}/bin/galera_recovery); [[ $? -eq 0 ]] && export _WSREP_START_POSITION=$VAR || exit 1
+            fi
+          fi
+
+          # The last two environment variables are used for starting Galera clusters
+          exec ${cfg.package}/bin/mysqld --defaults-file=/etc/my.cnf ${mysqldOptions} $_WSREP_NEW_CLUSTER $_WSREP_START_POSITION
+        '';
+
         postStart = let
           # The super user account to use on *first* run of MySQL server
           superUser = if isMariaDB then cfg.user else "root";
@@ -481,8 +493,7 @@ in
           Type = if hasNotify then "notify" else "simple";
           Restart = "on-abort";
           RestartSec = "5s";
-          # The last two environment variables are used for starting Galera clusters
-          ExecStart = "${cfg.package}/bin/mysqld --defaults-file=/etc/my.cnf ${mysqldOptions} $_WSREP_NEW_CLUSTER $_WSREP_START_POSITION";
+
           # User and group
           User = cfg.user;
           Group = cfg.group;
diff --git a/nixos/modules/services/desktops/pipewire.nix b/nixos/modules/services/desktops/pipewire.nix
deleted file mode 100644
index 134becf6b0c40..0000000000000
--- a/nixos/modules/services/desktops/pipewire.nix
+++ /dev/null
@@ -1,183 +0,0 @@
-# pipewire service.
-{ config, lib, pkgs, ... }:
-
-with lib;
-
-let
-  cfg = config.services.pipewire;
-  enable32BitAlsaPlugins = cfg.alsa.support32Bit
-                           && pkgs.stdenv.isx86_64
-                           && pkgs.pkgsi686Linux.pipewire != null;
-
-  # The package doesn't output to $out/lib/pipewire directly so that the
-  # overlays can use the outputs to replace the originals in FHS environments.
-  #
-  # This doesn't work in general because of missing development information.
-  jack-libs = pkgs.runCommand "jack-libs" {} ''
-    mkdir -p "$out/lib"
-    ln -s "${cfg.package.jack}/lib" "$out/lib/pipewire"
-  '';
-in {
-
-  meta = {
-    maintainers = teams.freedesktop.members;
-  };
-
-  ###### interface
-  options = {
-    services.pipewire = {
-      enable = mkEnableOption "pipewire service";
-
-      package = mkOption {
-        type = types.package;
-        default = pkgs.pipewire;
-        defaultText = "pkgs.pipewire";
-        example = literalExample "pkgs.pipewire";
-        description = ''
-          The pipewire derivation to use.
-        '';
-      };
-
-      socketActivation = mkOption {
-        default = true;
-        type = types.bool;
-        description = ''
-          Automatically run pipewire when connections are made to the pipewire socket.
-        '';
-      };
-
-      extraConfig = mkOption {
-        type = types.lines;
-        default = "";
-        description = ''
-          Literal string to append to /etc/pipewire/pipewire.conf.
-        '';
-      };
-
-      sessionManager = mkOption {
-        type = types.nullOr types.string;
-        default = null;
-        example = literalExample ''"''${pipewire}/bin/pipewire-media-session"'';
-        description = ''
-          Path to the pipewire session manager executable.
-        '';
-      };
-
-      sessionManagerArguments = mkOption {
-        type = types.listOf types.string;
-        default = [];
-        example = literalExample ''[ "-p" "bluez5.msbc-support=true" ]'';
-        description = ''
-          Arguments passed to the pipewire session manager.
-        '';
-      };
-
-      alsa = {
-        enable = mkEnableOption "ALSA support";
-        support32Bit = mkEnableOption "32-bit ALSA support on 64-bit systems";
-      };
-
-      jack = {
-        enable = mkEnableOption "JACK audio emulation";
-      };
-
-      pulse = {
-        enable = mkEnableOption "PulseAudio server emulation";
-      };
-    };
-  };
-
-
-  ###### implementation
-  config = mkIf cfg.enable {
-    assertions = [
-      {
-        assertion = cfg.pulse.enable -> !config.hardware.pulseaudio.enable;
-        message = "PipeWire based PulseAudio server emulation replaces PulseAudio. This option requires `hardware.pulseaudio.enable` to be set to false";
-      }
-      {
-        assertion = cfg.jack.enable -> !config.services.jack.jackd.enable;
-        message = "PipeWire based JACK emulation doesn't use the JACK service. This option requires `services.jack.jackd.enable` to be set to false";
-      }
-    ];
-
-    services.pipewire.sessionManager = mkDefault "${cfg.package}/bin/pipewire-media-session";
-
-    environment.systemPackages = [ cfg.package ]
-                                 ++ lib.optional cfg.jack.enable jack-libs;
-
-    systemd.packages = [ cfg.package ]
-                       ++ lib.optional cfg.pulse.enable cfg.package.pulse;
-
-    # PipeWire depends on DBUS but doesn't list it. Without this booting
-    # into a terminal results in the service crashing with an error.
-    systemd.user.sockets.pipewire.wantedBy = lib.mkIf cfg.socketActivation [ "sockets.target" ];
-    systemd.user.sockets.pipewire-pulse.wantedBy = lib.mkIf (cfg.socketActivation && cfg.pulse.enable) ["sockets.target"];
-    systemd.user.services.pipewire.bindsTo = [ "dbus.service" ];
-    services.udev.packages = [ cfg.package ];
-
-    # If any paths are updated here they must also be updated in the package test.
-    environment.etc."alsa/conf.d/49-pipewire-modules.conf" = mkIf cfg.alsa.enable {
-      text = ''
-        pcm_type.pipewire {
-          libs.native = ${cfg.package.lib}/lib/alsa-lib/libasound_module_pcm_pipewire.so ;
-          ${optionalString enable32BitAlsaPlugins
-            "libs.32Bit = ${pkgs.pkgsi686Linux.pipewire.lib}/lib/alsa-lib/libasound_module_pcm_pipewire.so ;"}
-        }
-        ctl_type.pipewire {
-          libs.native = ${cfg.package.lib}/lib/alsa-lib/libasound_module_ctl_pipewire.so ;
-          ${optionalString enable32BitAlsaPlugins
-            "libs.32Bit = ${pkgs.pkgsi686Linux.pipewire.lib}/lib/alsa-lib/libasound_module_ctl_pipewire.so ;"}
-        }
-      '';
-    };
-    environment.etc."alsa/conf.d/50-pipewire.conf" = mkIf cfg.alsa.enable {
-      source = "${cfg.package}/share/alsa/alsa.conf.d/50-pipewire.conf";
-    };
-    environment.etc."alsa/conf.d/99-pipewire-default.conf" = mkIf cfg.alsa.enable {
-      source = "${cfg.package}/share/alsa/alsa.conf.d/99-pipewire-default.conf";
-    };
-    environment.sessionVariables.LD_LIBRARY_PATH =
-      lib.optional cfg.jack.enable "/run/current-system/sw/lib/pipewire";
-
-    environment.etc."pipewire/pipewire.conf" = {
-      # Adapted from src/daemon/pipewire.conf.in
-      text = ''
-        set-prop link.max-buffers 16 # version < 3 clients can't handle more
-
-        add-spa-lib audio.convert* audioconvert/libspa-audioconvert
-        add-spa-lib api.alsa.* alsa/libspa-alsa
-        add-spa-lib api.v4l2.* v4l2/libspa-v4l2
-        add-spa-lib api.libcamera.* libcamera/libspa-libcamera
-        add-spa-lib api.bluez5.* bluez5/libspa-bluez5
-        add-spa-lib api.vulkan.* vulkan/libspa-vulkan
-        add-spa-lib api.jack.* jack/libspa-jack
-        add-spa-lib support.* support/libspa-support
-
-        load-module libpipewire-module-rtkit # rt.prio=20 rt.time.soft=200000 rt.time.hard=200000
-        load-module libpipewire-module-protocol-native
-        load-module libpipewire-module-profiler
-        load-module libpipewire-module-metadata
-        load-module libpipewire-module-spa-device-factory
-        load-module libpipewire-module-spa-node-factory
-        load-module libpipewire-module-client-node
-        load-module libpipewire-module-client-device
-        load-module libpipewire-module-portal
-        load-module libpipewire-module-access
-        load-module libpipewire-module-adapter
-        load-module libpipewire-module-link-factory
-        load-module libpipewire-module-session-manager
-
-        create-object spa-node-factory factory.name=support.node.driver node.name=Dummy priority.driver=8000
-
-        exec ${cfg.sessionManager} ${lib.concatStringsSep " " cfg.sessionManagerArguments}
-
-        ${cfg.extraConfig}
-      '';
-    };
-
-    environment.etc."pipewire/media-session.d/with-alsa" = mkIf cfg.alsa.enable { text = ""; };
-    environment.etc."pipewire/media-session.d/with-pulseaudio" = mkIf cfg.pulse.enable { text = ""; };
-    environment.etc."pipewire/media-session.d/with-jack" = mkIf cfg.jack.enable { text = ""; };
-  };
-}
diff --git a/nixos/modules/services/desktops/pipewire/alsa-monitor.conf.json b/nixos/modules/services/desktops/pipewire/alsa-monitor.conf.json
new file mode 100644
index 0000000000000..53fc9cc96343b
--- /dev/null
+++ b/nixos/modules/services/desktops/pipewire/alsa-monitor.conf.json
@@ -0,0 +1,34 @@
+{
+  "properties": {},
+  "rules": [
+    {
+      "matches": [
+        {
+          "device.name": "~alsa_card.*"
+        }
+      ],
+      "actions": {
+        "update-props": {
+          "api.alsa.use-acp": true,
+          "api.acp.auto-profile": false,
+          "api.acp.auto-port": false
+        }
+      }
+    },
+    {
+      "matches": [
+        {
+          "node.name": "~alsa_input.*"
+        },
+        {
+          "node.name": "~alsa_output.*"
+        }
+      ],
+      "actions": {
+        "update-props": {
+          "node.pause-on-idle": false
+        }
+      }
+    }
+  ]
+}
diff --git a/nixos/modules/services/desktops/pipewire/bluez-monitor.conf.json b/nixos/modules/services/desktops/pipewire/bluez-monitor.conf.json
new file mode 100644
index 0000000000000..4d50cb9f1adb8
--- /dev/null
+++ b/nixos/modules/services/desktops/pipewire/bluez-monitor.conf.json
@@ -0,0 +1,30 @@
+{
+  "properties": {},
+  "rules": [
+    {
+      "matches": [
+        {
+          "device.name": "~bluez_card.*"
+        }
+      ],
+      "actions": {
+        "update-props": {}
+      }
+    },
+    {
+      "matches": [
+        {
+          "node.name": "~bluez_input.*"
+        },
+        {
+          "node.name": "~bluez_output.*"
+        }
+      ],
+      "actions": {
+        "update-props": {
+          "node.pause-on-idle": false
+        }
+      }
+    }
+  ]
+}
diff --git a/nixos/modules/services/desktops/pipewire/client-rt.conf.json b/nixos/modules/services/desktops/pipewire/client-rt.conf.json
new file mode 100644
index 0000000000000..d294927b4f642
--- /dev/null
+++ b/nixos/modules/services/desktops/pipewire/client-rt.conf.json
@@ -0,0 +1,26 @@
+{
+  "context.properties": {
+    "log.level": 0
+  },
+  "context.spa-libs": {
+    "audio.convert.*": "audioconvert/libspa-audioconvert",
+    "support.*": "support/libspa-support"
+  },
+  "context.modules": {
+    "libpipewire-module-rtkit": {
+      "args": {},
+      "flags": [
+        "ifexists",
+        "nofail"
+      ]
+    },
+    "libpipewire-module-protocol-native": null,
+    "libpipewire-module-client-node": null,
+    "libpipewire-module-client-device": null,
+    "libpipewire-module-adapter": null,
+    "libpipewire-module-metadata": null,
+    "libpipewire-module-session-manager": null
+  },
+  "filter.properties": {},
+  "stream.properties": {}
+}
diff --git a/nixos/modules/services/desktops/pipewire/client.conf.json b/nixos/modules/services/desktops/pipewire/client.conf.json
new file mode 100644
index 0000000000000..224938abbbcf7
--- /dev/null
+++ b/nixos/modules/services/desktops/pipewire/client.conf.json
@@ -0,0 +1,19 @@
+{
+  "context.properties": {
+    "log.level": 0
+  },
+  "context.spa-libs": {
+    "audio.convert.*": "audioconvert/libspa-audioconvert",
+    "support.*": "support/libspa-support"
+  },
+  "context.modules": {
+    "libpipewire-module-protocol-native": null,
+    "libpipewire-module-client-node": null,
+    "libpipewire-module-client-device": null,
+    "libpipewire-module-adapter": null,
+    "libpipewire-module-metadata": null,
+    "libpipewire-module-session-manager": null
+  },
+  "filter.properties": {},
+  "stream.properties": {}
+}
diff --git a/nixos/modules/services/desktops/pipewire/jack.conf.json b/nixos/modules/services/desktops/pipewire/jack.conf.json
new file mode 100644
index 0000000000000..2de04036b312d
--- /dev/null
+++ b/nixos/modules/services/desktops/pipewire/jack.conf.json
@@ -0,0 +1,21 @@
+{
+  "context.properties": {
+    "log.level": 0
+  },
+  "context.spa-libs": {
+    "support.*": "support/libspa-support"
+  },
+  "context.modules": {
+    "libpipewire-module-rtkit": {
+      "args": {},
+      "flags": [
+        "ifexists",
+        "nofail"
+      ]
+    },
+    "libpipewire-module-protocol-native": null,
+    "libpipewire-module-client-node": null,
+    "libpipewire-module-metadata": null
+  },
+  "jack.properties": {}
+}
diff --git a/nixos/modules/services/desktops/pipewire/media-session.conf.json b/nixos/modules/services/desktops/pipewire/media-session.conf.json
new file mode 100644
index 0000000000000..4b2505ff8164c
--- /dev/null
+++ b/nixos/modules/services/desktops/pipewire/media-session.conf.json
@@ -0,0 +1,53 @@
+{
+  "context.properties": {},
+  "context.spa-libs": {
+    "api.bluez5.*": "bluez5/libspa-bluez5",
+    "api.alsa.*": "alsa/libspa-alsa",
+    "api.v4l2.*": "v4l2/libspa-v4l2",
+    "api.libcamera.*": "libcamera/libspa-libcamera"
+  },
+  "context.modules": {
+    "libpipewire-module-rtkit": {
+      "args": {},
+      "flags": [
+        "ifexists",
+        "nofail"
+      ]
+    },
+    "libpipewire-module-protocol-native": null,
+    "libpipewire-module-client-node": null,
+    "libpipewire-module-client-device": null,
+    "libpipewire-module-adapter": null,
+    "libpipewire-module-metadata": null,
+    "libpipewire-module-session-manager": null
+  },
+  "session.modules": {
+    "default": [
+      "flatpak",
+      "portal",
+      "v4l2",
+      "suspend-node",
+      "policy-node"
+    ],
+    "with-audio": [
+      "metadata",
+      "default-nodes",
+      "default-profile",
+      "default-routes",
+      "alsa-seq",
+      "alsa-monitor"
+    ],
+    "with-alsa": [
+      "with-audio"
+    ],
+    "with-jack": [
+      "with-audio"
+    ],
+    "with-pulseaudio": [
+      "with-audio",
+      "bluez5",
+      "restore-stream",
+      "streams-follow-default"
+    ]
+  }
+}
diff --git a/nixos/modules/services/desktops/pipewire/pipewire-media-session.nix b/nixos/modules/services/desktops/pipewire/pipewire-media-session.nix
new file mode 100644
index 0000000000000..b41ea349fb8db
--- /dev/null
+++ b/nixos/modules/services/desktops/pipewire/pipewire-media-session.nix
@@ -0,0 +1,129 @@
+# pipewire example session manager.
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.pipewire.media-session;
+  enable32BitAlsaPlugins = cfg.alsa.support32Bit
+                           && pkgs.stdenv.isx86_64
+                           && pkgs.pkgsi686Linux.pipewire != null;
+
+  prioritizeNativeProtocol = {
+    "context.modules" = {
+      "libpipewire-module-protocol-native" = {
+        _priority = -100;
+        _content = null;
+      };
+    };
+  };
+
+  # Use upstream config files passed through spa-json-dump as the base
+  # Patched here as necessary for them to work with this module
+  defaults = {
+    alsa-monitor = (builtins.fromJSON (builtins.readFile ./alsa-monitor.conf.json));
+    bluez-monitor = (builtins.fromJSON (builtins.readFile ./bluez-monitor.conf.json));
+    media-session = recursiveUpdate (builtins.fromJSON (builtins.readFile ./media-session.conf.json)) prioritizeNativeProtocol;
+    v4l2-monitor = (builtins.fromJSON (builtins.readFile ./v4l2-monitor.conf.json));
+  };
+  # Helpers for generating the pipewire JSON config file
+  mkSPAValueString = v:
+  if builtins.isList v then "[${lib.concatMapStringsSep " " mkSPAValueString v}]"
+  else if lib.types.attrs.check v then
+    "{${lib.concatStringsSep " " (mkSPAKeyValue v)}}"
+  else if builtins.isString v then "\"${lib.generators.mkValueStringDefault { } v}\""
+  else lib.generators.mkValueStringDefault { } v;
+
+  mkSPAKeyValue = attrs: map (def: def.content) (
+  lib.sortProperties
+    (
+      lib.mapAttrsToList
+        (k: v: lib.mkOrder (v._priority or 1000) "${lib.escape [ "=" ":" ] k} = ${mkSPAValueString (v._content or v)}")
+        attrs
+    )
+  );
+
+  toSPAJSON = attrs: lib.concatStringsSep "\n" (mkSPAKeyValue attrs);
+in {
+
+  meta = {
+    maintainers = teams.freedesktop.members;
+  };
+
+  ###### interface
+  options = {
+    services.pipewire.media-session = {
+      enable = mkOption {
+        type = types.bool;
+        default = config.services.pipewire.enable;
+        defaultText = "config.services.pipewire.enable";
+        description = "Example pipewire session manager";
+      };
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.pipewire.mediaSession;
+        example = literalExample "pkgs.pipewire.mediaSession";
+        description = ''
+          The pipewire-media-session derivation to use.
+        '';
+      };
+
+      config = {
+        media-session = mkOption {
+          type = types.attrs;
+          description = ''
+            Configuration for the media session core. For details see
+            https://gitlab.freedesktop.org/pipewire/pipewire/-/blob/${cfg.package.version}/src/daemon/media-session.d/media-session.conf
+          '';
+          default = {};
+        };
+
+        alsa-monitor = mkOption {
+          type = types.attrs;
+          description = ''
+            Configuration for the alsa monitor. For details see
+            https://gitlab.freedesktop.org/pipewire/pipewire/-/blob/${cfg.package.version}/src/daemon/media-session.d/alsa-monitor.conf
+          '';
+          default = {};
+        };
+
+        bluez-monitor = mkOption {
+          type = types.attrs;
+          description = ''
+            Configuration for the bluez5 monitor. For details see
+            https://gitlab.freedesktop.org/pipewire/pipewire/-/blob/${cfg.package.version}/src/daemon/media-session.d/bluez-monitor.conf
+          '';
+          default = {};
+        };
+
+        v4l2-monitor = mkOption {
+          type = types.attrs;
+          description = ''
+            Configuration for the V4L2 monitor. For details see
+            https://gitlab.freedesktop.org/pipewire/pipewire/-/blob/${cfg.package.version}/src/daemon/media-session.d/v4l2-monitor.conf
+          '';
+          default = {};
+        };
+      };
+    };
+  };
+
+  ###### implementation
+  config = mkIf cfg.enable {
+    environment.systemPackages = [ cfg.package ];
+    systemd.packages = [ cfg.package ];
+    systemd.user.services.pipewire-media-session.wantedBy = [ "pipewire.service" ];
+
+    environment.etc."pipewire/media-session.d/media-session.conf" = { text = toSPAJSON (recursiveUpdate defaults.media-session cfg.config.media-session); };
+    environment.etc."pipewire/media-session.d/v4l2-monitor.conf" = { text = toSPAJSON (recursiveUpdate defaults.v4l2-monitor cfg.config.v4l2-monitor); };
+
+    environment.etc."pipewire/media-session.d/with-alsa" = mkIf config.services.pipewire.alsa.enable { text = ""; };
+    environment.etc."pipewire/media-session.d/alsa-monitor.conf" = mkIf config.services.pipewire.alsa.enable { text = toSPAJSON (recursiveUpdate defaults.alsa-monitor cfg.config.alsa-monitor); };
+
+    environment.etc."pipewire/media-session.d/with-pulseaudio" = mkIf config.services.pipewire.pulse.enable { text = ""; };
+    environment.etc."pipewire/media-session.d/bluez-monitor.conf" = mkIf config.services.pipewire.pulse.enable { text = toSPAJSON (recursiveUpdate defaults.bluez-monitor cfg.config.bluez-monitor); };
+
+    environment.etc."pipewire/media-session.d/with-jack" = mkIf config.services.pipewire.jack.enable { text = ""; };
+  };
+}
diff --git a/nixos/modules/services/desktops/pipewire/pipewire-pulse.conf.json b/nixos/modules/services/desktops/pipewire/pipewire-pulse.conf.json
new file mode 100644
index 0000000000000..da08bcea2c90b
--- /dev/null
+++ b/nixos/modules/services/desktops/pipewire/pipewire-pulse.conf.json
@@ -0,0 +1,28 @@
+{
+  "context.properties": {},
+  "context.spa-libs": {
+    "audio.convert.*": "audioconvert/libspa-audioconvert",
+    "support.*": "support/libspa-support"
+  },
+  "context.modules": {
+    "libpipewire-module-rtkit": {
+      "args": {},
+      "flags": [
+        "ifexists",
+        "nofail"
+      ]
+    },
+    "libpipewire-module-protocol-native": null,
+    "libpipewire-module-client-node": null,
+    "libpipewire-module-adapter": null,
+    "libpipewire-module-metadata": null,
+    "libpipewire-module-protocol-pulse": {
+      "args": {
+        "server.address": [
+          "unix:native"
+        ]
+      }
+    }
+  },
+  "stream.properties": {}
+}
diff --git a/nixos/modules/services/desktops/pipewire/pipewire.conf.json b/nixos/modules/services/desktops/pipewire/pipewire.conf.json
new file mode 100644
index 0000000000000..59e2afca09396
--- /dev/null
+++ b/nixos/modules/services/desktops/pipewire/pipewire.conf.json
@@ -0,0 +1,55 @@
+{
+  "context.properties": {
+    "link.max-buffers": 16,
+    "core.daemon": true,
+    "core.name": "pipewire-0"
+  },
+  "context.spa-libs": {
+    "audio.convert.*": "audioconvert/libspa-audioconvert",
+    "api.alsa.*": "alsa/libspa-alsa",
+    "api.v4l2.*": "v4l2/libspa-v4l2",
+    "api.libcamera.*": "libcamera/libspa-libcamera",
+    "api.bluez5.*": "bluez5/libspa-bluez5",
+    "api.vulkan.*": "vulkan/libspa-vulkan",
+    "api.jack.*": "jack/libspa-jack",
+    "support.*": "support/libspa-support"
+  },
+  "context.modules": {
+    "libpipewire-module-rtkit": {
+      "args": {},
+      "flags": [
+        "ifexists",
+        "nofail"
+      ]
+    },
+    "libpipewire-module-protocol-native": null,
+    "libpipewire-module-profiler": null,
+    "libpipewire-module-metadata": null,
+    "libpipewire-module-spa-device-factory": null,
+    "libpipewire-module-spa-node-factory": null,
+    "libpipewire-module-client-node": null,
+    "libpipewire-module-client-device": null,
+    "libpipewire-module-portal": {
+      "flags": [
+        "ifexists",
+        "nofail"
+      ]
+    },
+    "libpipewire-module-access": {
+      "args": {}
+    },
+    "libpipewire-module-adapter": null,
+    "libpipewire-module-link-factory": null,
+    "libpipewire-module-session-manager": null
+  },
+  "context.objects": {
+    "spa-node-factory": {
+      "args": {
+        "factory.name": "support.node.driver",
+        "node.name": "Dummy-Driver",
+        "priority.driver": 8000
+      }
+    }
+  },
+  "context.exec": {}
+}
diff --git a/nixos/modules/services/desktops/pipewire/pipewire.nix b/nixos/modules/services/desktops/pipewire/pipewire.nix
new file mode 100644
index 0000000000000..2577e77c4a1ff
--- /dev/null
+++ b/nixos/modules/services/desktops/pipewire/pipewire.nix
@@ -0,0 +1,232 @@
+# pipewire service.
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.pipewire;
+  enable32BitAlsaPlugins = cfg.alsa.support32Bit
+                           && pkgs.stdenv.isx86_64
+                           && pkgs.pkgsi686Linux.pipewire != null;
+
+  # The package doesn't output to $out/lib/pipewire directly so that the
+  # overlays can use the outputs to replace the originals in FHS environments.
+  #
+  # This doesn't work in general because of missing development information.
+  jack-libs = pkgs.runCommand "jack-libs" {} ''
+    mkdir -p "$out/lib"
+    ln -s "${cfg.package.jack}/lib" "$out/lib/pipewire"
+  '';
+
+  prioritizeNativeProtocol = {
+    "context.modules" = {
+      # Most other modules depend on this, so put it first
+      "libpipewire-module-protocol-native" = {
+        _priority = -100;
+        _content = null;
+      };
+    };
+  };
+
+  fixDaemonModulePriorities = {
+    "context.modules" = {
+      # Most other modules depend on thism so put it first
+      "libpipewire-module-protocol-native" = {
+        _priority = -100;
+        _content = null;
+      };
+      # Needs to be before libpipewire-module-access
+      "libpipewire-module-portal" = {
+        _priority = -50;
+        _content = {
+          flags = [
+            "ifexists"
+            "nofail"
+          ];
+        };
+      };
+    };
+  };
+
+  # Use upstream config files passed through spa-json-dump as the base
+  # Patched here as necessary for them to work with this module
+  defaults = {
+    client = recursiveUpdate (builtins.fromJSON (builtins.readFile ./client.conf.json)) prioritizeNativeProtocol;
+    client-rt = recursiveUpdate (builtins.fromJSON (builtins.readFile ./client-rt.conf.json)) prioritizeNativeProtocol;
+    jack = recursiveUpdate (builtins.fromJSON (builtins.readFile ./jack.conf.json)) prioritizeNativeProtocol;
+    # Remove session manager invocation from the upstream generated file, it points to the wrong path
+    pipewire = recursiveUpdate (builtins.fromJSON (builtins.readFile ./pipewire.conf.json)) fixDaemonModulePriorities;
+    pipewire-pulse = recursiveUpdate (builtins.fromJSON (builtins.readFile ./pipewire-pulse.conf.json)) prioritizeNativeProtocol;
+  };
+
+  # Helpers for generating the pipewire JSON config file
+  mkSPAValueString = v:
+  if builtins.isList v then "[${lib.concatMapStringsSep " " mkSPAValueString v}]"
+  else if lib.types.attrs.check v then
+    "{${lib.concatStringsSep " " (mkSPAKeyValue v)}}"
+  else if builtins.isString v then "\"${lib.generators.mkValueStringDefault { } v}\""
+  else lib.generators.mkValueStringDefault { } v;
+
+  mkSPAKeyValue = attrs: map (def: def.content) (
+  lib.sortProperties
+    (
+      lib.mapAttrsToList
+        (k: v: lib.mkOrder (v._priority or 1000) "${lib.escape [ "=" ] k} = ${mkSPAValueString (v._content or v)}")
+        attrs
+    )
+  );
+
+  toSPAJSON = attrs: lib.concatStringsSep "\n" (mkSPAKeyValue attrs);
+in {
+
+  meta = {
+    maintainers = teams.freedesktop.members;
+  };
+
+  ###### interface
+  options = {
+    services.pipewire = {
+      enable = mkEnableOption "pipewire service";
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.pipewire;
+        defaultText = "pkgs.pipewire";
+        example = literalExample "pkgs.pipewire";
+        description = ''
+          The pipewire derivation to use.
+        '';
+      };
+
+      socketActivation = mkOption {
+        default = true;
+        type = types.bool;
+        description = ''
+          Automatically run pipewire when connections are made to the pipewire socket.
+        '';
+      };
+
+      config = {
+        client = mkOption {
+          type = types.attrs;
+          default = {};
+          description = ''
+            Configuration for pipewire clients. For details see
+            https://gitlab.freedesktop.org/pipewire/pipewire/-/blob/${cfg.package.version}/src/daemon/client.conf.in
+          '';
+        };
+
+        client-rt = mkOption {
+          type = types.attrs;
+          default = {};
+          description = ''
+            Configuration for realtime pipewire clients. For details see
+            https://gitlab.freedesktop.org/pipewire/pipewire/-/blob/${cfg.package.version}/src/daemon/client-rt.conf.in
+          '';
+        };
+
+        jack = mkOption {
+          type = types.attrs;
+          default = {};
+          description = ''
+            Configuration for the pipewire daemon's jack module. For details see
+            https://gitlab.freedesktop.org/pipewire/pipewire/-/blob/${cfg.package.version}/src/daemon/jack.conf.in
+          '';
+        };
+
+        pipewire = mkOption {
+          type = types.attrs;
+          default = {};
+          description = ''
+            Configuration for the pipewire daemon. For details see
+            https://gitlab.freedesktop.org/pipewire/pipewire/-/blob/${cfg.package.version}/src/daemon/pipewire.conf.in
+          '';
+        };
+
+        pipewire-pulse = mkOption {
+          type = types.attrs;
+          default = {};
+          description = ''
+            Configuration for the pipewire-pulse daemon. For details see
+            https://gitlab.freedesktop.org/pipewire/pipewire/-/blob/${cfg.package.version}/src/daemon/pipewire-pulse.conf.in
+          '';
+        };
+      };
+
+      alsa = {
+        enable = mkEnableOption "ALSA support";
+        support32Bit = mkEnableOption "32-bit ALSA support on 64-bit systems";
+      };
+
+      jack = {
+        enable = mkEnableOption "JACK audio emulation";
+      };
+
+      pulse = {
+        enable = mkEnableOption "PulseAudio server emulation";
+      };
+    };
+  };
+
+
+  ###### implementation
+  config = mkIf cfg.enable {
+    assertions = [
+      {
+        assertion = cfg.pulse.enable -> !config.hardware.pulseaudio.enable;
+        message = "PipeWire based PulseAudio server emulation replaces PulseAudio. This option requires `hardware.pulseaudio.enable` to be set to false";
+      }
+      {
+        assertion = cfg.jack.enable -> !config.services.jack.jackd.enable;
+        message = "PipeWire based JACK emulation doesn't use the JACK service. This option requires `services.jack.jackd.enable` to be set to false";
+      }
+    ];
+
+    environment.systemPackages = [ cfg.package ]
+                                 ++ lib.optional cfg.jack.enable jack-libs;
+
+    systemd.packages = [ cfg.package ]
+                       ++ lib.optional cfg.pulse.enable cfg.package.pulse;
+
+    # PipeWire depends on DBUS but doesn't list it. Without this booting
+    # into a terminal results in the service crashing with an error.
+    systemd.user.sockets.pipewire.wantedBy = lib.mkIf cfg.socketActivation [ "sockets.target" ];
+    systemd.user.sockets.pipewire-pulse.wantedBy = lib.mkIf (cfg.socketActivation && cfg.pulse.enable) ["sockets.target"];
+    systemd.user.services.pipewire.bindsTo = [ "dbus.service" ];
+    services.udev.packages = [ cfg.package ];
+
+    # If any paths are updated here they must also be updated in the package test.
+    environment.etc."alsa/conf.d/49-pipewire-modules.conf" = mkIf cfg.alsa.enable {
+      text = ''
+        pcm_type.pipewire {
+          libs.native = ${cfg.package.lib}/lib/alsa-lib/libasound_module_pcm_pipewire.so ;
+          ${optionalString enable32BitAlsaPlugins
+            "libs.32Bit = ${pkgs.pkgsi686Linux.pipewire.lib}/lib/alsa-lib/libasound_module_pcm_pipewire.so ;"}
+        }
+        ctl_type.pipewire {
+          libs.native = ${cfg.package.lib}/lib/alsa-lib/libasound_module_ctl_pipewire.so ;
+          ${optionalString enable32BitAlsaPlugins
+            "libs.32Bit = ${pkgs.pkgsi686Linux.pipewire.lib}/lib/alsa-lib/libasound_module_ctl_pipewire.so ;"}
+        }
+      '';
+    };
+    environment.etc."alsa/conf.d/50-pipewire.conf" = mkIf cfg.alsa.enable {
+      source = "${cfg.package}/share/alsa/alsa.conf.d/50-pipewire.conf";
+    };
+    environment.etc."alsa/conf.d/99-pipewire-default.conf" = mkIf cfg.alsa.enable {
+      source = "${cfg.package}/share/alsa/alsa.conf.d/99-pipewire-default.conf";
+    };
+
+    environment.etc."pipewire/client.conf" = { text = toSPAJSON (recursiveUpdate defaults.client cfg.config.client); };
+    environment.etc."pipewire/client-rt.conf" = { text = toSPAJSON (recursiveUpdate defaults.client-rt cfg.config.client-rt); };
+    environment.etc."pipewire/jack.conf" = { text = toSPAJSON (recursiveUpdate defaults.jack cfg.config.jack); };
+    environment.etc."pipewire/pipewire.conf" = { text = toSPAJSON (recursiveUpdate defaults.pipewire cfg.config.pipewire); };
+    environment.etc."pipewire/pipewire-pulse.conf" = { text = toSPAJSON (recursiveUpdate defaults.pipewire-pulse cfg.config.pipewire-pulse); };
+
+    environment.sessionVariables.LD_LIBRARY_PATH =
+      lib.optional cfg.jack.enable "/run/current-system/sw/lib/pipewire";
+
+    # https://gitlab.freedesktop.org/pipewire/pipewire/-/issues/464#note_723554
+    systemd.user.services.pipewire.environment."PIPEWIRE_LINK_PASSIVE" = "1";
+  };
+}
diff --git a/nixos/modules/services/desktops/pipewire/v4l2-monitor.conf.json b/nixos/modules/services/desktops/pipewire/v4l2-monitor.conf.json
new file mode 100644
index 0000000000000..b08cba1b604b5
--- /dev/null
+++ b/nixos/modules/services/desktops/pipewire/v4l2-monitor.conf.json
@@ -0,0 +1,30 @@
+{
+  "properties": {},
+  "rules": [
+    {
+      "matches": [
+        {
+          "device.name": "~v4l2_device.*"
+        }
+      ],
+      "actions": {
+        "update-props": {}
+      }
+    },
+    {
+      "matches": [
+        {
+          "node.name": "~v4l2_input.*"
+        },
+        {
+          "node.name": "~v4l2_output.*"
+        }
+      ],
+      "actions": {
+        "update-props": {
+          "node.pause-on-idle": false
+        }
+      }
+    }
+  ]
+}
diff --git a/nixos/modules/services/games/minetest-server.nix b/nixos/modules/services/games/minetest-server.nix
index f52079fc1ef62..2111c970d4f27 100644
--- a/nixos/modules/services/games/minetest-server.nix
+++ b/nixos/modules/services/games/minetest-server.nix
@@ -4,7 +4,7 @@ with lib;
 
 let
   cfg   = config.services.minetest-server;
-  flag  = val: name: if val != null then "--${name} ${val} " else "";
+  flag  = val: name: if val != null then "--${name} ${toString val} " else "";
   flags = [
     (flag cfg.gameId "gameid")
     (flag cfg.world "world")
diff --git a/nixos/modules/services/hardware/acpid.nix b/nixos/modules/services/hardware/acpid.nix
index 4c97485d97261..3e619fe32ef17 100644
--- a/nixos/modules/services/hardware/acpid.nix
+++ b/nixos/modules/services/hardware/acpid.nix
@@ -3,21 +3,22 @@
 with lib;
 
 let
+  cfg = config.services.acpid;
 
   canonicalHandlers = {
     powerEvent = {
       event = "button/power.*";
-      action = config.services.acpid.powerEventCommands;
+      action = cfg.powerEventCommands;
     };
 
     lidEvent = {
       event = "button/lid.*";
-      action = config.services.acpid.lidEventCommands;
+      action = cfg.lidEventCommands;
     };
 
     acEvent = {
       event = "ac_adapter.*";
-      action = config.services.acpid.acEventCommands;
+      action = cfg.acEventCommands;
     };
   };
 
@@ -33,7 +34,7 @@ let
             echo "event=${handler.event}" > $fn
             echo "action=${pkgs.writeShellScriptBin "${name}.sh" handler.action }/bin/${name}.sh '%e'" >> $fn
           '';
-        in concatStringsSep "\n" (mapAttrsToList f (canonicalHandlers // config.services.acpid.handlers))
+        in concatStringsSep "\n" (mapAttrsToList f (canonicalHandlers // cfg.handlers))
       }
     '';
 
@@ -47,11 +48,7 @@ in
 
     services.acpid = {
 
-      enable = mkOption {
-        type = types.bool;
-        default = false;
-        description = "Whether to enable the ACPI daemon.";
-      };
+      enable = mkEnableOption "the ACPI daemon";
 
       logEvents = mkOption {
         type = types.bool;
@@ -129,26 +126,28 @@ in
 
   ###### implementation
 
-  config = mkIf config.services.acpid.enable {
+  config = mkIf cfg.enable {
 
     systemd.services.acpid = {
       description = "ACPI Daemon";
+      documentation = [ "man:acpid(8)" ];
 
       wantedBy = [ "multi-user.target" ];
-      after = [ "systemd-udev-settle.service" ];
-
-      path = [ pkgs.acpid ];
 
       serviceConfig = {
-        Type = "forking";
+        ExecStart = escapeShellArgs
+          ([ "${pkgs.acpid}/bin/acpid"
+             "--foreground"
+             "--netlink"
+             "--confdir" "${acpiConfDir}"
+           ] ++ optional cfg.logEvents "--logevents"
+          );
       };
-
       unitConfig = {
         ConditionVirtualization = "!systemd-nspawn";
         ConditionPathExists = [ "/proc/acpi" ];
       };
 
-      script = "acpid ${optionalString config.services.acpid.logEvents "--logevents"} --confdir ${acpiConfDir}";
     };
 
   };
diff --git a/nixos/modules/services/hardware/bluetooth.nix b/nixos/modules/services/hardware/bluetooth.nix
index 6f5a6d3bf2886..08ad90126b1d2 100644
--- a/nixos/modules/services/hardware/bluetooth.nix
+++ b/nixos/modules/services/hardware/bluetooth.nix
@@ -1,12 +1,39 @@
 { config, lib, pkgs, ... }:
-
-with lib;
-
 let
   cfg = config.hardware.bluetooth;
-  bluez-bluetooth = cfg.package;
+  package = cfg.package;
+
+  inherit (lib)
+    mkDefault mkEnableOption mkIf mkOption
+    mkRenamedOptionModule mkRemovedOptionModule
+    concatStringsSep escapeShellArgs
+    optional optionals optionalAttrs recursiveUpdate types;
+
+  cfgFmt = pkgs.formats.ini { };
+
+  # bluez will complain if some of the sections are not found, so just make them
+  # empty (but present in the file) for now
+  defaults = {
+    General.ControllerMode = "dual";
+    Controller = { };
+    GATT = { };
+    Policy.AutoEnable = cfg.powerOnBoot;
+  };
+
+  hasDisabledPlugins = builtins.length cfg.disabledPlugins > 0;
 
-in {
+in
+{
+  imports = [
+    (mkRenamedOptionModule [ "hardware" "bluetooth" "config" ] [ "hardware" "bluetooth" "settings" ])
+    (mkRemovedOptionModule [ "hardware" "bluetooth" "extraConfig" ] ''
+      Use hardware.bluetooth.settings instead.
+
+      This is part of the general move to use structured settings instead of raw
+      text for config as introduced by RFC0042:
+      https://github.com/NixOS/rfcs/blob/master/rfcs/0042-config-option.md
+    '')
+  ];
 
   ###### interface
 
@@ -18,7 +45,7 @@ in {
       hsphfpd.enable = mkEnableOption "support for hsphfpd[-prototype] implementation";
 
       powerOnBoot = mkOption {
-        type    = types.bool;
+        type = types.bool;
         default = true;
         description = "Whether to power up the default Bluetooth controller on boot.";
       };
@@ -38,8 +65,15 @@ in {
         '';
       };
 
-      config = mkOption {
-        type = with types; attrsOf (attrsOf (oneOf [ bool int str ]));
+      disabledPlugins = mkOption {
+        type = types.listOf types.str;
+        default = [ ];
+        description = "Built-in plugins to disable";
+      };
+
+      settings = mkOption {
+        type = cfgFmt.type;
+        default = { };
         example = {
           General = {
             ControllerMode = "bredr";
@@ -47,79 +81,65 @@ in {
         };
         description = "Set configuration for system-wide bluetooth (/etc/bluetooth/main.conf).";
       };
-
-      extraConfig = mkOption {
-        type = with types; nullOr lines;
-        default = null;
-        example = ''
-          [General]
-          ControllerMode = bredr
-        '';
-        description = ''
-          Set additional configuration for system-wide bluetooth (/etc/bluetooth/main.conf).
-        '';
-      };
     };
-
   };
 
   ###### implementation
 
   config = mkIf cfg.enable {
-    warnings = optional (cfg.extraConfig != null) "hardware.bluetooth.`extraConfig` is deprecated, please use hardware.bluetooth.`config`.";
+    environment.systemPackages = [ package ]
+      ++ optional cfg.hsphfpd.enable pkgs.hsphfpd;
 
-    hardware.bluetooth.config = {
-      Policy = {
-        AutoEnable = mkDefault cfg.powerOnBoot;
-      };
-    };
-
-    environment.systemPackages = [ bluez-bluetooth ]
-      ++ optionals cfg.hsphfpd.enable [ pkgs.hsphfpd ];
-
-    environment.etc."bluetooth/main.conf"= {
-      source = pkgs.writeText "main.conf"
-        (generators.toINI { } cfg.config + optionalString (cfg.extraConfig != null) cfg.extraConfig);
-    };
-
-    services.udev.packages = [ bluez-bluetooth ];
-    services.dbus.packages = [ bluez-bluetooth ]
-      ++ optionals cfg.hsphfpd.enable [ pkgs.hsphfpd ];
-    systemd.packages       = [ bluez-bluetooth ];
+    environment.etc."bluetooth/main.conf".source =
+      cfgFmt.generate "main.conf" (recursiveUpdate defaults cfg.settings);
+    services.udev.packages = [ package ];
+    services.dbus.packages = [ package ]
+      ++ optional cfg.hsphfpd.enable pkgs.hsphfpd;
+    systemd.packages = [ package ];
 
     systemd.services = {
-      bluetooth = {
+      bluetooth =
+        let
+          # `man bluetoothd` will refer to main.conf in the nix store but bluez
+          # will in fact load the configuration file at /etc/bluetooth/main.conf
+          # so force it here to avoid any ambiguity and things suddenly breaking
+          # if/when the bluez derivation is changed.
+          args = [ "-f" "/etc/bluetooth/main.conf" ]
+            ++ optional hasDisabledPlugins
+            "--noplugin=${concatStringsSep "," cfg.disabledPlugins}";
+        in
+        {
+          wantedBy = [ "bluetooth.target" ];
+          aliases = [ "dbus-org.bluez.service" ];
+          serviceConfig.ExecStart = [
+            ""
+            "${package}/libexec/bluetooth/bluetoothd ${escapeShellArgs args}"
+          ];
+          # restarting can leave people without a mouse/keyboard
+          unitConfig.X-RestartIfChanged = false;
+        };
+    }
+    // (optionalAttrs cfg.hsphfpd.enable {
+      hsphfpd = {
+        after = [ "bluetooth.service" ];
+        requires = [ "bluetooth.service" ];
         wantedBy = [ "bluetooth.target" ];
-        aliases  = [ "dbus-org.bluez.service" ];
-        # restarting can leave people without a mouse/keyboard
-        unitConfig.X-RestartIfChanged = false;
+
+        description = "A prototype implementation used for connecting HSP/HFP Bluetooth devices";
+        serviceConfig.ExecStart = "${pkgs.hsphfpd}/bin/hsphfpd.pl";
       };
-    }
-      // (optionalAttrs cfg.hsphfpd.enable {
-        hsphfpd = {
-          after = [ "bluetooth.service" ];
-          requires = [ "bluetooth.service" ];
-          wantedBy = [ "multi-user.target" ];
-
-          description = "A prototype implementation used for connecting HSP/HFP Bluetooth devices";
-          serviceConfig.ExecStart = "${pkgs.hsphfpd}/bin/hsphfpd.pl";
-        };
-      })
-      ;
+    });
 
     systemd.user.services = {
       obex.aliases = [ "dbus-org.bluez.obex.service" ];
     }
-      // (optionalAttrs cfg.hsphfpd.enable {
-        telephony_client = {
-          wantedBy = [ "default.target"];
-
-          description = "telephony_client for hsphfpd";
-          serviceConfig.ExecStart = "${pkgs.hsphfpd}/bin/telephony_client.pl";
-        };
-      })
-      ;
+    // optionalAttrs cfg.hsphfpd.enable {
+      telephony_client = {
+        wantedBy = [ "default.target" ];
 
+        description = "telephony_client for hsphfpd";
+        serviceConfig.ExecStart = "${pkgs.hsphfpd}/bin/telephony_client.pl";
+      };
+    };
   };
-
 }
diff --git a/nixos/modules/services/hardware/spacenavd.nix b/nixos/modules/services/hardware/spacenavd.nix
new file mode 100644
index 0000000000000..7afae76cc4fbd
--- /dev/null
+++ b/nixos/modules/services/hardware/spacenavd.nix
@@ -0,0 +1,26 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let cfg = config.hardware.spacenavd;
+
+in {
+
+  options = {
+    hardware.spacenavd = {
+      enable = mkEnableOption "spacenavd to support 3DConnexion devices";
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.user.services.spacenavd = {
+      description = "Daemon for the Spacenavigator 6DOF mice by 3Dconnexion";
+      after = [ "syslog.target" ];
+      wantedBy = [ "graphical.target" ];
+      serviceConfig = {
+        ExecStart = "${pkgs.spacenavd}/bin/spacenavd -d -l syslog";
+        StandardError = "syslog";
+      };
+    };
+  };
+}
diff --git a/nixos/modules/services/hardware/trezord.nix b/nixos/modules/services/hardware/trezord.nix
index 8c609bbf825b6..a65d4250c2e54 100644
--- a/nixos/modules/services/hardware/trezord.nix
+++ b/nixos/modules/services/hardware/trezord.nix
@@ -48,7 +48,7 @@ in {
 
     systemd.services.trezord = {
       description = "Trezor Bridge";
-      after = [ "systemd-udev-settle.service" "network.target" ];
+      after = [ "network.target" ];
       wantedBy = [ "multi-user.target" ];
       path = [];
       serviceConfig = {
diff --git a/nixos/modules/services/hardware/udev.nix b/nixos/modules/services/hardware/udev.nix
index 63027f7744dc9..d48b5444677c1 100644
--- a/nixos/modules/services/hardware/udev.nix
+++ b/nixos/modules/services/hardware/udev.nix
@@ -202,13 +202,27 @@ in
         '';
       };
 
-      extraRules = mkOption {
+      initrdRules = mkOption {
         default = "";
         example = ''
           SUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*", ATTR{address}=="00:1D:60:B9:6D:4F", KERNEL=="eth*", NAME="my_fast_network_card"
         '';
         type = types.lines;
         description = ''
+          <command>udev</command> rules to include in the initrd
+          <emphasis>only</emphasis>. They'll be written into file
+          <filename>99-local.rules</filename>. Thus they are read and applied
+          after the essential initrd rules.
+        '';
+      };
+
+      extraRules = mkOption {
+        default = "";
+        example = ''
+          ENV{ID_VENDOR_ID}=="046d", ENV{ID_MODEL_ID}=="0825", ENV{PULSE_IGNORE}="1"
+        '';
+        type = types.lines;
+        description = ''
           Additional <command>udev</command> rules. They'll be written
           into file <filename>99-local.rules</filename>. Thus they are
           read and applied after all other rules.
@@ -284,6 +298,13 @@ in
 
     boot.kernelParams = mkIf (!config.networking.usePredictableInterfaceNames) [ "net.ifnames=0" ];
 
+    boot.initrd.extraUdevRulesCommands = optionalString (cfg.initrdRules != "")
+      ''
+        cat <<'EOF' > $out/99-local.rules
+        ${cfg.initrdRules}
+        EOF
+      '';
+
     environment.etc =
       {
         "udev/rules.d".source = udevRules;
diff --git a/nixos/modules/services/hardware/xow.nix b/nixos/modules/services/hardware/xow.nix
index a18d60ad83bec..311181176bd83 100644
--- a/nixos/modules/services/hardware/xow.nix
+++ b/nixos/modules/services/hardware/xow.nix
@@ -10,7 +10,10 @@ in {
   config = lib.mkIf cfg.enable {
     hardware.uinput.enable = true;
 
+    boot.extraModprobeConfig = lib.readFile "${pkgs.xow}/lib/modprobe.d/xow-blacklist.conf";
+
     systemd.packages = [ pkgs.xow ];
+    systemd.services.xow.wantedBy = [ "multi-user.target" ];
 
     services.udev.packages = [ pkgs.xow ];
   };
diff --git a/nixos/modules/services/mail/mlmmj.nix b/nixos/modules/services/mail/mlmmj.nix
index d58d93c4214c1..fd74f2dc5f07f 100644
--- a/nixos/modules/services/mail/mlmmj.nix
+++ b/nixos/modules/services/mail/mlmmj.nix
@@ -16,7 +16,14 @@ let
   alias = domain: list: "${list}: \"|${pkgs.mlmmj}/bin/mlmmj-receive -L ${listDir domain list}/\"";
   subjectPrefix = list: "[${list}]";
   listAddress = domain: list: "${list}@${domain}";
-  customHeaders = domain: list: [ "List-Id: ${list}" "Reply-To: ${list}@${domain}" ];
+  customHeaders = domain: list: [
+    "List-Id: ${list}"
+    "Reply-To: ${list}@${domain}"
+    "List-Post: <mailto:${list}@${domain}>"
+    "List-Help: <mailto:${list}+help@${domain}>"
+    "List-Subscribe: <mailto:${list}+subscribe@${domain}>"
+    "List-Unsubscribe: <mailto:${list}+unsubscribe@${domain}>"
+  ];
   footer = domain: list: "To unsubscribe send a mail to ${list}+unsubscribe@${domain}";
   createList = d: l:
     let ctlDir = listCtl d l; in
@@ -110,17 +117,29 @@ in
     services.postfix = {
       enable = true;
       recipientDelimiter= "+";
-      extraMasterConf = ''
-        mlmmj unix - n n - - pipe flags=ORhu user=mlmmj argv=${pkgs.mlmmj}/bin/mlmmj-receive -F -L ${spoolDir}/$nexthop
-      '';
+      masterConfig.mlmmj = {
+        type = "unix";
+        private = true;
+        privileged = true;
+        chroot = false;
+        wakeup = 0;
+        command = "pipe";
+        args = [
+          "flags=ORhu"
+          "user=mlmmj"
+          "argv=${pkgs.mlmmj}/bin/mlmmj-receive"
+          "-F"
+          "-L"
+          "${spoolDir}/$nexthop"
+        ];
+      };
 
       extraAliases = concatMapLines (alias cfg.listDomain) cfg.mailLists;
 
-      extraConfig = ''
-        transport_maps = hash:${stateDir}/transports
-        virtual_alias_maps = hash:${stateDir}/virtuals
-        propagate_unmatched_extensions = virtual
-      '';
+      extraConfig = "propagate_unmatched_extensions = virtual";
+
+      virtual = concatMapLines (virtual cfg.listDomain) cfg.mailLists;
+      transport = concatMapLines (transport cfg.listDomain) cfg.mailLists;
     };
 
     environment.systemPackages = [ pkgs.mlmmj ];
@@ -129,10 +148,8 @@ in
           ${pkgs.coreutils}/bin/mkdir -p ${stateDir} ${spoolDir}/${cfg.listDomain}
           ${pkgs.coreutils}/bin/chown -R ${cfg.user}:${cfg.group} ${spoolDir}
           ${concatMapLines (createList cfg.listDomain) cfg.mailLists}
-          echo "${concatMapLines (virtual cfg.listDomain) cfg.mailLists}" > ${stateDir}/virtuals
-          echo "${concatMapLines (transport cfg.listDomain) cfg.mailLists}" > ${stateDir}/transports
-          ${pkgs.postfix}/bin/postmap ${stateDir}/virtuals
-          ${pkgs.postfix}/bin/postmap ${stateDir}/transports
+          ${pkgs.postfix}/bin/postmap /etc/postfix/virtual
+          ${pkgs.postfix}/bin/postmap /etc/postfix/transport
       '';
 
     systemd.services.mlmmj-maintd = {
diff --git a/nixos/modules/services/misc/apache-kafka.nix b/nixos/modules/services/misc/apache-kafka.nix
index f3a650a260f1e..69dfadfe54e0d 100644
--- a/nixos/modules/services/misc/apache-kafka.nix
+++ b/nixos/modules/services/misc/apache-kafka.nix
@@ -90,19 +90,7 @@ in {
 
     jvmOptions = mkOption {
       description = "Extra command line options for the JVM running Kafka.";
-      default = [
-        "-server"
-        "-Xmx1G"
-        "-Xms1G"
-        "-XX:+UseCompressedOops"
-        "-XX:+UseParNewGC"
-        "-XX:+UseConcMarkSweepGC"
-        "-XX:+CMSClassUnloadingEnabled"
-        "-XX:+CMSScavengeBeforeRemark"
-        "-XX:+DisableExplicitGC"
-        "-Djava.awt.headless=true"
-        "-Djava.net.preferIPv4Stack=true"
-      ];
+      default = [];
       type = types.listOf types.str;
       example = [
         "-Djava.net.preferIPv4Stack=true"
@@ -118,6 +106,13 @@ in {
       type = types.package;
     };
 
+    jre = mkOption {
+      description = "The JRE with which to run Kafka";
+      default = cfg.package.passthru.jre;
+      defaultText = "pkgs.apacheKafka.passthru.jre";
+      type = types.package;
+    };
+
   };
 
   config = mkIf cfg.enable {
@@ -138,7 +133,7 @@ in {
       after = [ "network.target" ];
       serviceConfig = {
         ExecStart = ''
-          ${pkgs.jre}/bin/java \
+          ${cfg.jre}/bin/java \
             -cp "${cfg.package}/libs/*" \
             -Dlog4j.configuration=file:${logConfig} \
             ${toString cfg.jvmOptions} \
diff --git a/nixos/modules/services/misc/defaultUnicornConfig.rb b/nixos/modules/services/misc/defaultUnicornConfig.rb
deleted file mode 100644
index 0b58c59c7a513..0000000000000
--- a/nixos/modules/services/misc/defaultUnicornConfig.rb
+++ /dev/null
@@ -1,69 +0,0 @@
-worker_processes 3
-
-listen ENV["UNICORN_PATH"] + "/tmp/sockets/gitlab.socket", :backlog => 1024
-listen "/run/gitlab/gitlab.socket", :backlog => 1024
-
-working_directory ENV["GITLAB_PATH"]
-
-pid ENV["UNICORN_PATH"] + "/tmp/pids/unicorn.pid"
-
-timeout 60
-
-# combine Ruby 2.0.0dev or REE with "preload_app true" for memory savings
-# http://rubyenterpriseedition.com/faq.html#adapt_apps_for_cow
-preload_app true
-GC.respond_to?(:copy_on_write_friendly=) and
-  GC.copy_on_write_friendly = true
-
-check_client_connection false
-
-before_fork do |server, worker|
-  # the following is highly recommended for Rails + "preload_app true"
-  # as there's no need for the master process to hold a connection
-  defined?(ActiveRecord::Base) and
-    ActiveRecord::Base.connection.disconnect!
-
-  # The following is only recommended for memory/DB-constrained
-  # installations.  It is not needed if your system can house
-  # twice as many worker_processes as you have configured.
-  #
-  # This allows a new master process to incrementally
-  # phase out the old master process with SIGTTOU to avoid a
-  # thundering herd (especially in the "preload_app false" case)
-  # when doing a transparent upgrade.  The last worker spawned
-  # will then kill off the old master process with a SIGQUIT.
-  old_pid = "#{server.config[:pid]}.oldbin"
-  if old_pid != server.pid
-    begin
-      sig = (worker.nr + 1) >= server.worker_processes ? :QUIT : :TTOU
-      Process.kill(sig, File.read(old_pid).to_i)
-    rescue Errno::ENOENT, Errno::ESRCH
-    end
-  end
-
-  # Throttle the master from forking too quickly by sleeping.  Due
-  # to the implementation of standard Unix signal handlers, this
-  # helps (but does not completely) prevent identical, repeated signals
-  # from being lost when the receiving process is busy.
-  # sleep 1
-end
-
-after_fork do |server, worker|
-  # per-process listener ports for debugging/admin/migrations
-  # addr = "127.0.0.1:#{9293 + worker.nr}"
-  # server.listen(addr, :tries => -1, :delay => 5, :tcp_nopush => true)
-
-  # the following is *required* for Rails + "preload_app true",
-  defined?(ActiveRecord::Base) and
-    ActiveRecord::Base.establish_connection
-
-  # reset prometheus client, this will cause any opened metrics files to be closed
-  defined?(::Prometheus::Client.reinitialize_on_pid_change) &&
-    Prometheus::Client.reinitialize_on_pid_change
-
-  # if preload_app is true, then you may also want to check and
-  # restart any other shared sockets/descriptors such as Memcached,
-  # and Redis.  TokyoCabinet file handles are safe to reuse
-  # between any number of forked children (assuming your kernel
-  # correctly implements pread()/pwrite() system calls)
-end
diff --git a/nixos/modules/services/misc/etesync-dav.nix b/nixos/modules/services/misc/etesync-dav.nix
new file mode 100644
index 0000000000000..9d7cfda371b13
--- /dev/null
+++ b/nixos/modules/services/misc/etesync-dav.nix
@@ -0,0 +1,92 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.etesync-dav;
+in
+  {
+    options.services.etesync-dav = {
+      enable = mkEnableOption "etesync-dav";
+
+      host = mkOption {
+        type = types.str;
+        default = "localhost";
+        description = "The server host address.";
+      };
+
+      port = mkOption {
+        type = types.port;
+        default = 37358;
+        description = "The server host port.";
+      };
+
+      apiUrl = mkOption {
+        type = types.str;
+        default = "https://api.etesync.com/";
+        description = "The url to the etesync API.";
+      };
+
+      openFirewall = mkOption {
+        default = false;
+        type = types.bool;
+        description = "Whether to open the firewall for the specified port.";
+      };
+
+      sslCertificate = mkOption {
+        type = types.nullOr types.path;
+        default = null;
+        example = "/var/etesync.crt";
+        description = ''
+          Path to server SSL certificate. It will be copied into
+          etesync-dav's data directory.
+        '';
+      };
+
+      sslCertificateKey = mkOption {
+        type = types.nullOr types.path;
+        default = null;
+        example = "/var/etesync.key";
+        description = ''
+          Path to server SSL certificate key.  It will be copied into
+          etesync-dav's data directory.
+        '';
+      };
+    };
+
+    config = mkIf cfg.enable {
+      networking.firewall.allowedTCPPorts = mkIf cfg.openFirewall [ cfg.port ];
+
+      systemd.services.etesync-dav = {
+        description = "etesync-dav - A CalDAV and CardDAV adapter for EteSync";
+        after = [ "network-online.target" ];
+        wantedBy = [ "multi-user.target" ];
+        path = [ pkgs.etesync-dav ];
+        environment = {
+          ETESYNC_LISTEN_ADDRESS = cfg.host;
+          ETESYNC_LISTEN_PORT = toString cfg.port;
+          ETESYNC_URL = cfg.apiUrl;
+          ETESYNC_DATA_DIR = "/var/lib/etesync-dav";
+        };
+
+        serviceConfig = {
+          Type = "simple";
+          DynamicUser = true;
+          StateDirectory = "etesync-dav";
+          ExecStart = "${pkgs.etesync-dav}/bin/etesync-dav";
+          ExecStartPre = mkIf (cfg.sslCertificate != null || cfg.sslCertificateKey != null) (
+            pkgs.writers.writeBash "etesync-dav-copy-keys" ''
+              ${optionalString (cfg.sslCertificate != null) ''
+                cp ${toString cfg.sslCertificate} $STATE_DIRECTORY/etesync.crt
+              ''}
+              ${optionalString (cfg.sslCertificateKey != null) ''
+                cp ${toString cfg.sslCertificateKey} $STATE_DIRECTORY/etesync.key
+              ''}
+            ''
+          );
+          Restart = "on-failure";
+          RestartSec = "30min 1s";
+        };
+      };
+    };
+  }
diff --git a/nixos/modules/services/misc/gitlab.nix b/nixos/modules/services/misc/gitlab.nix
index de4d1bf1987a1..1d45af36349c8 100644
--- a/nixos/modules/services/misc/gitlab.nix
+++ b/nixos/modules/services/misc/gitlab.nix
@@ -142,7 +142,7 @@ let
 
   gitlabEnv = {
     HOME = "${cfg.statePath}/home";
-    UNICORN_PATH = "${cfg.statePath}/";
+    PUMA_PATH = "${cfg.statePath}/";
     GITLAB_PATH = "${cfg.packages.gitlab}/share/gitlab/";
     SCHEMA = "${cfg.statePath}/db/structure.sql";
     GITLAB_UPLOADS_PATH = "${cfg.statePath}/uploads";
@@ -424,7 +424,7 @@ in {
 
         port = mkOption {
           type = types.int;
-          default = 465;
+          default = 25;
           description = "Port of the SMTP server for Gitlab.";
         };
 
@@ -454,7 +454,7 @@ in {
         authentication = mkOption {
           type = with types; nullOr str;
           default = null;
-          description = "Authentitcation type to use, see http://api.rubyonrails.org/classes/ActionMailer/Base.html";
+          description = "Authentication type to use, see http://api.rubyonrails.org/classes/ActionMailer/Base.html";
         };
 
         enableStartTLSAuto = mkOption {
@@ -641,6 +641,11 @@ in {
 
     environment.systemPackages = [ pkgs.git gitlab-rake gitlab-rails cfg.packages.gitlab-shell ];
 
+    systemd.targets.gitlab = {
+      description = "Common target for all GitLab services.";
+      wantedBy = [ "multi-user.target" ];
+    };
+
     # Redis is required for the sidekiq queue runner.
     services.redis.enable = mkDefault true;
 
@@ -655,36 +660,45 @@ in {
     # here.
     systemd.services.gitlab-postgresql = let pgsql = config.services.postgresql; in mkIf databaseActuallyCreateLocally {
       after = [ "postgresql.service" ];
-      wantedBy = [ "multi-user.target" ];
-      path = [ pgsql.package ];
+      bindsTo = [ "postgresql.service" ];
+      wantedBy = [ "gitlab.target" ];
+      partOf = [ "gitlab.target" ];
+      path = [
+        pgsql.package
+        pkgs.util-linux
+      ];
       script = ''
         set -eu
 
-        PSQL="${pkgs.util-linux}/bin/runuser -u ${pgsql.superUser} -- psql --port=${toString pgsql.port}"
+        PSQL() {
+            psql --port=${toString pgsql.port} "$@"
+        }
 
-        $PSQL -tAc "SELECT 1 FROM pg_database WHERE datname = '${cfg.databaseName}'" | grep -q 1 || $PSQL -tAc 'CREATE DATABASE "${cfg.databaseName}" OWNER "${cfg.databaseUsername}"'
-        current_owner=$($PSQL -tAc "SELECT pg_catalog.pg_get_userbyid(datdba) FROM pg_catalog.pg_database WHERE datname = '${cfg.databaseName}'")
+        PSQL -tAc "SELECT 1 FROM pg_database WHERE datname = '${cfg.databaseName}'" | grep -q 1 || PSQL -tAc 'CREATE DATABASE "${cfg.databaseName}" OWNER "${cfg.databaseUsername}"'
+        current_owner=$(PSQL -tAc "SELECT pg_catalog.pg_get_userbyid(datdba) FROM pg_catalog.pg_database WHERE datname = '${cfg.databaseName}'")
         if [[ "$current_owner" != "${cfg.databaseUsername}" ]]; then
-            $PSQL -tAc 'ALTER DATABASE "${cfg.databaseName}" OWNER TO "${cfg.databaseUsername}"'
+            PSQL -tAc 'ALTER DATABASE "${cfg.databaseName}" OWNER TO "${cfg.databaseUsername}"'
             if [[ -e "${config.services.postgresql.dataDir}/.reassigning_${cfg.databaseName}" ]]; then
                 echo "Reassigning ownership of database ${cfg.databaseName} to user ${cfg.databaseUsername} failed on last boot. Failing..."
                 exit 1
             fi
             touch "${config.services.postgresql.dataDir}/.reassigning_${cfg.databaseName}"
-            $PSQL "${cfg.databaseName}" -tAc "REASSIGN OWNED BY \"$current_owner\" TO \"${cfg.databaseUsername}\""
+            PSQL "${cfg.databaseName}" -tAc "REASSIGN OWNED BY \"$current_owner\" TO \"${cfg.databaseUsername}\""
             rm "${config.services.postgresql.dataDir}/.reassigning_${cfg.databaseName}"
         fi
-        $PSQL '${cfg.databaseName}' -tAc "CREATE EXTENSION IF NOT EXISTS pg_trgm"
-        $PSQL '${cfg.databaseName}' -tAc "CREATE EXTENSION IF NOT EXISTS btree_gist;"
+        PSQL '${cfg.databaseName}' -tAc "CREATE EXTENSION IF NOT EXISTS pg_trgm"
+        PSQL '${cfg.databaseName}' -tAc "CREATE EXTENSION IF NOT EXISTS btree_gist;"
       '';
 
       serviceConfig = {
+        User = pgsql.superUser;
         Type = "oneshot";
+        RemainAfterExit = true;
       };
     };
 
     # Use postfix to send out mails.
-    services.postfix.enable = mkDefault true;
+    services.postfix.enable = mkDefault (cfg.smtp.enable && cfg.smtp.address == "localhost");
 
     users.users.${cfg.user} =
       { group = cfg.group;
@@ -703,7 +717,6 @@ in {
       "d ${cfg.statePath} 0750 ${cfg.user} ${cfg.group} -"
       "d ${cfg.statePath}/builds 0750 ${cfg.user} ${cfg.group} -"
       "d ${cfg.statePath}/config 0750 ${cfg.user} ${cfg.group} -"
-      "d ${cfg.statePath}/config/initializers 0750 ${cfg.user} ${cfg.group} -"
       "d ${cfg.statePath}/db 0750 ${cfg.user} ${cfg.group} -"
       "d ${cfg.statePath}/log 0750 ${cfg.user} ${cfg.group} -"
       "d ${cfg.statePath}/repositories 2770 ${cfg.user} ${cfg.group} -"
@@ -726,13 +739,156 @@ in {
       "L+ /run/gitlab/uploads - - - - ${cfg.statePath}/uploads"
 
       "L+ /run/gitlab/shell-config.yml - - - - ${pkgs.writeText "config.yml" (builtins.toJSON gitlabShellConfig)}"
-
-      "L+ ${cfg.statePath}/config/unicorn.rb - - - - ${./defaultUnicornConfig.rb}"
     ];
 
+
+    systemd.services.gitlab-config = {
+      wantedBy = [ "gitlab.target" ];
+      partOf = [ "gitlab.target" ];
+      path = with pkgs; [
+        jq
+        openssl
+        replace
+        git
+      ];
+      serviceConfig = {
+        Type = "oneshot";
+        User = cfg.user;
+        Group = cfg.group;
+        TimeoutSec = "infinity";
+        Restart = "on-failure";
+        WorkingDirectory = "${cfg.packages.gitlab}/share/gitlab";
+        RemainAfterExit = true;
+
+        ExecStartPre = let
+          preStartFullPrivileges = ''
+            shopt -s dotglob nullglob
+            set -eu
+
+            chown --no-dereference '${cfg.user}':'${cfg.group}' '${cfg.statePath}'/*
+            if [[ -n "$(ls -A '${cfg.statePath}'/config/)" ]]; then
+              chown --no-dereference '${cfg.user}':'${cfg.group}' '${cfg.statePath}'/config/*
+            fi
+          '';
+        in "+${pkgs.writeShellScript "gitlab-pre-start-full-privileges" preStartFullPrivileges}";
+
+        ExecStart = pkgs.writeShellScript "gitlab-config" ''
+          set -eu
+
+          umask u=rwx,g=rx,o=
+
+          cp -f ${cfg.packages.gitlab}/share/gitlab/VERSION ${cfg.statePath}/VERSION
+          rm -rf ${cfg.statePath}/db/*
+          rm -f ${cfg.statePath}/lib
+          find '${cfg.statePath}/config/' -maxdepth 1 -mindepth 1 -type d -execdir rm -rf {} \;
+          cp -rf --no-preserve=mode ${cfg.packages.gitlab}/share/gitlab/config.dist/* ${cfg.statePath}/config
+          cp -rf --no-preserve=mode ${cfg.packages.gitlab}/share/gitlab/db/* ${cfg.statePath}/db
+          ln -sf ${extraGitlabRb} ${cfg.statePath}/config/initializers/extra-gitlab.rb
+
+          ${cfg.packages.gitlab-shell}/bin/install
+
+          ${optionalString cfg.smtp.enable ''
+              install -m u=rw ${smtpSettings} ${cfg.statePath}/config/initializers/smtp_settings.rb
+              ${optionalString (cfg.smtp.passwordFile != null) ''
+                  smtp_password=$(<'${cfg.smtp.passwordFile}')
+                  replace-literal -e '@smtpPassword@' "$smtp_password" '${cfg.statePath}/config/initializers/smtp_settings.rb'
+              ''}
+          ''}
+
+          (
+            umask u=rwx,g=,o=
+
+            openssl rand -hex 32 > ${cfg.statePath}/gitlab_shell_secret
+
+            rm -f '${cfg.statePath}/config/database.yml'
+
+            ${if cfg.databasePasswordFile != null then ''
+                export db_password="$(<'${cfg.databasePasswordFile}')"
+
+                if [[ -z "$db_password" ]]; then
+                  >&2 echo "Database password was an empty string!"
+                  exit 1
+                fi
+
+                jq <${pkgs.writeText "database.yml" (builtins.toJSON databaseConfig)} \
+                   '.production.password = $ENV.db_password' \
+                   >'${cfg.statePath}/config/database.yml'
+              ''
+              else ''
+                jq <${pkgs.writeText "database.yml" (builtins.toJSON databaseConfig)} \
+                   >'${cfg.statePath}/config/database.yml'
+              ''
+            }
+
+            ${utils.genJqSecretsReplacementSnippet
+                gitlabConfig
+                "${cfg.statePath}/config/gitlab.yml"
+            }
+
+            rm -f '${cfg.statePath}/config/secrets.yml'
+
+            export secret="$(<'${cfg.secrets.secretFile}')"
+            export db="$(<'${cfg.secrets.dbFile}')"
+            export otp="$(<'${cfg.secrets.otpFile}')"
+            export jws="$(<'${cfg.secrets.jwsFile}')"
+            jq -n '{production: {secret_key_base: $ENV.secret,
+                    otp_key_base: $ENV.otp,
+                    db_key_base: $ENV.db,
+                    openid_connect_signing_key: $ENV.jws}}' \
+               > '${cfg.statePath}/config/secrets.yml'
+          )
+
+          # We remove potentially broken links to old gitlab-shell versions
+          rm -Rf ${cfg.statePath}/repositories/**/*.git/hooks
+
+          git config --global core.autocrlf "input"
+        '';
+      };
+    };
+
+    systemd.services.gitlab-db-config = {
+      after = [ "gitlab-config.service" "gitlab-postgresql.service" "postgresql.service" ];
+      bindsTo = [
+        "gitlab-config.service"
+      ] ++ optional (cfg.databaseHost == "") "postgresql.service"
+        ++ optional databaseActuallyCreateLocally "gitlab-postgresql.service";
+      wantedBy = [ "gitlab.target" ];
+      partOf = [ "gitlab.target" ];
+      serviceConfig = {
+        Type = "oneshot";
+        User = cfg.user;
+        Group = cfg.group;
+        TimeoutSec = "infinity";
+        Restart = "on-failure";
+        WorkingDirectory = "${cfg.packages.gitlab}/share/gitlab";
+        RemainAfterExit = true;
+
+        ExecStart = pkgs.writeShellScript "gitlab-db-config" ''
+          set -eu
+          umask u=rwx,g=rx,o=
+
+          initial_root_password="$(<'${cfg.initialRootPasswordFile}')"
+          ${gitlab-rake}/bin/gitlab-rake gitlab:db:configure GITLAB_ROOT_PASSWORD="$initial_root_password" \
+                                                             GITLAB_ROOT_EMAIL='${cfg.initialRootEmail}' > /dev/null
+        '';
+      };
+    };
+
     systemd.services.gitlab-sidekiq = {
-      after = [ "network.target" "redis.service" "gitlab.service" ];
-      wantedBy = [ "multi-user.target" ];
+      after = [
+        "network.target"
+        "redis.service"
+        "postgresql.service"
+        "gitlab-config.service"
+        "gitlab-db-config.service"
+      ];
+      bindsTo = [
+        "redis.service"
+        "gitlab-config.service"
+        "gitlab-db-config.service"
+      ] ++ optional (cfg.databaseHost == "") "postgresql.service";
+      wantedBy = [ "gitlab.target" ];
+      partOf = [ "gitlab.target" ];
       environment = gitlabEnv;
       path = with pkgs; [
         postgresqlPackage
@@ -758,9 +914,10 @@ in {
     };
 
     systemd.services.gitaly = {
-      after = [ "network.target" "gitlab.service" ];
-      bindsTo = [ "gitlab.service" ];
-      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" "gitlab-config.service" ];
+      bindsTo = [ "gitlab-config.service" ];
+      wantedBy = [ "gitlab.target" ];
+      partOf = [ "gitlab.target" ];
       path = with pkgs; [
         openssh
         procps  # See https://gitlab.com/gitlab-org/gitaly/issues/1562
@@ -783,8 +940,10 @@ in {
 
     systemd.services.gitlab-pages = mkIf (gitlabConfig.production.pages.enabled or false) {
       description = "GitLab static pages daemon";
-      after = [ "network.target" "redis.service" "gitlab.service" ]; # gitlab.service creates configs
-      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" "gitlab-config.service" ];
+      bindsTo = [ "gitlab-config.service" ];
+      wantedBy = [ "gitlab.target" ];
+      partOf = [ "gitlab.target" ];
 
       path = [ pkgs.unzip ];
 
@@ -803,7 +962,8 @@ in {
 
     systemd.services.gitlab-workhorse = {
       after = [ "network.target" ];
-      wantedBy = [ "multi-user.target" ];
+      wantedBy = [ "gitlab.target" ];
+      partOf = [ "gitlab.target" ];
       path = with pkgs; [
         exiftool
         git
@@ -832,8 +992,10 @@ in {
 
     systemd.services.gitlab-mailroom = mkIf (gitlabConfig.production.incoming_email.enabled or false) {
       description = "GitLab incoming mail daemon";
-      after = [ "network.target" "redis.service" "gitlab.service" ]; # gitlab.service creates configs
-      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" "redis.service" "gitlab-config.service" ];
+      bindsTo = [ "gitlab-config.service" ];
+      wantedBy = [ "gitlab.target" ];
+      partOf = [ "gitlab.target" ];
       environment = gitlabEnv;
       serviceConfig = {
         Type = "simple";
@@ -842,15 +1004,26 @@ in {
 
         User = cfg.user;
         Group = cfg.group;
-        ExecStart = "${cfg.packages.gitlab.rubyEnv}/bin/bundle exec mail_room -c ${cfg.packages.gitlab}/share/gitlab/config.dist/mail_room.yml";
+        ExecStart = "${cfg.packages.gitlab.rubyEnv}/bin/bundle exec mail_room -c ${cfg.statePath}/config/mail_room.yml";
         WorkingDirectory = gitlabEnv.HOME;
       };
     };
 
     systemd.services.gitlab = {
-      after = [ "gitlab-workhorse.service" "network.target" "gitlab-postgresql.service" "redis.service" ];
-      requires = [ "gitlab-sidekiq.service" ];
-      wantedBy = [ "multi-user.target" ];
+      after = [
+        "gitlab-workhorse.service"
+        "network.target"
+        "redis.service"
+        "gitlab-config.service"
+        "gitlab-db-config.service"
+      ];
+      bindsTo = [
+        "redis.service"
+        "gitlab-config.service"
+        "gitlab-db-config.service"
+      ] ++ optional (cfg.databaseHost == "") "postgresql.service";
+      wantedBy = [ "gitlab.target" ];
+      partOf = [ "gitlab.target" ];
       environment = gitlabEnv;
       path = with pkgs; [
         postgresqlPackage
@@ -868,96 +1041,7 @@ in {
         TimeoutSec = "infinity";
         Restart = "on-failure";
         WorkingDirectory = "${cfg.packages.gitlab}/share/gitlab";
-        ExecStartPre = let
-          preStartFullPrivileges = ''
-            shopt -s dotglob nullglob
-            set -eu
-
-            chown --no-dereference '${cfg.user}':'${cfg.group}' '${cfg.statePath}'/*
-            chown --no-dereference '${cfg.user}':'${cfg.group}' '${cfg.statePath}'/config/*
-          '';
-          preStart = ''
-            set -eu
-
-            cp -f ${cfg.packages.gitlab}/share/gitlab/VERSION ${cfg.statePath}/VERSION
-            rm -rf ${cfg.statePath}/db/*
-            rm -rf ${cfg.statePath}/config/initializers/*
-            rm -f ${cfg.statePath}/lib
-            cp -rf --no-preserve=mode ${cfg.packages.gitlab}/share/gitlab/config.dist/* ${cfg.statePath}/config
-            cp -rf --no-preserve=mode ${cfg.packages.gitlab}/share/gitlab/db/* ${cfg.statePath}/db
-            ln -sf ${extraGitlabRb} ${cfg.statePath}/config/initializers/extra-gitlab.rb
-
-            ${cfg.packages.gitlab-shell}/bin/install
-
-            ${optionalString cfg.smtp.enable ''
-              install -m u=rw ${smtpSettings} ${cfg.statePath}/config/initializers/smtp_settings.rb
-              ${optionalString (cfg.smtp.passwordFile != null) ''
-                smtp_password=$(<'${cfg.smtp.passwordFile}')
-                ${pkgs.replace}/bin/replace-literal -e '@smtpPassword@' "$smtp_password" '${cfg.statePath}/config/initializers/smtp_settings.rb'
-              ''}
-            ''}
-
-            (
-              umask u=rwx,g=,o=
-
-              ${pkgs.openssl}/bin/openssl rand -hex 32 > ${cfg.statePath}/gitlab_shell_secret
-
-              if [[ -h '${cfg.statePath}/config/database.yml' ]]; then
-                rm '${cfg.statePath}/config/database.yml'
-              fi
-
-              ${if cfg.databasePasswordFile != null then ''
-                  export db_password="$(<'${cfg.databasePasswordFile}')"
-
-                  if [[ -z "$db_password" ]]; then
-                    >&2 echo "Database password was an empty string!"
-                    exit 1
-                  fi
-
-                  ${pkgs.jq}/bin/jq <${pkgs.writeText "database.yml" (builtins.toJSON databaseConfig)} \
-                                    '.production.password = $ENV.db_password' \
-                                    >'${cfg.statePath}/config/database.yml'
-                ''
-                else ''
-                  ${pkgs.jq}/bin/jq <${pkgs.writeText "database.yml" (builtins.toJSON databaseConfig)} \
-                                    >'${cfg.statePath}/config/database.yml'
-                ''
-              }
-
-              ${utils.genJqSecretsReplacementSnippet
-                  gitlabConfig
-                  "${cfg.statePath}/config/gitlab.yml"
-              }
-
-              if [[ -h '${cfg.statePath}/config/secrets.yml' ]]; then
-                rm '${cfg.statePath}/config/secrets.yml'
-              fi
-
-              export secret="$(<'${cfg.secrets.secretFile}')"
-              export db="$(<'${cfg.secrets.dbFile}')"
-              export otp="$(<'${cfg.secrets.otpFile}')"
-              export jws="$(<'${cfg.secrets.jwsFile}')"
-              ${pkgs.jq}/bin/jq -n '{production: {secret_key_base: $ENV.secret,
-                                                  otp_key_base: $ENV.otp,
-                                                  db_key_base: $ENV.db,
-                                                  openid_connect_signing_key: $ENV.jws}}' \
-                                > '${cfg.statePath}/config/secrets.yml'
-            )
-
-            initial_root_password="$(<'${cfg.initialRootPasswordFile}')"
-            ${gitlab-rake}/bin/gitlab-rake gitlab:db:configure GITLAB_ROOT_PASSWORD="$initial_root_password" \
-                                                               GITLAB_ROOT_EMAIL='${cfg.initialRootEmail}' > /dev/null
-
-            # We remove potentially broken links to old gitlab-shell versions
-            rm -Rf ${cfg.statePath}/repositories/**/*.git/hooks
-
-            ${pkgs.git}/bin/git config --global core.autocrlf "input"
-          '';
-        in [
-          "+${pkgs.writeShellScript "gitlab-pre-start-full-privileges" preStartFullPrivileges}"
-          "${pkgs.writeShellScript "gitlab-pre-start" preStart}"
-        ];
-        ExecStart = "${cfg.packages.gitlab.rubyEnv}/bin/unicorn -c ${cfg.statePath}/config/unicorn.rb -E production";
+        ExecStart = "${cfg.packages.gitlab.rubyEnv}/bin/puma -C ${cfg.statePath}/config/puma.rb -e production";
       };
 
     };
diff --git a/nixos/modules/services/misc/gollum.nix b/nixos/modules/services/misc/gollum.nix
index 0c9c7548305ba..4053afa69be56 100644
--- a/nixos/modules/services/misc/gollum.nix
+++ b/nixos/modules/services/misc/gollum.nix
@@ -115,4 +115,6 @@ in
       };
     };
   };
+
+  meta.maintainers = with lib.maintainers; [ erictapen ];
 }
diff --git a/nixos/modules/services/misc/home-assistant.nix b/nixos/modules/services/misc/home-assistant.nix
index 1f2e13f373257..f53c49a1ee6d1 100644
--- a/nixos/modules/services/misc/home-assistant.nix
+++ b/nixos/modules/services/misc/home-assistant.nix
@@ -183,8 +183,14 @@ in {
     };
 
     package = mkOption {
-      default = pkgs.home-assistant;
-      defaultText = "pkgs.home-assistant";
+      default = pkgs.home-assistant.overrideAttrs (oldAttrs: {
+        doInstallCheck = false;
+      });
+      defaultText = literalExample ''
+        pkgs.home-assistant.overrideAttrs (oldAttrs: {
+          doInstallCheck = false;
+        })
+      '';
       type = types.package;
       example = literalExample ''
         pkgs.home-assistant.override {
@@ -192,7 +198,7 @@ in {
         }
       '';
       description = ''
-        Home Assistant package to use.
+        Home Assistant package to use. By default the tests are disabled, as they take a considerable amout of time to complete.
         Override <literal>extraPackages</literal> or <literal>extraComponents</literal> in order to add additional dependencies.
         If you specify <option>config</option> and do not set <option>autoExtraComponents</option>
         to <literal>false</literal>, overriding <literal>extraComponents</literal> will have no effect.
diff --git a/nixos/modules/services/misc/lifecycled.nix b/nixos/modules/services/misc/lifecycled.nix
new file mode 100644
index 0000000000000..1c8942998d6cd
--- /dev/null
+++ b/nixos/modules/services/misc/lifecycled.nix
@@ -0,0 +1,164 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+let
+  cfg = config.services.lifecycled;
+
+  # TODO: Add the ability to extend this with an rfc 42-like interface.
+  # In the meantime, one can modify the environment (as
+  # long as it's not overriding anything from here) with
+  # systemd.services.lifecycled.serviceConfig.Environment
+  configFile = pkgs.writeText "lifecycled" ''
+    LIFECYCLED_HANDLER=${cfg.handler}
+    ${lib.optionalString (cfg.cloudwatchGroup != null) "LIFECYCLED_CLOUDWATCH_GROUP=${cfg.cloudwatchGroup}"}
+    ${lib.optionalString (cfg.cloudwatchStream != null) "LIFECYCLED_CLOUDWATCH_STREAM=${cfg.cloudwatchStream}"}
+    ${lib.optionalString cfg.debug "LIFECYCLED_DEBUG=${lib.boolToString cfg.debug}"}
+    ${lib.optionalString (cfg.instanceId != null) "LIFECYCLED_INSTANCE_ID=${cfg.instanceId}"}
+    ${lib.optionalString cfg.json "LIFECYCLED_JSON=${lib.boolToString cfg.json}"}
+    ${lib.optionalString cfg.noSpot "LIFECYCLED_NO_SPOT=${lib.boolToString cfg.noSpot}"}
+    ${lib.optionalString (cfg.snsTopic != null) "LIFECYCLED_SNS_TOPIC=${cfg.snsTopic}"}
+    ${lib.optionalString (cfg.awsRegion != null) "AWS_REGION=${cfg.awsRegion}"}
+  '';
+in
+{
+  meta.maintainers = with maintainers; [ cole-h grahamc ];
+
+  options = {
+    services.lifecycled = {
+      enable = mkEnableOption "lifecycled";
+
+      queueCleaner = {
+        enable = mkEnableOption "lifecycled-queue-cleaner";
+
+        frequency = mkOption {
+          type = types.str;
+          default = "hourly";
+          description = ''
+            How often to trigger the queue cleaner.
+
+            NOTE: This string should be a valid value for a systemd
+            timer's <literal>OnCalendar</literal> configuration. See
+            <citerefentry><refentrytitle>systemd.timer</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+            for more information.
+          '';
+        };
+
+        parallel = mkOption {
+          type = types.ints.unsigned;
+          default = 20;
+          description = ''
+            The number of parallel deletes to run.
+          '';
+        };
+      };
+
+      instanceId = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = ''
+          The instance ID to listen for events for.
+        '';
+      };
+
+      snsTopic = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = ''
+          The SNS topic that receives events.
+        '';
+      };
+
+      noSpot = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Disable the spot termination listener.
+        '';
+      };
+
+      handler = mkOption {
+        type = types.path;
+        description = ''
+          The script to invoke to handle events.
+        '';
+      };
+
+      json = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Enable JSON logging.
+        '';
+      };
+
+      cloudwatchGroup = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = ''
+          Write logs to a specific Cloudwatch Logs group.
+        '';
+      };
+
+      cloudwatchStream = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = ''
+          Write logs to a specific Cloudwatch Logs stream. Defaults to the instance ID.
+        '';
+      };
+
+      debug = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Enable debugging information.
+        '';
+      };
+
+      # XXX: Can be removed if / when
+      # https://github.com/buildkite/lifecycled/pull/91 is merged.
+      awsRegion = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = ''
+          The region used for accessing AWS services.
+        '';
+      };
+    };
+  };
+
+  ### Implementation ###
+
+  config = mkMerge [
+    (mkIf cfg.enable {
+      environment.etc."lifecycled".source = configFile;
+
+      systemd.packages = [ pkgs.lifecycled ];
+      systemd.services.lifecycled = {
+        wantedBy = [ "network-online.target" ];
+        restartTriggers = [ configFile ];
+      };
+    })
+
+    (mkIf cfg.queueCleaner.enable {
+      systemd.services.lifecycled-queue-cleaner = {
+        description = "Lifecycle Daemon Queue Cleaner";
+        environment = optionalAttrs (cfg.awsRegion != null) { AWS_REGION = cfg.awsRegion; };
+        serviceConfig = {
+          Type = "oneshot";
+          ExecStart = "${pkgs.lifecycled}/bin/lifecycled-queue-cleaner -parallel ${toString cfg.queueCleaner.parallel}";
+        };
+      };
+
+      systemd.timers.lifecycled-queue-cleaner = {
+        description = "Lifecycle Daemon Queue Cleaner Timer";
+        wantedBy = [ "timers.target" ];
+        after = [ "network-online.target" ];
+        timerConfig = {
+          Unit = "lifecycled-queue-cleaner.service";
+          OnCalendar = "${cfg.queueCleaner.frequency}";
+        };
+      };
+    })
+  ];
+}
diff --git a/nixos/modules/services/misc/nix-gc.nix b/nixos/modules/services/misc/nix-gc.nix
index 12bed05757ad5..a7a6a3b59644e 100644
--- a/nixos/modules/services/misc/nix-gc.nix
+++ b/nixos/modules/services/misc/nix-gc.nix
@@ -21,13 +21,45 @@ in
       };
 
       dates = mkOption {
+        type = types.str;
         default = "03:15";
+        example = "weekly";
+        description = ''
+          How often or when garbage collection is performed. For most desktop and server systems
+          a sufficient garbage collection is once a week.
+
+          The format is described in
+          <citerefentry><refentrytitle>systemd.time</refentrytitle>
+          <manvolnum>7</manvolnum></citerefentry>.
+        '';
+      };
+
+      randomizedDelaySec = mkOption {
+        default = "0";
         type = types.str;
+        example = "45min";
         description = ''
-          Specification (in the format described by
+          Add a randomized delay before each automatic upgrade.
+          The delay will be chosen between zero and this value.
+          This value must be a time span in the format specified by
           <citerefentry><refentrytitle>systemd.time</refentrytitle>
-          <manvolnum>7</manvolnum></citerefentry>) of the time at
-          which the garbage collector will run.
+          <manvolnum>7</manvolnum></citerefentry>
+        '';
+      };
+
+      persistent = mkOption {
+        default = true;
+        type = types.bool;
+        example = false;
+        description = ''
+          Takes a boolean argument. If true, the time when the service
+          unit was last triggered is stored on disk. When the timer is
+          activated, the service unit is triggered immediately if it
+          would have been triggered at least once during the time when
+          the timer was inactive. Such triggering is nonetheless
+          subject to the delay imposed by RandomizedDelaySec=. This is
+          useful to catch up on missed runs of the service when the
+          system was powered down.
         '';
       };
 
@@ -50,11 +82,18 @@ in
 
   config = {
 
-    systemd.services.nix-gc =
-      { description = "Nix Garbage Collector";
-        script = "exec ${config.nix.package.out}/bin/nix-collect-garbage ${cfg.options}";
-        startAt = optional cfg.automatic cfg.dates;
+    systemd.services.nix-gc = {
+      description = "Nix Garbage Collector";
+      script = "exec ${config.nix.package.out}/bin/nix-collect-garbage ${cfg.options}";
+      startAt = optional cfg.automatic cfg.dates;
+    };
+
+    systemd.timers.nix-gc = lib.mkIf cfg.automatic {
+      timerConfig = {
+        RandomizedDelaySec = cfg.randomizedDelaySec;
+        Persistent = cfg.persistent;
       };
+    };
 
   };
 
diff --git a/nixos/modules/services/misc/plikd.nix b/nixos/modules/services/misc/plikd.nix
new file mode 100644
index 0000000000000..a62dbef1d2af3
--- /dev/null
+++ b/nixos/modules/services/misc/plikd.nix
@@ -0,0 +1,82 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+  cfg = config.services.plikd;
+
+  format = pkgs.formats.toml {};
+  plikdCfg = format.generate "plikd.cfg" cfg.settings;
+in
+{
+  options = {
+    services.plikd = {
+      enable = mkEnableOption "the plikd server";
+
+      openFirewall = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Open ports in the firewall for the plikd.";
+      };
+
+      settings = mkOption {
+        type = format.type;
+        default = {};
+        description = ''
+          Configuration for plikd, see <link xlink:href="https://github.com/root-gg/plik/blob/master/server/plikd.cfg"/>
+          for supported values.
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    services.plikd.settings = mapAttrs (name: mkDefault) {
+      ListenPort = 8080;
+      ListenAddress = "localhost";
+      DataBackend = "file";
+      DataBackendConfig = {
+         Directory = "/var/lib/plikd";
+      };
+      MetadataBackendConfig = {
+        Driver = "sqlite3";
+        ConnectionString = "/var/lib/plikd/plik.db";
+      };
+    };
+
+    systemd.services.plikd = {
+      description = "Plikd file sharing server";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig = {
+        Type = "simple";
+        ExecStart = "${pkgs.plikd}/bin/plikd --config ${plikdCfg}";
+        Restart = "on-failure";
+        StateDirectory = "plikd";
+        LogsDirectory = "plikd";
+        DynamicUser = true;
+
+        # Basic hardening
+        NoNewPrivileges = "yes";
+        PrivateTmp = "yes";
+        PrivateDevices = "yes";
+        DevicePolicy = "closed";
+        ProtectSystem = "strict";
+        ProtectHome = "read-only";
+        ProtectControlGroups = "yes";
+        ProtectKernelModules = "yes";
+        ProtectKernelTunables = "yes";
+        RestrictAddressFamilies = "AF_UNIX AF_INET AF_INET6 AF_NETLINK";
+        RestrictNamespaces = "yes";
+        RestrictRealtime = "yes";
+        RestrictSUIDSGID = "yes";
+        MemoryDenyWriteExecute = "yes";
+        LockPersonality = "yes";
+      };
+    };
+
+    networking.firewall = mkIf cfg.openFirewall {
+      allowedTCPPorts = [ cfg.settings.ListenPort ];
+    };
+  };
+}
diff --git a/nixos/modules/services/monitoring/alerta.nix b/nixos/modules/services/monitoring/alerta.nix
index 34f2d41706a56..7c6eff713cb12 100644
--- a/nixos/modules/services/monitoring/alerta.nix
+++ b/nixos/modules/services/monitoring/alerta.nix
@@ -95,13 +95,13 @@ in
         ALERTA_SVR_CONF_FILE = alertaConf;
       };
       serviceConfig = {
-        ExecStart = "${pkgs.python36Packages.alerta-server}/bin/alertad run --port ${toString cfg.port} --host ${cfg.bind}";
+        ExecStart = "${pkgs.alerta-server}/bin/alertad run --port ${toString cfg.port} --host ${cfg.bind}";
         User = "alerta";
         Group = "alerta";
       };
     };
 
-    environment.systemPackages = [ pkgs.python36Packages.alerta ];
+    environment.systemPackages = [ pkgs.alerta ];
 
     users.users.alerta = {
       uid = config.ids.uids.alerta;
diff --git a/nixos/modules/services/monitoring/grafana.nix b/nixos/modules/services/monitoring/grafana.nix
index c8515c4b8988c..88727e70ee321 100644
--- a/nixos/modules/services/monitoring/grafana.nix
+++ b/nixos/modules/services/monitoring/grafana.nix
@@ -65,10 +65,18 @@ let
 
   dashboardFile = pkgs.writeText "dashboard.yaml" (builtins.toJSON dashboardConfiguration);
 
+  notifierConfiguration = {
+    apiVersion = 1;
+    notifiers = cfg.provision.notifiers;
+  };
+
+  notifierFile = pkgs.writeText "notifier.yaml" (builtins.toJSON notifierConfiguration);
+
   provisionConfDir =  pkgs.runCommand "grafana-provisioning" { } ''
-    mkdir -p $out/{datasources,dashboards}
+    mkdir -p $out/{datasources,dashboards,notifiers}
     ln -sf ${datasourceFile} $out/datasources/datasource.yaml
     ln -sf ${dashboardFile} $out/dashboards/dashboard.yaml
+    ln -sf ${notifierFile} $out/notifiers/notifier.yaml
   '';
 
   # Get a submodule without any embedded metadata:
@@ -79,80 +87,80 @@ let
     options = {
       name = mkOption {
         type = types.str;
-        description = "Name of the datasource. Required";
+        description = "Name of the datasource. Required.";
       };
       type = mkOption {
         type = types.enum ["graphite" "prometheus" "cloudwatch" "elasticsearch" "influxdb" "opentsdb" "mysql" "mssql" "postgres" "loki"];
-        description = "Datasource type. Required";
+        description = "Datasource type. Required.";
       };
       access = mkOption {
         type = types.enum ["proxy" "direct"];
         default = "proxy";
-        description = "Access mode. proxy or direct (Server or Browser in the UI). Required";
+        description = "Access mode. proxy or direct (Server or Browser in the UI). Required.";
       };
       orgId = mkOption {
         type = types.int;
         default = 1;
-        description = "Org id. will default to orgId 1 if not specified";
+        description = "Org id. will default to orgId 1 if not specified.";
       };
       url = mkOption {
         type = types.str;
-        description = "Url of the datasource";
+        description = "Url of the datasource.";
       };
       password = mkOption {
         type = types.nullOr types.str;
         default = null;
-        description = "Database password, if used";
+        description = "Database password, if used.";
       };
       user = mkOption {
         type = types.nullOr types.str;
         default = null;
-        description = "Database user, if used";
+        description = "Database user, if used.";
       };
       database = mkOption {
         type = types.nullOr types.str;
         default = null;
-        description = "Database name, if used";
+        description = "Database name, if used.";
       };
       basicAuth = mkOption {
         type = types.nullOr types.bool;
         default = null;
-        description = "Enable/disable basic auth";
+        description = "Enable/disable basic auth.";
       };
       basicAuthUser = mkOption {
         type = types.nullOr types.str;
         default = null;
-        description = "Basic auth username";
+        description = "Basic auth username.";
       };
       basicAuthPassword = mkOption {
         type = types.nullOr types.str;
         default = null;
-        description = "Basic auth password";
+        description = "Basic auth password.";
       };
       withCredentials = mkOption {
         type = types.bool;
         default = false;
-        description = "Enable/disable with credentials headers";
+        description = "Enable/disable with credentials headers.";
       };
       isDefault = mkOption {
         type = types.bool;
         default = false;
-        description = "Mark as default datasource. Max one per org";
+        description = "Mark as default datasource. Max one per org.";
       };
       jsonData = mkOption {
         type = types.nullOr types.attrs;
         default = null;
-        description = "Datasource specific configuration";
+        description = "Datasource specific configuration.";
       };
       secureJsonData = mkOption {
         type = types.nullOr types.attrs;
         default = null;
-        description = "Datasource specific secure configuration";
+        description = "Datasource specific secure configuration.";
       };
       version = mkOption {
         type = types.int;
         default = 1;
-        description = "Version";
+        description = "Version.";
       };
       editable = mkOption {
         type = types.bool;
@@ -168,41 +176,99 @@ let
       name = mkOption {
         type = types.str;
         default = "default";
-        description = "Provider name";
+        description = "Provider name.";
       };
       orgId = mkOption {
         type = types.int;
         default = 1;
-        description = "Organization ID";
+        description = "Organization ID.";
       };
       folder = mkOption {
         type = types.str;
         default = "";
-        description = "Add dashboards to the specified folder";
+        description = "Add dashboards to the specified folder.";
       };
       type = mkOption {
         type = types.str;
         default = "file";
-        description = "Dashboard provider type";
+        description = "Dashboard provider type.";
       };
       disableDeletion = mkOption {
         type = types.bool;
         default = false;
-        description = "Disable deletion when JSON file is removed";
+        description = "Disable deletion when JSON file is removed.";
       };
       updateIntervalSeconds = mkOption {
         type = types.int;
         default = 10;
-        description = "How often Grafana will scan for changed dashboards";
+        description = "How often Grafana will scan for changed dashboards.";
       };
       options = {
         path = mkOption {
           type = types.path;
-          description = "Path grafana will watch for dashboards";
+          description = "Path grafana will watch for dashboards.";
         };
       };
     };
   };
+
+  grafanaTypes.notifierConfig = types.submodule {
+    options = {
+      name = mkOption {
+        type = types.str;
+        default = "default";
+        description = "Notifier name.";
+      };
+      type = mkOption {
+        type = types.enum ["dingding" "discord" "email" "googlechat" "hipchat" "kafka" "line" "teams" "opsgenie" "pagerduty" "prometheus-alertmanager" "pushover" "sensu" "sensugo" "slack" "telegram" "threema" "victorops" "webhook"];
+        description = "Notifier type.";
+      };
+      uid = mkOption {
+        type = types.str;
+        description = "Unique notifier identifier.";
+      };
+      org_id = mkOption {
+        type = types.int;
+        default = 1;
+        description = "Organization ID.";
+      };
+      org_name = mkOption {
+        type = types.str;
+        default = "Main Org.";
+        description = "Organization name.";
+      };
+      is_default = mkOption {
+        type = types.bool;
+        description = "Is the default notifier.";
+        default = false;
+      };
+      send_reminder = mkOption {
+        type = types.bool;
+        default = true;
+        description = "Should the notifier be sent reminder notifications while alerts continue to fire.";
+      };
+      frequency = mkOption {
+        type = types.str;
+        default = "5m";
+        description = "How frequently should the notifier be sent reminders.";
+      };
+      disable_resolve_message = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Turn off the message that sends when an alert returns to OK.";
+      };
+      settings = mkOption {
+        type = types.nullOr types.attrs;
+        default = null;
+        description = "Settings for the notifier type.";
+      };
+      secure_settings = mkOption {
+        type = types.nullOr types.attrs;
+        default = null;
+        description = "Secure settings for the notifier type.";
+      };
+    };
+  };
 in {
   options.services.grafana = {
     enable = mkEnableOption "grafana";
@@ -337,17 +403,23 @@ in {
     provision = {
       enable = mkEnableOption "provision";
       datasources = mkOption {
-        description = "Grafana datasources configuration";
+        description = "Grafana datasources configuration.";
         default = [];
         type = types.listOf grafanaTypes.datasourceConfig;
         apply = x: map _filter x;
       };
       dashboards = mkOption {
-        description = "Grafana dashboard configuration";
+        description = "Grafana dashboard configuration.";
         default = [];
         type = types.listOf grafanaTypes.dashboardConfig;
         apply = x: map _filter x;
       };
+      notifiers = mkOption {
+        description = "Grafana notifier configuration.";
+        default = [];
+        type = types.listOf grafanaTypes.notifierConfig;
+        apply = x: map _filter x;
+      };
     };
 
     security = {
@@ -391,12 +463,12 @@ in {
     smtp = {
       enable = mkEnableOption "smtp";
       host = mkOption {
-        description = "Host to connect to";
+        description = "Host to connect to.";
         default = "localhost:25";
         type = types.str;
       };
       user = mkOption {
-        description = "User used for authentication";
+        description = "User used for authentication.";
         default = "";
         type = types.str;
       };
@@ -417,7 +489,7 @@ in {
         type = types.nullOr types.path;
       };
       fromAddress = mkOption {
-        description = "Email address used for sending";
+        description = "Email address used for sending.";
         default = "admin@grafana.localhost";
         type = types.str;
       };
@@ -425,7 +497,7 @@ in {
 
     users = {
       allowSignUp = mkOption {
-        description = "Disable user signup / registration";
+        description = "Disable user signup / registration.";
         default = false;
         type = types.bool;
       };
@@ -451,17 +523,17 @@ in {
 
     auth.anonymous = {
       enable = mkOption {
-        description = "Whether to allow anonymous access";
+        description = "Whether to allow anonymous access.";
         default = false;
         type = types.bool;
       };
       org_name = mkOption {
-        description = "Which organization to allow anonymous access to";
+        description = "Which organization to allow anonymous access to.";
         default = "Main Org.";
         type = types.str;
       };
       org_role = mkOption {
-        description = "Which role anonymous users have in the organization";
+        description = "Which role anonymous users have in the organization.";
         default = "Viewer";
         type = types.str;
       };
@@ -470,7 +542,7 @@ in {
 
     analytics.reporting = {
       enable = mkOption {
-        description = "Whether to allow anonymous usage reporting to stats.grafana.net";
+        description = "Whether to allow anonymous usage reporting to stats.grafana.net.";
         default = true;
         type = types.bool;
       };
@@ -496,6 +568,9 @@ in {
       (optional (
         any (x: x.password != null || x.basicAuthPassword != null || x.secureJsonData != null) cfg.provision.datasources
       ) "Datasource passwords will be stored as plaintext in the Nix store!")
+      (optional (
+        any (x: x.secure_settings != null) cfg.provision.notifiers
+      ) "Notifier secure settings will be stored as plaintext in the Nix store!")
     ];
 
     environment.systemPackages = [ cfg.package ];
diff --git a/nixos/modules/services/monitoring/prometheus/default.nix b/nixos/modules/services/monitoring/prometheus/default.nix
index 9103a6f932dbb..bd74e1a9cdb54 100644
--- a/nixos/modules/services/monitoring/prometheus/default.nix
+++ b/nixos/modules/services/monitoring/prometheus/default.nix
@@ -468,7 +468,7 @@ let
         '';
       };
 
-      value = mkOption {
+      values = mkOption {
         type = types.listOf types.str;
         default = [];
         description = ''
diff --git a/nixos/modules/services/monitoring/prometheus/exporters.nix b/nixos/modules/services/monitoring/prometheus/exporters.nix
index 81b6b5931eae5..115ecbf12b5e6 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters.nix
@@ -3,7 +3,7 @@
 let
   inherit (lib) concatStrings foldl foldl' genAttrs literalExample maintainers
                 mapAttrsToList mkDefault mkEnableOption mkIf mkMerge mkOption
-                optional types;
+                optional types mkOptionDefault flip attrNames;
 
   cfg = config.services.prometheus.exporters;
 
@@ -22,6 +22,7 @@ let
 
   exporterOpts = genAttrs [
     "apcupsd"
+    "artifactory"
     "bind"
     "bird"
     "blackbox"
@@ -31,6 +32,7 @@ let
     "fritzbox"
     "json"
     "keylight"
+    "knot"
     "lnd"
     "mail"
     "mikrotik"
@@ -65,7 +67,7 @@ let
   mkExporterOpts = ({ name, port }: {
     enable = mkEnableOption "the prometheus ${name} exporter";
     port = mkOption {
-      type = types.int;
+      type = types.port;
       default = port;
       description = ''
         Port to listen on.
@@ -93,9 +95,8 @@ let
       '';
     };
     firewallFilter = mkOption {
-      type = types.str;
-      default = "-p tcp -m tcp --dport ${toString cfg.${name}.port}";
-      defaultText = "-p tcp -m tcp --dport ${toString port}";
+      type = types.nullOr types.str;
+      default = null;
       example = literalExample ''
         "-i eth0 -p tcp -m tcp --dport ${toString port}"
       '';
@@ -123,12 +124,14 @@ let
 
   mkSubModule = { name, port, extraOpts, imports }: {
     ${name} = mkOption {
-      type = types.submodule {
+      type = types.submodule [{
         inherit imports;
         options = (mkExporterOpts {
           inherit name port;
         } // extraOpts);
-      };
+      } ({ config, ... }: mkIf config.openFirewall {
+        firewallFilter = mkOptionDefault "-p tcp -m tcp --dport ${toString config.port}";
+      })];
       internal = true;
       default = {};
     };
@@ -233,7 +236,13 @@ in
         Please specify either 'services.prometheus.exporters.sql.configuration' or
           'services.prometheus.exporters.sql.configFile'
       '';
-    } ];
+    } ] ++ (flip map (attrNames cfg) (exporter: {
+      assertion = cfg.${exporter}.firewallFilter != null -> cfg.${exporter}.openFirewall;
+      message = ''
+        The `firewallFilter'-option of exporter ${exporter} doesn't have any effect unless
+        `openFirewall' is set to `true'!
+      '';
+    }));
   }] ++ [(mkIf config.services.minio.enable {
     services.prometheus.exporters.minio.minioAddress  = mkDefault "http://localhost:9000";
     services.prometheus.exporters.minio.minioAccessKey = mkDefault config.services.minio.accessKey;
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/artifactory.nix b/nixos/modules/services/monitoring/prometheus/exporters/artifactory.nix
new file mode 100644
index 0000000000000..2adcecc728bde
--- /dev/null
+++ b/nixos/modules/services/monitoring/prometheus/exporters/artifactory.nix
@@ -0,0 +1,59 @@
+{ config, lib, pkgs, options }:
+
+with lib;
+
+let
+  cfg = config.services.prometheus.exporters.artifactory;
+in
+{
+  port = 9531;
+  extraOpts = {
+    scrapeUri = mkOption {
+      type = types.str;
+      default = "http://localhost:8081/artifactory";
+      description = ''
+        URI on which to scrape JFrog Artifactory.
+      '';
+    };
+
+    artiUsername = mkOption {
+      type = types.str;
+      description = ''
+        Username for authentication against JFrog Artifactory API.
+      '';
+    };
+
+    artiPassword = mkOption {
+      type = types.str;
+      default = "";
+      description = ''
+        Password for authentication against JFrog Artifactory API.
+        One of the password or access token needs to be set.
+      '';
+    };
+
+    artiAccessToken = mkOption {
+      type = types.str;
+      default = "";
+      description = ''
+        Access token for authentication against JFrog Artifactory API.
+        One of the password or access token needs to be set.
+      '';
+    };
+  };
+  serviceOpts = {
+    serviceConfig = {
+      ExecStart = ''
+        ${pkgs.prometheus-artifactory-exporter}/bin/artifactory_exporter \
+          --web.listen-address ${cfg.listenAddress}:${toString cfg.port} \
+          --artifactory.scrape-uri ${cfg.scrapeUri} \
+          ${concatStringsSep " \\\n  " cfg.extraFlags}
+      '';
+      Environment = [
+        "ARTI_USERNAME=${cfg.artiUsername}"
+        "ARTI_PASSWORD=${cfg.artiPassword}"
+        "ARTI_ACCESS_TOKEN=${cfg.artiAccessToken}"
+      ];
+    };
+  };
+}
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/knot.nix b/nixos/modules/services/monitoring/prometheus/exporters/knot.nix
new file mode 100644
index 0000000000000..46c28fe0a5781
--- /dev/null
+++ b/nixos/modules/services/monitoring/prometheus/exporters/knot.nix
@@ -0,0 +1,50 @@
+{ config, lib, pkgs, options }:
+
+with lib;
+
+let
+  cfg = config.services.prometheus.exporters.knot;
+in {
+  port = 9433;
+  extraOpts = {
+    knotLibraryPath = mkOption {
+      type = types.str;
+      default = "${pkgs.knot-dns.out}/lib/libknot.so";
+      defaultText = "\${pkgs.knot-dns}/lib/libknot.so";
+      description = ''
+        Path to the library of <package>knot-dns</package>.
+      '';
+    };
+
+    knotSocketPath = mkOption {
+      type = types.str;
+      default = "/run/knot/knot.sock";
+      description = ''
+        Socket path of <citerefentry><refentrytitle>knotd</refentrytitle>
+        <manvolnum>8</manvolnum></citerefentry>.
+      '';
+    };
+
+    knotSocketTimeout = mkOption {
+      type = types.int;
+      default = 2000;
+      description = ''
+        Timeout in seconds.
+      '';
+    };
+  };
+  serviceOpts = {
+    serviceConfig = {
+      ExecStart = ''
+        ${pkgs.prometheus-knot-exporter}/bin/knot_exporter \
+          --web-listen-addr ${cfg.listenAddress} \
+          --web-listen-port ${toString cfg.port} \
+          --knot-library-path ${cfg.knotLibraryPath} \
+          --knot-socket-path ${cfg.knotSocketPath} \
+          --knot-socket-timeout ${toString cfg.knotSocketTimeout} \
+          ${concatStringsSep " \\\n  " cfg.extraFlags}
+      '';
+      SupplementaryGroups = [ "knot" ];
+    };
+  };
+}
diff --git a/nixos/modules/services/network-filesystems/ceph.nix b/nixos/modules/services/network-filesystems/ceph.nix
index 632c3fb1059db..d833062c47370 100644
--- a/nixos/modules/services/network-filesystems/ceph.nix
+++ b/nixos/modules/services/network-filesystems/ceph.nix
@@ -316,7 +316,7 @@ in
     client = {
       enable = mkEnableOption "Ceph client configuration";
       extraConfig = mkOption {
-        type = with types; attrsOf str;
+        type = with types; attrsOf (attrsOf str);
         default = {};
         example = ''
           {
diff --git a/nixos/modules/services/networking/dhcpcd.nix b/nixos/modules/services/networking/dhcpcd.nix
index d10bffd914743..31e4b6ad2988c 100644
--- a/nixos/modules/services/networking/dhcpcd.nix
+++ b/nixos/modules/services/networking/dhcpcd.nix
@@ -191,9 +191,8 @@ in
       { description = "DHCP Client";
 
         wantedBy = [ "multi-user.target" ] ++ optional (!hasDefaultGatewaySet) "network-online.target";
-        wants = [ "network.target" "systemd-udev-settle.service" ];
+        wants = [ "network.target" ];
         before = [ "network-online.target" ];
-        after = [ "systemd-udev-settle.service" ];
 
         restartTriggers = [ exitHook ];
 
diff --git a/nixos/modules/services/networking/dnscrypt-proxy2.nix b/nixos/modules/services/networking/dnscrypt-proxy2.nix
index afc2a6d1c757c..72965c267a861 100644
--- a/nixos/modules/services/networking/dnscrypt-proxy2.nix
+++ b/nixos/modules/services/networking/dnscrypt-proxy2.nix
@@ -113,7 +113,6 @@ in
           "~@memlock"
           "~@resources"
           "~@setuid"
-          "~@sync"
           "~@timer"
         ];
       };
diff --git a/nixos/modules/services/networking/flannel.nix b/nixos/modules/services/networking/flannel.nix
index 4c040112d28d4..32a7eb3ed69e8 100644
--- a/nixos/modules/services/networking/flannel.nix
+++ b/nixos/modules/services/networking/flannel.nix
@@ -162,10 +162,7 @@ in {
         NODE_NAME = cfg.nodeName;
       };
       path = [ pkgs.iptables ];
-      preStart = ''
-        mkdir -p /run/flannel
-        touch /run/flannel/docker
-      '' + optionalString (cfg.storageBackend == "etcd") ''
+      preStart = optionalString (cfg.storageBackend == "etcd") ''
         echo "setting network configuration"
         until ${pkgs.etcdctl}/bin/etcdctl set /coreos.com/network/config '${builtins.toJSON networkConfig}'
         do
@@ -177,6 +174,7 @@ in {
         ExecStart = "${cfg.package}/bin/flannel";
         Restart = "always";
         RestartSec = "10s";
+        RuntimeDirectory = "flannel";
       };
     };
 
diff --git a/nixos/modules/services/networking/kresd.nix b/nixos/modules/services/networking/kresd.nix
index 4131ff8be5d02..d7208354d4a76 100644
--- a/nixos/modules/services/networking/kresd.nix
+++ b/nixos/modules/services/networking/kresd.nix
@@ -8,9 +8,9 @@ let
   # Convert systemd-style address specification to kresd config line(s).
   # On Nix level we don't attempt to precisely validate the address specifications.
   mkListen = kind: addr: let
-    al_v4 = builtins.match "([0-9.]\+):([0-9]\+)" addr;
-    al_v6 = builtins.match "\\[(.\+)]:([0-9]\+)" addr;
-    al_portOnly = builtins.match "()([0-9]\+)" addr;
+    al_v4 = builtins.match "([0-9.]+):([0-9]+)" addr;
+    al_v6 = builtins.match "\\[(.+)]:([0-9]+)" addr;
+    al_portOnly = builtins.match "()([0-9]+)" addr;
     al = findFirst (a: a != null)
       (throw "services.kresd.*: incorrect address specification '${addr}'")
       [ al_v4 al_v6 al_portOnly ];
diff --git a/nixos/modules/services/networking/privoxy.nix b/nixos/modules/services/networking/privoxy.nix
index 7caae3282032c..7c22b7d09b9bd 100644
--- a/nixos/modules/services/networking/privoxy.nix
+++ b/nixos/modules/services/networking/privoxy.nix
@@ -4,26 +4,46 @@ with lib;
 
 let
 
-  inherit (pkgs) privoxy;
-
   cfg = config.services.privoxy;
 
-  confFile = pkgs.writeText "privoxy.conf" (''
-    user-manual ${privoxy}/share/doc/privoxy/user-manual
-    confdir ${privoxy}/etc/
-    listen-address  ${cfg.listenAddress}
-    enable-edit-actions ${if (cfg.enableEditActions == true) then "1" else "0"}
-    ${concatMapStrings (f: "actionsfile ${f}\n") cfg.actionsFiles}
-    ${concatMapStrings (f: "filterfile ${f}\n") cfg.filterFiles}
-  '' + optionalString cfg.enableTor ''
-    forward-socks5t / 127.0.0.1:9063 .
-    toggle 1
-    enable-remote-toggle 0
-    enable-edit-actions 0
-    enable-remote-http-toggle 0
-  '' + ''
-    ${cfg.extraConfig}
-  '');
+  serialise = name: val:
+         if isList val then concatMapStrings (serialise name) val
+    else if isBool val then serialise name (if val then "1" else "0")
+    else "${name} ${toString val}\n";
+
+  configType = with types;
+    let atom = oneOf [ int bool string path ];
+    in attrsOf (either atom (listOf atom))
+    // { description = ''
+          privoxy configuration type. The format consists of an attribute
+          set of settings. Each setting can be either a value (integer, string,
+          boolean or path) or a list of such values.
+        '';
+       };
+
+  ageType = types.str // {
+    check = x:
+      isString x &&
+      (builtins.match "([0-9]+([smhdw]|min|ms|us)*)+" x != null);
+    description = "tmpfiles.d(5) age format";
+  };
+
+  configFile = pkgs.writeText "privoxy.conf"
+    (concatStrings (
+      # Relative paths in some options are relative to confdir. Privoxy seems
+      # to parse the options in order of appearance, so this must come first.
+      # Nix however doesn't preserve the order in attrsets, so we have to
+      # hardcode confdir here.
+      [ "confdir ${pkgs.privoxy}/etc\n" ]
+      ++ mapAttrsToList serialise cfg.settings
+    ));
+
+  inspectAction = pkgs.writeText "inspect-all-https.action"
+    ''
+      # Enable HTTPS inspection for all requests
+      {+https-inspection}
+      /
+    '';
 
 in
 
@@ -31,70 +51,144 @@ in
 
   ###### interface
 
-  options = {
+  options.services.privoxy = {
 
-    services.privoxy = {
+    enable = mkEnableOption "Privoxy, non-caching filtering proxy";
 
-      enable = mkOption {
-        type = types.bool;
-        default = false;
-        description = ''
-          Whether to enable the Privoxy non-caching filtering proxy.
-        '';
-      };
+    enableTor = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Whether to configure Privoxy to use Tor's faster SOCKS port,
+        suitable for HTTP.
+      '';
+    };
 
-      listenAddress = mkOption {
-        type = types.str;
-        default = "127.0.0.1:8118";
-        description = ''
-          Address the proxy server is listening to.
-        '';
-      };
+    inspectHttps = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Whether to configure Privoxy to inspect HTTPS requests, meaning all
+        encrypted traffic will be filtered as well. This works by decrypting
+        and re-encrypting the requests using a per-domain generated certificate.
 
-      actionsFiles = mkOption {
-        type = types.listOf types.str;
-        example = [ "match-all.action" "default.action" "/etc/privoxy/user.action" ];
-        default = [ "match-all.action" "default.action" ];
-        description = ''
-          List of paths to Privoxy action files.
-          These paths may either be absolute or relative to the privoxy configuration directory.
-        '';
-      };
+        To issue per-domain certificates, Privoxy must be provided with a CA
+        certificate, using the <literal>ca-cert-file</literal>,
+        <literal>ca-key-file</literal> settings.
 
-      filterFiles = mkOption {
-        type = types.listOf types.str;
-        example = [ "default.filter" "/etc/privoxy/user.filter" ];
-        default = [ "default.filter" ];
-        description = ''
-          List of paths to Privoxy filter files.
-          These paths may either be absolute or relative to the privoxy configuration directory.
-        '';
-      };
+        <warning><para>
+          The CA certificate must also be added to the system trust roots,
+          otherwise browsers will reject all Privoxy certificates as invalid.
+          You can do so by using the option
+          <option>security.pki.certificateFiles</option>.
+        </para></warning>
+      '';
+    };
 
-      enableEditActions = mkOption {
-        type = types.bool;
-        default = false;
-        description = ''
-          Whether or not the web-based actions file editor may be used.
-        '';
-      };
+    certsLifetime = mkOption {
+      type = ageType;
+      default = "10d";
+      example = "12h";
+      description = ''
+        If <literal>inspectHttps</literal> is enabled, the time generated HTTPS
+        certificates will be stored in a temporary directory for reuse. Once
+        the lifetime has expired the directory will cleared and the certificate
+        will have to be generated again, on-demand.
 
-      enableTor = mkOption {
-        type = types.bool;
-        default = false;
-        description = ''
-          Whether to configure Privoxy to use Tor's faster SOCKS port,
-          suitable for HTTP.
-        '';
-      };
+        Depending on the traffic, you may want to reduce the lifetime to limit
+        the disk usage, since Privoxy itself never deletes the certificates.
 
-      extraConfig = mkOption {
-        type = types.lines;
-        default = "" ;
-        description = ''
-          Extra configuration. Contents will be added verbatim to the configuration file.
-        '';
+        <note><para>The format is that of the <literal>tmpfiles.d(5)</literal>
+        Age parameter.</para></note>
+      '';
+    };
+
+    userActions = mkOption {
+      type = types.lines;
+      default = "";
+      description = ''
+        Actions to be included in a <literal>user.action</literal> file. This
+        will have a higher priority and can be used to override all other
+        actions.
+      '';
+    };
+
+    userFilters = mkOption {
+      type = types.lines;
+      default = "";
+      description = ''
+        Filters to be included in a <literal>user.filter</literal> file. This
+        will have a higher priority and can be used to override all other
+        filters definitions.
+      '';
+    };
+
+    settings = mkOption {
+      type = types.submodule {
+        freeformType = configType;
+
+        options.listen-address = mkOption {
+          type = types.str;
+          default = "127.0.0.1:8118";
+          description = "Pair of address:port the proxy server is listening to.";
+        };
+
+        options.enable-edit-actions = mkOption {
+          type = types.bool;
+          default = false;
+          description = "Whether the web-based actions file editor may be used.";
+        };
+
+        options.actionsfile = mkOption {
+          type = types.listOf types.str;
+          # This must come after all other entries, in order to override the
+          # other actions/filters installed by Privoxy or the user.
+          apply = x: x ++ optional (cfg.userActions != "")
+            (toString (pkgs.writeText "user.actions" cfg.userActions));
+          default = [ "match-all.action" "default.action" ];
+          description = ''
+            List of paths to Privoxy action files. These paths may either be
+            absolute or relative to the privoxy configuration directory.
+          '';
+        };
+
+        options.filterfile = mkOption {
+          type = types.listOf types.str;
+          default = [ "default.filter" ];
+          apply = x: x ++ optional (cfg.userFilters != "")
+            (toString (pkgs.writeText "user.filter" cfg.userFilters));
+          description = ''
+            List of paths to Privoxy filter files. These paths may either be
+            absolute or relative to the privoxy configuration directory.
+          '';
+        };
       };
+      default = {};
+      example = literalExample ''
+        { # Listen on IPv6 only
+          listen-address = "[::]:8118";
+
+          # Forward .onion requests to Tor
+          forward-socks5 = ".onion localhost:9050 .";
+
+          # Log redirects and filters
+          debug = [ 128 64 ];
+          # This is equivalent to writing these lines
+          # in the Privoxy configuration file:
+          # debug 128
+          # debug 64
+        }
+      '';
+      description = ''
+        This option is mapped to the main Privoxy configuration file.
+        Check out the Privoxy user manual at
+        <link xlink:href="https://www.privoxy.org/user-manual/config.html"/>
+        for available settings and documentation.
+
+        <note><para>
+          Repeated settings can be represented by using a list.
+        </para></note>
+      '';
     };
 
   };
@@ -104,23 +198,33 @@ in
   config = mkIf cfg.enable {
 
     users.users.privoxy = {
+      description = "Privoxy daemon user";
       isSystemUser = true;
-      home = "/var/empty";
       group = "privoxy";
     };
 
     users.groups.privoxy = {};
 
+    systemd.tmpfiles.rules = optional cfg.inspectHttps
+      "d ${cfg.settings.certificate-directory} 0770 privoxy privoxy ${cfg.certsLifetime}";
+
     systemd.services.privoxy = {
       description = "Filtering web proxy";
       after = [ "network.target" "nss-lookup.target" ];
       wantedBy = [ "multi-user.target" ];
-      serviceConfig.ExecStart = "${privoxy}/bin/privoxy --no-daemon --user privoxy ${confFile}";
-
-      serviceConfig.PrivateDevices = true;
-      serviceConfig.PrivateTmp = true;
-      serviceConfig.ProtectHome = true;
-      serviceConfig.ProtectSystem = "full";
+      serviceConfig = {
+        User = "privoxy";
+        Group = "privoxy";
+        ExecStart = "${pkgs.privoxy}/bin/privoxy --no-daemon ${configFile}";
+        PrivateDevices = true;
+        PrivateTmp = true;
+        ProtectHome = true;
+        ProtectSystem = "full";
+      };
+      unitConfig =  mkIf cfg.inspectHttps {
+        ConditionPathExists = with cfg.settings;
+          [ ca-cert-file ca-key-file ];
+      };
     };
 
     services.tor.settings.SOCKSPort = mkIf cfg.enableTor [
@@ -128,8 +232,48 @@ in
       { addr = "127.0.0.1"; port = 9063; IsolateDestAddr = false; }
     ];
 
+    services.privoxy.settings = {
+      user-manual = "${pkgs.privoxy}/share/doc/privoxy/user-manual";
+      # This is needed for external filters
+      temporary-directory = "/tmp";
+      filterfile = [ "default.filter" ];
+      actionsfile =
+        [ "match-all.action"
+          "default.action"
+        ] ++ optional cfg.inspectHttps (toString inspectAction);
+    } // (optionalAttrs cfg.enableTor {
+      forward-socks5 = "127.0.0.1:9063 .";
+      toggle = true;
+      enable-remote-toggle = false;
+      enable-edit-actions = false;
+      enable-remote-http-toggle = false;
+    }) // (optionalAttrs cfg.inspectHttps {
+      # This allows setting absolute key/crt paths
+      ca-directory = "/var/empty";
+      certificate-directory = "/run/privoxy/certs";
+      trusted-cas-file = "/etc/ssl/certs/ca-certificates.crt";
+    });
+
   };
 
+  imports =
+    let
+      top = x: [ "services" "privoxy" x ];
+      setting = x: [ "services" "privoxy" "settings" x ];
+    in
+    [ (mkRenamedOptionModule (top "enableEditActions") (setting "enable-edit-actions"))
+      (mkRenamedOptionModule (top "listenAddress") (setting "listen-address"))
+      (mkRenamedOptionModule (top "actionsFiles") (setting "actionsfile"))
+      (mkRenamedOptionModule (top "filterFiles") (setting "filterfile"))
+      (mkRemovedOptionModule (top "extraConfig")
+      ''
+        Use services.privoxy.settings instead.
+        This is part of the general move to use structured settings instead of raw
+        text for config as introduced by RFC0042:
+        https://github.com/NixOS/rfcs/blob/master/rfcs/0042-config-option.md
+      '')
+    ];
+
   meta.maintainers = with lib.maintainers; [ rnhmjoj ];
 
 }
diff --git a/nixos/modules/services/printing/cupsd.nix b/nixos/modules/services/printing/cupsd.nix
index e67badfcd29eb..b19cd07325266 100644
--- a/nixos/modules/services/printing/cupsd.nix
+++ b/nixos/modules/services/printing/cupsd.nix
@@ -270,7 +270,7 @@ in
       drivers = mkOption {
         type = types.listOf types.path;
         default = [];
-        example = literalExample "with pkgs; [ gutenprint hplip splix cups-googlecloudprint ]";
+        example = literalExample "with pkgs; [ gutenprint hplip splix ]";
         description = ''
           CUPS drivers to use. Drivers provided by CUPS, cups-filters,
           Ghostscript and Samba are added unconditionally. If this list contains
diff --git a/nixos/modules/services/security/clamav.nix b/nixos/modules/services/security/clamav.nix
index aaf6fb0479baf..340cbbf02fb40 100644
--- a/nixos/modules/services/security/clamav.nix
+++ b/nixos/modules/services/security/clamav.nix
@@ -8,30 +8,19 @@ let
   cfg = config.services.clamav;
   pkg = pkgs.clamav;
 
-  clamdConfigFile = pkgs.writeText "clamd.conf" ''
-    DatabaseDirectory ${stateDir}
-    LocalSocket ${runDir}/clamd.ctl
-    PidFile ${runDir}/clamd.pid
-    TemporaryDirectory /tmp
-    User clamav
-    Foreground yes
-
-    ${cfg.daemon.extraConfig}
-  '';
-
-  freshclamConfigFile = pkgs.writeText "freshclam.conf" ''
-    DatabaseDirectory ${stateDir}
-    Foreground yes
-    Checks ${toString cfg.updater.frequency}
-
-    ${cfg.updater.extraConfig}
-
-    DatabaseMirror database.clamav.net
-  '';
+  toKeyValue = generators.toKeyValue {
+    mkKeyValue = generators.mkKeyValueDefault {} " ";
+    listsAsDuplicateKeys = true;
+  };
+
+  clamdConfigFile = pkgs.writeText "clamd.conf" (toKeyValue cfg.daemon.settings);
+  freshclamConfigFile = pkgs.writeText "freshclam.conf" (toKeyValue cfg.updater.settings);
 in
 {
   imports = [
-    (mkRenamedOptionModule [ "services" "clamav" "updater" "config" ] [ "services" "clamav" "updater" "extraConfig" ])
+    (mkRemovedOptionModule [ "services" "clamav" "updater" "config" ] "Use services.clamav.updater.settings instead.")
+    (mkRemovedOptionModule [ "services" "clamav" "updater" "extraConfig" ] "Use services.clamav.updater.settings instead.")
+    (mkRemovedOptionModule [ "services" "clamav" "daemon" "extraConfig" ] "Use services.clamav.daemon.settings instead.")
   ];
 
   options = {
@@ -39,12 +28,12 @@ in
       daemon = {
         enable = mkEnableOption "ClamAV clamd daemon";
 
-        extraConfig = mkOption {
-          type = types.lines;
-          default = "";
+        settings = mkOption {
+          type = with types; attrsOf (oneOf [ bool int str (listOf str) ]);
+          default = {};
           description = ''
-            Extra configuration for clamd. Contents will be added verbatim to the
-            configuration file.
+            ClamAV configuration. Refer to <link xlink:href="https://linux.die.net/man/5/clamd.conf"/>,
+            for details on supported values.
           '';
         };
       };
@@ -68,12 +57,12 @@ in
           '';
         };
 
-        extraConfig = mkOption {
-          type = types.lines;
-          default = "";
+        settings = mkOption {
+          type = with types; attrsOf (oneOf [ bool int str (listOf str) ]);
+          default = {};
           description = ''
-            Extra configuration for freshclam. Contents will be added verbatim to the
-            configuration file.
+            freshclam configuration. Refer to <link xlink:href="https://linux.die.net/man/5/freshclam.conf"/>,
+            for details on supported values.
           '';
         };
       };
@@ -93,6 +82,22 @@ in
     users.groups.${clamavGroup} =
       { gid = config.ids.gids.clamav; };
 
+    services.clamav.daemon.settings = {
+      DatabaseDirectory = stateDir;
+      LocalSocket = "${runDir}/clamd.ctl";
+      PidFile = "${runDir}/clamd.pid";
+      TemporaryDirectory = "/tmp";
+      User = "clamav";
+      Foreground = true;
+    };
+
+    services.clamav.updater.settings = {
+      DatabaseDirectory = stateDir;
+      Foreground = true;
+      Checks = cfg.updater.frequency;
+      DatabaseMirror = [ "database.clamav.net" ];
+    };
+
     environment.etc."clamav/freshclam.conf".source = freshclamConfigFile;
     environment.etc."clamav/clamd.conf".source = clamdConfigFile;
 
diff --git a/nixos/modules/services/ttys/kmscon.nix b/nixos/modules/services/ttys/kmscon.nix
index dc37f9bee4b36..4fe720bf044bc 100644
--- a/nixos/modules/services/ttys/kmscon.nix
+++ b/nixos/modules/services/ttys/kmscon.nix
@@ -82,11 +82,8 @@ in {
       X-RestartIfChanged=false
     '';
 
-    systemd.units."autovt@.service".unit = pkgs.runCommand "unit" { preferLocalBuild = true; }
-        ''
-          mkdir -p $out
-          ln -s ${config.systemd.units."kmsconvt@.service".unit}/kmsconvt@.service $out/autovt@.service
-        '';
+    systemd.suppressedSystemUnits = [ "autovt@.service" ];
+    systemd.units."kmsconvt@.service".aliases = [ "autovt@.service" ];
 
     systemd.services.systemd-vconsole-setup.enable = false;
 
diff --git a/nixos/modules/services/web-apps/dokuwiki.nix b/nixos/modules/services/web-apps/dokuwiki.nix
index 9567223ebc7b5..1ce584c6a46dc 100644
--- a/nixos/modules/services/web-apps/dokuwiki.nix
+++ b/nixos/modules/services/web-apps/dokuwiki.nix
@@ -329,7 +329,7 @@ in
           extraConfig = "internal;";
         };
 
-        locations."~ ^/lib.*\.(js|css|gif|png|ico|jpg|jpeg)$" = {
+        locations."~ ^/lib.*\\.(js|css|gif|png|ico|jpg|jpeg)$" = {
           extraConfig = "expires 365d;";
         };
 
@@ -349,7 +349,7 @@ in
           '';
         };
 
-        locations."~ \.php$" = {
+        locations."~ \\.php$" = {
           extraConfig = ''
               try_files $uri $uri/ /doku.php;
               include ${pkgs.nginx}/conf/fastcgi_params;
diff --git a/nixos/modules/services/web-apps/mastodon.nix b/nixos/modules/services/web-apps/mastodon.nix
index 92b1be963bf7f..ea7aebc3b12d5 100644
--- a/nixos/modules/services/web-apps/mastodon.nix
+++ b/nixos/modules/services/web-apps/mastodon.nix
@@ -25,10 +25,28 @@ let
     ES_ENABLED = if (cfg.elasticsearch.host != null) then "true" else "false";
     ES_HOST = cfg.elasticsearch.host;
     ES_PORT = toString(cfg.elasticsearch.port);
+
+    TRUSTED_PROXY_IP = cfg.trustedProxy;
   }
   // (if cfg.smtp.authenticate then { SMTP_LOGIN  = cfg.smtp.user; } else {})
   // cfg.extraConfig;
 
+  cfgService = {
+    # User and group
+    User = cfg.user;
+    Group = cfg.group;
+    # State directory and mode
+    StateDirectory = "mastodon";
+    StateDirectoryMode = "0750";
+    # Logs directory and mode
+    LogsDirectory = "mastodon";
+    LogsDirectoryMode = "0750";
+    # Access write directories
+    UMask = "0027";
+    # Sandboxing
+    PrivateTmp = true;
+  };
+
   envFile = pkgs.writeText "mastodon.env" (lib.concatMapStrings (s: s + "\n") (
     (lib.concatLists (lib.mapAttrsToList (name: value:
       if value != null then [
@@ -93,7 +111,6 @@ in {
       group = lib.mkOption {
         description = ''
           Group under which mastodon runs.
-          If it is set to "mastodon", a group will be created.
         '';
         type = lib.types.str;
         default = "mastodon";
@@ -179,6 +196,26 @@ in {
         type = lib.types.str;
       };
 
+      trustedProxy = lib.mkOption {
+        description = ''
+          You need to set it to the IP from which your reverse proxy sends requests to Mastodon's web process,
+          otherwise Mastodon will record the reverse proxy's own IP as the IP of all requests, which would be
+          bad because IP addresses are used for important rate limits and security functions.
+        '';
+        type = lib.types.str;
+        default = "127.0.0.1";
+      };
+
+      enableUnixSocket = lib.mkOption {
+        description = ''
+          Instead of binding to an IP address like 127.0.0.1, you may bind to a Unix socket. This variable
+          is process-specific, e.g. you need different values for every process, and it works for both web (Puma)
+          processes and streaming API (Node.js) processes.
+        '';
+        type = lib.types.bool;
+        default = true;
+      };
+
       redis = {
         createLocally = lib.mkOption {
           description = "Configure local Redis server for Mastodon.";
@@ -370,19 +407,16 @@ in {
       environment = env;
       serviceConfig = {
         Type = "oneshot";
-        User = cfg.user;
-        Group = cfg.group;
         WorkingDirectory = cfg.package;
-        LogsDirectory = "mastodon";
-        StateDirectory = "mastodon";
-      };
+      } // cfgService;
+
       after = [ "network.target" ];
       wantedBy = [ "multi-user.target" ];
     };
 
     systemd.services.mastodon-init-db = lib.mkIf cfg.automaticMigrations {
       script = ''
-        if [ `psql mastodon -c \
+        if [ `psql ${cfg.database.name} -c \
                 "select count(*) from pg_class c \
                 join pg_namespace s on s.oid = c.relnamespace \
                 where s.nspname not in ('pg_catalog', 'pg_toast', 'information_schema') \
@@ -397,14 +431,9 @@ in {
       environment = env;
       serviceConfig = {
         Type = "oneshot";
-        User = cfg.user;
-        Group = cfg.group;
         EnvironmentFile = "/var/lib/mastodon/.secrets_env";
-        PrivateTmp = true;
-        LogsDirectory = "mastodon";
-        StateDirectory = "mastodon";
         WorkingDirectory = cfg.package;
-      };
+      } // cfgService;
       after = [ "mastodon-init-dirs.service" "network.target" ] ++ (if databaseActuallyCreateLocally then [ "postgresql.service" ] else []);
       wantedBy = [ "multi-user.target" ];
     };
@@ -415,21 +444,20 @@ in {
         ++ (if cfg.automaticMigrations then [ "mastodon-init-db.service" ] else [ "mastodon-init-dirs.service" ]);
       description = "Mastodon streaming";
       wantedBy = [ "multi-user.target" ];
-      environment = env // {
-        PORT = toString(cfg.streamingPort);
-      };
+      environment = env // (if cfg.enableUnixSocket
+        then { SOCKET = "/run/mastodon-streaming/streaming.socket"; }
+        else { PORT = toString(cfg.streamingPort); }
+      );
       serviceConfig = {
-        ExecStart = "${pkgs.nodejs-slim}/bin/node streaming";
+        ExecStart = "${cfg.package}/run-streaming.sh";
         Restart = "always";
         RestartSec = 20;
-        User = cfg.user;
-        Group = cfg.group;
-        WorkingDirectory = cfg.package;
         EnvironmentFile = "/var/lib/mastodon/.secrets_env";
-        PrivateTmp = true;
-        LogsDirectory = "mastodon";
-        StateDirectory = "mastodon";
-      };
+        WorkingDirectory = cfg.package;
+        # Runtime directory and mode
+        RuntimeDirectory = "mastodon-streaming";
+        RuntimeDirectoryMode = "0750";
+      } // cfgService;
     };
 
     systemd.services.mastodon-web = {
@@ -438,21 +466,20 @@ in {
         ++ (if cfg.automaticMigrations then [ "mastodon-init-db.service" ] else [ "mastodon-init-dirs.service" ]);
       description = "Mastodon web";
       wantedBy = [ "multi-user.target" ];
-      environment = env // {
-        PORT = toString(cfg.webPort);
-      };
+      environment = env // (if cfg.enableUnixSocket
+        then { SOCKET = "/run/mastodon-web/web.socket"; }
+        else { PORT = toString(cfg.webPort); }
+      );
       serviceConfig = {
         ExecStart = "${cfg.package}/bin/puma -C config/puma.rb";
         Restart = "always";
         RestartSec = 20;
-        User = cfg.user;
-        Group = cfg.group;
-        WorkingDirectory = cfg.package;
         EnvironmentFile = "/var/lib/mastodon/.secrets_env";
-        PrivateTmp = true;
-        LogsDirectory = "mastodon";
-        StateDirectory = "mastodon";
-      };
+        WorkingDirectory = cfg.package;
+        # Runtime directory and mode
+        RuntimeDirectory = "mastodon-web";
+        RuntimeDirectoryMode = "0750";
+      } // cfgService;
       path = with pkgs; [ file imagemagick ffmpeg ];
     };
 
@@ -469,14 +496,9 @@ in {
         ExecStart = "${cfg.package}/bin/sidekiq -c 25 -r ${cfg.package}";
         Restart = "always";
         RestartSec = 20;
-        User = cfg.user;
-        Group = cfg.group;
-        WorkingDirectory = cfg.package;
         EnvironmentFile = "/var/lib/mastodon/.secrets_env";
-        PrivateTmp = true;
-        LogsDirectory = "mastodon";
-        StateDirectory = "mastodon";
-      };
+        WorkingDirectory = cfg.package;
+      } // cfgService;
       path = with pkgs; [ file imagemagick ffmpeg ];
     };
 
@@ -495,12 +517,12 @@ in {
         };
 
         locations."@proxy" = {
-          proxyPass = "http://127.0.0.1:${toString(cfg.webPort)}";
+          proxyPass = (if cfg.enableUnixSocket then "http://unix:/run/mastodon-web/web.socket" else "http://127.0.0.1:${toString(cfg.webPort)}");
           proxyWebsockets = true;
         };
 
         locations."/api/v1/streaming/" = {
-          proxyPass = "http://127.0.0.1:${toString(cfg.streamingPort)}/";
+          proxyPass = (if cfg.enableUnixSocket then "http://unix:/run/mastodon-streaming/streaming.socket" else "http://127.0.0.1:${toString(cfg.streamingPort)}/");
           proxyWebsockets = true;
         };
       };
@@ -534,7 +556,7 @@ in {
       (lib.attrsets.setAttrByPath [ cfg.user "packages" ] [ cfg.package mastodonEnv ])
     ];
 
-    users.groups.mastodon = lib.mkIf (cfg.group == "mastodon") { };
+    users.groups.${cfg.group}.members = lib.optional cfg.configureNginx config.services.nginx.user;
   };
 
   meta.maintainers = with lib.maintainers; [ happy-river erictapen ];
diff --git a/nixos/modules/services/web-apps/miniflux.nix b/nixos/modules/services/web-apps/miniflux.nix
index 304712d0efc3e..62906d5e6a0ce 100644
--- a/nixos/modules/services/web-apps/miniflux.nix
+++ b/nixos/modules/services/web-apps/miniflux.nix
@@ -44,7 +44,7 @@ in
         '';
         description = ''
           Configuration for Miniflux, refer to
-          <link xlink:href="http://docs.miniflux.app/en/latest/configuration.html"/>
+          <link xlink:href="https://miniflux.app/docs/configuration.html"/>
           for documentation on the supported values.
         '';
       };
diff --git a/nixos/modules/services/web-apps/moinmoin.nix b/nixos/modules/services/web-apps/moinmoin.nix
index 3a876f75f4a48..7a54255a46efc 100644
--- a/nixos/modules/services/web-apps/moinmoin.nix
+++ b/nixos/modules/services/web-apps/moinmoin.nix
@@ -211,7 +211,7 @@ in
             environment = let
               penv = python.buildEnv.override {
                 # setuptools: https://github.com/benoitc/gunicorn/issues/1716
-                extraLibs = [ python.pkgs.gevent python.pkgs.setuptools pkg ];
+                extraLibs = [ python.pkgs.eventlet python.pkgs.setuptools pkg ];
               };
             in {
               PYTHONPATH = "${dataDir}/${wikiIdent}/config:${penv}/${python.sitePackages}";
@@ -233,7 +233,7 @@ in
               ExecStart = ''${python.pkgs.gunicorn}/bin/gunicorn moin_wsgi \
                 --name gunicorn-${wikiIdent} \
                 --workers ${toString cfg.gunicorn.workers} \
-                --worker-class gevent \
+                --worker-class eventlet \
                 --bind unix:/run/moin/${wikiIdent}/gunicorn.sock
               '';
 
diff --git a/nixos/modules/services/web-apps/nextcloud.nix b/nixos/modules/services/web-apps/nextcloud.nix
index 60d403553352c..9a541aba6e43b 100644
--- a/nixos/modules/services/web-apps/nextcloud.nix
+++ b/nixos/modules/services/web-apps/nextcloud.nix
@@ -10,7 +10,7 @@ let
     extensions = { enabled, all }:
       (with all;
         enabled
-        ++ [ imagick ] # Always enabled
+        ++ optional (!cfg.disableImagemagick) imagick
         # Optionally enabled depending on caching settings
         ++ optional cfg.caching.apcu apcu
         ++ optional cfg.caching.redis redis
@@ -28,7 +28,10 @@ let
     upload_max_filesize = cfg.maxUploadSize;
     post_max_size = cfg.maxUploadSize;
     memory_limit = cfg.maxUploadSize;
-  } // cfg.phpOptions;
+  } // cfg.phpOptions
+    // optionalAttrs cfg.caching.apcu {
+      "apc.enable_cli" = "1";
+    };
 
   occ = pkgs.writeScriptBin "nextcloud-occ" ''
     #! ${pkgs.runtimeShell}
@@ -86,7 +89,7 @@ in {
     package = mkOption {
       type = types.package;
       description = "Which package to use for the Nextcloud instance.";
-      relatedPackages = [ "nextcloud18" "nextcloud19" "nextcloud20" ];
+      relatedPackages = [ "nextcloud19" "nextcloud20" "nextcloud21" ];
     };
 
     maxUploadSize = mkOption {
@@ -280,6 +283,36 @@ in {
           may be served via HTTPS.
         '';
       };
+
+      defaultPhoneRegion = mkOption {
+        default = null;
+        type = types.nullOr types.str;
+        example = "DE";
+        description = ''
+          <warning>
+           <para>This option exists since Nextcloud 21! If older versions are used,
+            this will throw an eval-error!</para>
+          </warning>
+
+          <link xlink:href="https://www.iso.org/iso-3166-country-codes.html">ISO 3611-1</link>
+          country codes for automatic phone-number detection without a country code.
+
+          With e.g. <literal>DE</literal> set, the <literal>+49</literal> can be omitted for
+          phone-numbers.
+        '';
+      };
+    };
+
+    disableImagemagick = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Whether to not load the ImageMagick module into PHP.
+        This is used by the theming app and for generating previews of certain images (e.g. SVG and HEIF).
+        You may want to disable it for increased security. In that case, previews will still be available
+        for some images (e.g. JPEG and PNG).
+        See https://github.com/nextcloud/server/issues/13099
+      '';
     };
 
     caching = {
@@ -345,10 +378,13 @@ in {
             && !(acfg.adminpass != null && acfg.adminpassFile != null));
           message = "Please specify exactly one of adminpass or adminpassFile";
         }
+        { assertion = versionOlder cfg.package.version "21" -> cfg.config.defaultPhoneRegion == null;
+          message = "The `defaultPhoneRegion'-setting is only supported for Nextcloud >=21!";
+        }
       ];
 
       warnings = let
-        latest = 20;
+        latest = 21;
         upgradeWarning = major: nixos:
           ''
             A legacy Nextcloud install (from before NixOS ${nixos}) may be installed.
@@ -366,9 +402,9 @@ in {
           Using config.services.nextcloud.poolConfig is deprecated and will become unsupported in a future release.
           Please migrate your configuration to config.services.nextcloud.poolSettings.
         '')
-        ++ (optional (versionOlder cfg.package.version "18") (upgradeWarning 17 "20.03"))
         ++ (optional (versionOlder cfg.package.version "19") (upgradeWarning 18 "20.09"))
-        ++ (optional (versionOlder cfg.package.version "20") (upgradeWarning 19 "21.05"));
+        ++ (optional (versionOlder cfg.package.version "20") (upgradeWarning 19 "21.05"))
+        ++ (optional (versionOlder cfg.package.version "21") (upgradeWarning 20 "21.05"));
 
       services.nextcloud.package = with pkgs;
         mkDefault (
@@ -378,14 +414,13 @@ in {
               nextcloud defined in an overlay, please set `services.nextcloud.package` to
               `pkgs.nextcloud`.
             ''
-          else if versionOlder stateVersion "20.03" then nextcloud17
           else if versionOlder stateVersion "20.09" then nextcloud18
           # 21.03 will not be an official release - it was instead 21.05.
           # This versionOlder statement remains set to 21.03 for backwards compatibility.
           # See https://github.com/NixOS/nixpkgs/pull/108899 and
           # https://github.com/NixOS/rfcs/blob/master/rfcs/0080-nixos-release-schedule.md.
           else if versionOlder stateVersion "21.03" then nextcloud19
-          else nextcloud20
+          else nextcloud21
         );
     }
 
@@ -443,6 +478,7 @@ in {
               'dbtype' => '${c.dbtype}',
               'trusted_domains' => ${writePhpArrary ([ cfg.hostName ] ++ c.extraTrustedDomains)},
               'trusted_proxies' => ${writePhpArrary (c.trustedProxies)},
+              ${optionalString (c.defaultPhoneRegion != null) "'default_phone_region' => '${c.defaultPhoneRegion}',"}
             ];
           '';
           occInstallCmd = let
@@ -591,6 +627,14 @@ in {
               access_log off;
             '';
           };
+          "= /" = {
+            priority = 100;
+            extraConfig = ''
+              if ( $http_user_agent ~ ^DavClnt ) {
+                return 302 /remote.php/webdav/$is_args$args;
+              }
+            '';
+          };
           "/" = {
             priority = 900;
             extraConfig = "rewrite ^ /index.php;";
@@ -602,11 +646,15 @@ in {
           "^~ /.well-known" = {
             priority = 210;
             extraConfig = ''
+              absolute_redirect off;
               location = /.well-known/carddav {
-                return 301 $scheme://$host/remote.php/dav;
+                return 301 /remote.php/dav;
               }
               location = /.well-known/caldav {
-                return 301 $scheme://$host/remote.php/dav;
+                return 301 /remote.php/dav;
+              }
+              location ~ ^/\.well-known/(?!acme-challenge|pki-validation) {
+                return 301 /index.php$request_uri;
               }
               try_files $uri $uri/ =404;
             '';
@@ -614,7 +662,7 @@ in {
           "~ ^/(?:build|tests|config|lib|3rdparty|templates|data)(?:$|/)".extraConfig = ''
             return 404;
           '';
-          "~ ^/(?:\\.|autotest|occ|issue|indie|db_|console)".extraConfig = ''
+          "~ ^/(?:\\.(?!well-known)|autotest|occ|issue|indie|db_|console)".extraConfig = ''
             return 404;
           '';
           "~ ^\\/(?:index|remote|public|cron|core\\/ajax\\/update|status|ocs\\/v[12]|updater\\/.+|oc[ms]-provider\\/.+|.+\\/richdocumentscode\\/proxy)\\.php(?:$|\\/)" = {
diff --git a/nixos/modules/services/web-apps/nextcloud.xml b/nixos/modules/services/web-apps/nextcloud.xml
index 6cbfda118c4f6..83a6f68edcbff 100644
--- a/nixos/modules/services/web-apps/nextcloud.xml
+++ b/nixos/modules/services/web-apps/nextcloud.xml
@@ -11,7 +11,7 @@
   desktop client is packaged at <literal>pkgs.nextcloud-client</literal>.
  </para>
  <para>
-  The current default by NixOS is <package>nextcloud20</package> which is also the latest
+  The current default by NixOS is <package>nextcloud21</package> which is also the latest
   major version available.
  </para>
  <section xml:id="module-services-nextcloud-basic-usage">
diff --git a/nixos/modules/services/web-apps/whitebophir.nix b/nixos/modules/services/web-apps/whitebophir.nix
index a19812547c448..b265296d5c1eb 100644
--- a/nixos/modules/services/web-apps/whitebophir.nix
+++ b/nixos/modules/services/web-apps/whitebophir.nix
@@ -16,6 +16,12 @@ in {
         description = "Whitebophir package to use.";
       };
 
+      listenAddress = mkOption {
+        type = types.str;
+        default = "0.0.0.0";
+        description = "Address to listen on (use 0.0.0.0 to allow access from any address).";
+      };
+
       port = mkOption {
         type = types.port;
         default = 5001;
@@ -30,7 +36,8 @@ in {
       wantedBy    = [ "multi-user.target" ];
       after       = [ "network.target" ];
       environment = {
-        PORT            = "${toString cfg.port}";
+        PORT            = toString cfg.port;
+        HOST            = toString cfg.listenAddress;
         WBO_HISTORY_DIR = "/var/lib/whitebophir";
       };
 
diff --git a/nixos/modules/services/web-servers/apache-httpd/default.nix b/nixos/modules/services/web-servers/apache-httpd/default.nix
index 7f50b8fd8d441..b2bb5055cd4c4 100644
--- a/nixos/modules/services/web-servers/apache-httpd/default.nix
+++ b/nixos/modules/services/web-servers/apache-httpd/default.nix
@@ -22,7 +22,9 @@ let
 
   php = cfg.phpPackage.override { apacheHttpd = pkg; };
 
-  phpMajorVersion = lib.versions.major (lib.getVersion php);
+  phpModuleName = let
+    majorVersion = lib.versions.major (lib.getVersion php);
+  in (if majorVersion == "8" then "php" else "php${majorVersion}");
 
   mod_perl = pkgs.apacheHttpdPackages.mod_perl.override { apacheHttpd = pkg; };
 
@@ -63,7 +65,7 @@ let
     ++ optional enableSSL "ssl"
     ++ optional enableUserDir "userdir"
     ++ optional cfg.enableMellon { name = "auth_mellon"; path = "${pkgs.apacheHttpdPackages.mod_auth_mellon}/modules/mod_auth_mellon.so"; }
-    ++ optional cfg.enablePHP { name = "php${phpMajorVersion}"; path = "${php}/modules/libphp${phpMajorVersion}.so"; }
+    ++ optional cfg.enablePHP { name = phpModuleName; path = "${php}/modules/lib${phpModuleName}.so"; }
     ++ optional cfg.enablePerl { name = "perl"; path = "${mod_perl}/modules/mod_perl.so"; }
     ++ cfg.extraModules;
 
diff --git a/nixos/modules/services/web-servers/nginx/default.nix b/nixos/modules/services/web-servers/nginx/default.nix
index fa8614e8ec174..f3175793ebefb 100644
--- a/nixos/modules/services/web-servers/nginx/default.nix
+++ b/nixos/modules/services/web-servers/nginx/default.nix
@@ -804,7 +804,7 @@ in
         ProtectControlGroups = true;
         RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" ];
         LockPersonality = true;
-        MemoryDenyWriteExecute = !(builtins.any (mod: (mod.allowMemoryWriteExecute or false)) pkgs.nginx.modules);
+        MemoryDenyWriteExecute = !(builtins.any (mod: (mod.allowMemoryWriteExecute or false)) cfg.package.modules);
         RestrictRealtime = true;
         RestrictSUIDSGID = true;
         PrivateMounts = true;
diff --git a/nixos/modules/services/x11/desktop-managers/gnome3.nix b/nixos/modules/services/x11/desktop-managers/gnome3.nix
index 671301246a8c7..99e6edfba26e2 100644
--- a/nixos/modules/services/x11/desktop-managers/gnome3.nix
+++ b/nixos/modules/services/x11/desktop-managers/gnome3.nix
@@ -197,12 +197,11 @@ in
   config = mkMerge [
     (mkIf (cfg.enable || flashbackEnabled) {
       # Seed our configuration into nixos-generate-config
-      system.nixos-generate-config.desktopConfiguration = ''
+      system.nixos-generate-config.desktopConfiguration = [''
         # Enable the GNOME 3 Desktop Environment.
-        services.xserver.enable = true;
         services.xserver.displayManager.gdm.enable = true;
         services.xserver.desktopManager.gnome3.enable = true;
-      '';
+      ''];
 
       services.gnome3.core-os-services.enable = true;
       services.gnome3.core-shell.enable = true;
diff --git a/nixos/modules/services/x11/desktop-managers/plasma5.nix b/nixos/modules/services/x11/desktop-managers/plasma5.nix
index d6cf86d3a2e61..44ee079b81737 100644
--- a/nixos/modules/services/x11/desktop-managers/plasma5.nix
+++ b/nixos/modules/services/x11/desktop-managers/plasma5.nix
@@ -184,12 +184,11 @@ in
   config = mkMerge [
     (mkIf cfg.enable {
       # Seed our configuration into nixos-generate-config
-      system.nixos-generate-config.desktopConfiguration = ''
+      system.nixos-generate-config.desktopConfiguration = [''
         # Enable the Plasma 5 Desktop Environment.
-        services.xserver.enable = true;
         services.xserver.displayManager.sddm.enable = true;
         services.xserver.desktopManager.plasma5.enable = true;
-      '';
+      ''];
 
       services.xserver.desktopManager.session = singleton {
         name = "plasma5";
diff --git a/nixos/modules/services/x11/desktop-managers/xfce.nix b/nixos/modules/services/x11/desktop-managers/xfce.nix
index d39b4d64904fb..fc7f7bea4e444 100644
--- a/nixos/modules/services/x11/desktop-managers/xfce.nix
+++ b/nixos/modules/services/x11/desktop-managers/xfce.nix
@@ -58,7 +58,7 @@ in
       noDesktop = mkOption {
         type = types.bool;
         default = false;
-        description = "Don't install XFCE desktop components (xfdesktop, panel and notification daemon).";
+        description = "Don't install XFCE desktop components (xfdesktop and panel).";
       };
 
       enableXfwm = mkOption {
@@ -98,6 +98,7 @@ in
       parole
       ristretto
       xfce4-appfinder
+      xfce4-notifyd
       xfce4-screenshooter
       xfce4-session
       xfce4-settings
@@ -119,7 +120,6 @@ in
         xfwm4
         xfwm4-themes
       ] ++ optionals (!cfg.noDesktop) [
-        xfce4-notifyd
         xfce4-panel
         xfdesktop
       ];
@@ -166,7 +166,8 @@ in
     # Systemd services
     systemd.packages = with pkgs.xfce; [
       (thunar.override { thunarPlugins = cfg.thunarPlugins; })
-    ] ++ optional (!cfg.noDesktop) xfce4-notifyd;
+      xfce4-notifyd
+    ];
 
   };
 }
diff --git a/nixos/modules/services/x11/display-managers/default.nix b/nixos/modules/services/x11/display-managers/default.nix
index 9fdbe753dad50..e04fcdaf4145d 100644
--- a/nixos/modules/services/x11/display-managers/default.nix
+++ b/nixos/modules/services/x11/display-managers/default.nix
@@ -37,6 +37,11 @@ let
       . /etc/profile
       cd "$HOME"
 
+      # Allow the user to execute commands at the beginning of the X session.
+      if test -f ~/.xprofile; then
+          source ~/.xprofile
+      fi
+
       ${optionalString cfg.displayManager.job.logToJournal ''
         if [ -z "$_DID_SYSTEMD_CAT" ]; then
           export _DID_SYSTEMD_CAT=1
@@ -64,22 +69,23 @@ let
 
       # Speed up application start by 50-150ms according to
       # http://kdemonkey.blogspot.nl/2008/04/magic-trick.html
-      rm -rf "$HOME/.compose-cache"
-      mkdir "$HOME/.compose-cache"
+      compose_cache="''${XCOMPOSECACHE:-$HOME/.compose-cache}"
+      mkdir -p "$compose_cache"
+      # To avoid accidentally deleting a wrongly set up XCOMPOSECACHE directory,
+      # defensively try to delete cache *files* only, following the file format specified in
+      # https://gitlab.freedesktop.org/xorg/lib/libx11/-/blob/master/modules/im/ximcp/imLcIm.c#L353-358
+      # sprintf (*res, "%s/%c%d_%03x_%08x_%08x", dir, _XimGetMyEndian(), XIM_CACHE_VERSION, (unsigned int)sizeof (DefTree), hash, hash2);
+      ${pkgs.findutils}/bin/find "$compose_cache" -maxdepth 1 -regextype posix-extended -regex '.*/[Bl][0-9]+_[0-9a-f]{3}_[0-9a-f]{8}_[0-9a-f]{8}' -delete
+      unset compose_cache
 
       # Work around KDE errors when a user first logs in and
       # .local/share doesn't exist yet.
-      mkdir -p "$HOME/.local/share"
+      mkdir -p "''${XDG_DATA_HOME:-$HOME/.local/share}"
 
       unset _DID_SYSTEMD_CAT
 
       ${cfg.displayManager.sessionCommands}
 
-      # Allow the user to execute commands at the beginning of the X session.
-      if test -f ~/.xprofile; then
-          source ~/.xprofile
-      fi
-
       # Start systemd user services for graphical sessions
       /run/current-system/systemd/bin/systemctl --user start graphical-session.target
 
diff --git a/nixos/modules/services/x11/display-managers/gdm.nix b/nixos/modules/services/x11/display-managers/gdm.nix
index e3c5adb9737fc..f79eb64b5a6a2 100644
--- a/nixos/modules/services/x11/display-managers/gdm.nix
+++ b/nixos/modules/services/x11/display-managers/gdm.nix
@@ -183,14 +183,20 @@ in
       "systemd-udev-settle.service"
     ];
     systemd.services.display-manager.conflicts = [
-       "getty@tty${gdm.initialVT}.service"
-       # TODO: Add "plymouth-quit.service" so GDM can control when plymouth quits.
-       # Currently this breaks switching configurations while using plymouth.
+      "getty@tty${gdm.initialVT}.service"
+      "plymouth-quit.service"
     ];
     systemd.services.display-manager.onFailure = [
       "plymouth-quit.service"
     ];
 
+    # Prevent nixos-rebuild switch from bringing down the graphical
+    # session. (If multi-user.target wants plymouth-quit.service which
+    # conflicts display-manager.service, then when nixos-rebuild
+    # switch starts multi-user.target, display-manager.service is
+    # stopped so plymouth-quit.service can be started.)
+    systemd.services.plymouth-quit.wantedBy = lib.mkForce [];
+
     systemd.services.display-manager.serviceConfig = {
       # Restart = "always"; - already defined in xserver.nix
       KillMode = "mixed";
diff --git a/nixos/modules/services/x11/xserver.nix b/nixos/modules/services/x11/xserver.nix
index 8858559d8f27d..35bd4dabb6738 100644
--- a/nixos/modules/services/x11/xserver.nix
+++ b/nixos/modules/services/x11/xserver.nix
@@ -251,11 +251,10 @@ in
 
       videoDrivers = mkOption {
         type = types.listOf types.str;
-        # !!! We'd like "nv" here, but it segfaults the X server.
-        default = [ "radeon" "cirrus" "vesa" "modesetting" ];
+        default = [ "amdgpu" "radeon" "nouveau" "modesetting" "fbdev" ];
         example = [
-          "ati_unfree" "amdgpu" "amdgpu-pro"
-          "nv" "nvidia" "nvidiaLegacy390" "nvidiaLegacy340" "nvidiaLegacy304"
+          "nvidia" "nvidiaLegacy390" "nvidiaLegacy340" "nvidiaLegacy304"
+          "amdgpu-pro"
         ];
         # TODO(@oxij): think how to easily add the rest, like those nvidia things
         relatedPackages = concatLists
diff --git a/nixos/modules/system/boot/kernel_config.nix b/nixos/modules/system/boot/kernel_config.nix
index 783685c9dfe41..5d9534024b06b 100644
--- a/nixos/modules/system/boot/kernel_config.nix
+++ b/nixos/modules/system/boot/kernel_config.nix
@@ -2,24 +2,6 @@
 
 with lib;
 let
-  findWinner = candidates: winner:
-    any (x: x == winner) candidates;
-
-  # winners is an ordered list where first item wins over 2nd etc
-  mergeAnswer = winners: locs: defs:
-    let
-      values = map (x: x.value) defs;
-      inter = intersectLists values winners;
-      winner = head winners;
-    in
-    if defs == [] then abort "This case should never happen."
-    else if winner == [] then abort "Give a valid list of winner"
-    else if inter == [] then mergeOneOption locs defs
-    else if findWinner values winner then
-      winner
-    else
-      mergeAnswer (tail winners) locs defs;
-
   mergeFalseByDefault = locs: defs:
     if defs == [] then abort "This case should never happen."
     else if any (x: x == false) (getValues defs) then false
@@ -28,9 +10,7 @@ let
   kernelItem = types.submodule {
     options = {
       tristate = mkOption {
-        type = types.enum [ "y" "m" "n" null ] // {
-          merge = mergeAnswer [ "y" "m" "n" ];
-        };
+        type = types.enum [ "y" "m" "n" null ];
         default = null;
         internal = true;
         visible = true;
diff --git a/nixos/modules/system/boot/loader/generic-extlinux-compatible/extlinux-conf-builder.sh b/nixos/modules/system/boot/loader/generic-extlinux-compatible/extlinux-conf-builder.sh
index 854684b87face..5ffffb95edb1b 100644
--- a/nixos/modules/system/boot/loader/generic-extlinux-compatible/extlinux-conf-builder.sh
+++ b/nixos/modules/system/boot/loader/generic-extlinux-compatible/extlinux-conf-builder.sh
@@ -109,7 +109,7 @@ addEntry() {
             exit 1
         fi
     fi
-    echo "  APPEND systemConfig=$path init=$path/init $extraParams"
+    echo "  APPEND init=$path/init $extraParams"
 }
 
 tmpFile="$target/extlinux/extlinux.conf.tmp.$$"
diff --git a/nixos/modules/system/boot/loader/grub/install-grub.pl b/nixos/modules/system/boot/loader/grub/install-grub.pl
index 59f5638044fe9..e0167654748e1 100644
--- a/nixos/modules/system/boot/loader/grub/install-grub.pl
+++ b/nixos/modules/system/boot/loader/grub/install-grub.pl
@@ -102,10 +102,10 @@ if (stat($bootPath)->dev != stat("/nix/store")->dev) {
 
 # Discover information about the location of the bootPath
 struct(Fs => {
-    device => '$',
-    type => '$',
-    mount => '$',
-});
+        device => '$',
+        type => '$',
+        mount => '$',
+    });
 sub PathInMount {
     my ($path, $mount) = @_;
     my @splitMount = split /\//, $mount;
@@ -154,16 +154,16 @@ sub GetFs {
     return $bestFs;
 }
 struct (Grub => {
-    path => '$',
-    search => '$',
-});
+        path => '$',
+        search => '$',
+    });
 my $driveid = 1;
 sub GrubFs {
     my ($dir) = @_;
     my $fs = GetFs($dir);
     my $path = substr($dir, length($fs->mount));
     if (substr($path, 0, 1) ne "/") {
-      $path = "/$path";
+        $path = "/$path";
     }
     my $search = "";
 
@@ -251,8 +251,8 @@ my $conf .= "# Automatically generated.  DO NOT EDIT THIS FILE!\n";
 
 if ($grubVersion == 1) {
     $conf .= "
-        default $defaultEntry
-        timeout $timeout
+    default $defaultEntry
+    timeout $timeout
     ";
     if ($splashImage) {
         copy $splashImage, "$bootPath/background.xpm.gz" or die "cannot copy $splashImage to $bootPath: $!\n";
@@ -302,51 +302,51 @@ else {
 
     if ($copyKernels == 0) {
         $conf .= "
-            " . $grubStore->search;
+        " . $grubStore->search;
     }
     # FIXME: should use grub-mkconfig.
     $conf .= "
-        " . $grubBoot->search . "
-        if [ -s \$prefix/grubenv ]; then
-          load_env
-        fi
-
-        # ‘grub-reboot’ sets a one-time saved entry, which we process here and
-        # then delete.
-        if [ \"\${next_entry}\" ]; then
-          set default=\"\${next_entry}\"
-          set next_entry=
-          save_env next_entry
-          set timeout=1
-        else
-          set default=$defaultEntry
-          set timeout=$timeout
-        fi
-
-        # Setup the graphics stack for bios and efi systems
-        if [ \"\${grub_platform}\" = \"efi\" ]; then
-          insmod efi_gop
-          insmod efi_uga
-        else
-          insmod vbe
-        fi
+    " . $grubBoot->search . "
+    if [ -s \$prefix/grubenv ]; then
+    load_env
+    fi
+
+    # ‘grub-reboot’ sets a one-time saved entry, which we process here and
+    # then delete.
+    if [ \"\${next_entry}\" ]; then
+    set default=\"\${next_entry}\"
+    set next_entry=
+    save_env next_entry
+    set timeout=1
+    else
+    set default=$defaultEntry
+    set timeout=$timeout
+    fi
+
+    # Setup the graphics stack for bios and efi systems
+    if [ \"\${grub_platform}\" = \"efi\" ]; then
+    insmod efi_gop
+    insmod efi_uga
+    else
+    insmod vbe
+    fi
     ";
 
     if ($font) {
         copy $font, "$bootPath/converted-font.pf2" or die "cannot copy $font to $bootPath: $!\n";
         $conf .= "
-            insmod font
-            if loadfont " . ($grubBoot->path eq "/" ? "" : $grubBoot->path) . "/converted-font.pf2; then
-              insmod gfxterm
-              if [ \"\${grub_platform}\" = \"efi\" ]; then
-                set gfxmode=$gfxmodeEfi
-                set gfxpayload=$gfxpayloadEfi
-              else
-                set gfxmode=$gfxmodeBios
-                set gfxpayload=$gfxpayloadBios
-              fi
-              terminal_output gfxterm
-            fi
+        insmod font
+        if loadfont " . ($grubBoot->path eq "/" ? "" : $grubBoot->path) . "/converted-font.pf2; then
+        insmod gfxterm
+        if [ \"\${grub_platform}\" = \"efi\" ]; then
+        set gfxmode=$gfxmodeEfi
+        set gfxpayload=$gfxpayloadEfi
+        else
+        set gfxmode=$gfxmodeBios
+        set gfxpayload=$gfxpayloadBios
+        fi
+        terminal_output gfxterm
+        fi
         ";
     }
     if ($splashImage) {
@@ -356,21 +356,21 @@ else {
         if ($suffix eq ".jpg") {
             $suffix = ".jpeg";
         }
-		if ($backgroundColor) {
-			$conf .= "
-		    background_color '$backgroundColor'
-		    ";
-		}
+        if ($backgroundColor) {
+            $conf .= "
+            background_color '$backgroundColor'
+            ";
+        }
         copy $splashImage, "$bootPath/background$suffix" or die "cannot copy $splashImage to $bootPath: $!\n";
         $conf .= "
-            insmod " . substr($suffix, 1) . "
-            if background_image --mode '$splashMode' " . ($grubBoot->path eq "/" ? "" : $grubBoot->path) . "/background$suffix; then
-              set color_normal=white/black
-              set color_highlight=black/white
-            else
-              set menu_color_normal=cyan/blue
-              set menu_color_highlight=white/blue
-            fi
+        insmod " . substr($suffix, 1) . "
+        if background_image --mode '$splashMode' " . ($grubBoot->path eq "/" ? "" : $grubBoot->path) . "/background$suffix; then
+        set color_normal=white/black
+        set color_highlight=black/white
+        else
+        set menu_color_normal=cyan/blue
+        set menu_color_highlight=white/blue
+        fi
         ";
     }
 
@@ -380,21 +380,21 @@ else {
         # Copy theme
         rcopy($theme, "$bootPath/theme") or die "cannot copy $theme to $bootPath\n";
         $conf .= "
-            # Sets theme.
-            set theme=" . ($grubBoot->path eq "/" ? "" : $grubBoot->path) . "/theme/theme.txt
-            export theme
-            # Load theme fonts, if any
-         ";
-
-         find( { wanted => sub {
-             if ($_ =~ /\.pf2$/i) {
-                 $font = File::Spec->abs2rel($File::Find::name, $theme);
-                 $conf .= "
-                     loadfont " . ($grubBoot->path eq "/" ? "" : $grubBoot->path) . "/theme/$font
-                 ";
-             }
-         }, no_chdir => 1 }, $theme );
-     }
+        # Sets theme.
+        set theme=" . ($grubBoot->path eq "/" ? "" : $grubBoot->path) . "/theme/theme.txt
+        export theme
+        # Load theme fonts, if any
+        ";
+
+        find( { wanted => sub {
+                    if ($_ =~ /\.pf2$/i) {
+                        $font = File::Spec->abs2rel($File::Find::name, $theme);
+                        $conf .= "
+                        loadfont " . ($grubBoot->path eq "/" ? "" : $grubBoot->path) . "/theme/$font
+                        ";
+                    }
+                }, no_chdir => 1 }, $theme );
+    }
 }
 
 $conf .= "$extraConfig\n";
@@ -433,25 +433,25 @@ sub addEntry {
 
     # Include second initrd with secrets
     if (-e -x "$path/append-initrd-secrets") {
-      my $initrdName = basename($initrd);
-      my $initrdSecretsPath = "$bootPath/kernels/$initrdName-secrets";
-
-      mkpath(dirname($initrdSecretsPath), 0, 0755);
-      my $oldUmask = umask;
-      # Make sure initrd is not world readable (won't work if /boot is FAT)
-      umask 0137;
-      my $initrdSecretsPathTemp = File::Temp::mktemp("$initrdSecretsPath.XXXXXXXX");
-      system("$path/append-initrd-secrets", $initrdSecretsPathTemp) == 0 or die "failed to create initrd secrets: $!\n";
-      # Check whether any secrets were actually added
-      if (-e $initrdSecretsPathTemp && ! -z _) {
-        rename $initrdSecretsPathTemp, $initrdSecretsPath or die "failed to move initrd secrets into place: $!\n";
-        $copied{$initrdSecretsPath} = 1;
-        $initrd .= " " . ($grubBoot->path eq "/" ? "" : $grubBoot->path) . "/kernels/$initrdName-secrets";
-      } else {
-        unlink $initrdSecretsPathTemp;
-        rmdir dirname($initrdSecretsPathTemp);
-      }
-      umask $oldUmask;
+        my $initrdName = basename($initrd);
+        my $initrdSecretsPath = "$bootPath/kernels/$initrdName-secrets";
+
+        mkpath(dirname($initrdSecretsPath), 0, 0755);
+        my $oldUmask = umask;
+        # Make sure initrd is not world readable (won't work if /boot is FAT)
+        umask 0137;
+        my $initrdSecretsPathTemp = File::Temp::mktemp("$initrdSecretsPath.XXXXXXXX");
+        system("$path/append-initrd-secrets", $initrdSecretsPathTemp) == 0 or die "failed to create initrd secrets: $!\n";
+        # Check whether any secrets were actually added
+        if (-e $initrdSecretsPathTemp && ! -z _) {
+            rename $initrdSecretsPathTemp, $initrdSecretsPath or die "failed to move initrd secrets into place: $!\n";
+            $copied{$initrdSecretsPath} = 1;
+            $initrd .= " " . ($grubBoot->path eq "/" ? "" : $grubBoot->path) . "/kernels/$initrdName-secrets";
+        } else {
+            unlink $initrdSecretsPathTemp;
+            rmdir dirname($initrdSecretsPathTemp);
+        }
+        umask $oldUmask;
     }
 
     my $xen = -e "$path/xen.gz" ? copyToKernelsDir(Cwd::abs_path("$path/xen.gz")) : undef;
@@ -459,9 +459,8 @@ sub addEntry {
     # FIXME: $confName
 
     my $kernelParams =
-        "systemConfig=" . Cwd::abs_path($path) . " " .
-        "init=" . Cwd::abs_path("$path/init") . " " .
-        readFile("$path/kernel-params");
+    "init=" . Cwd::abs_path("$path/init") . " " .
+    readFile("$path/kernel-params");
     my $xenParams = $xen && -e "$path/xen-params" ? readFile("$path/xen-params") : "";
 
     if ($grubVersion == 1) {
@@ -503,9 +502,9 @@ foreach my $link (@links) {
 
     my $date = strftime("%F", localtime(lstat($link)->mtime));
     my $version =
-        -e "$link/nixos-version"
-        ? readFile("$link/nixos-version")
-        : basename((glob(dirname(Cwd::abs_path("$link/kernel")) . "/lib/modules/*"))[0]);
+    -e "$link/nixos-version"
+    ? readFile("$link/nixos-version")
+    : basename((glob(dirname(Cwd::abs_path("$link/kernel")) . "/lib/modules/*"))[0]);
 
     if ($cfgName) {
         $entryName = $cfgName;
@@ -530,8 +529,8 @@ sub addProfile {
     sub nrFromGen { my ($x) = @_; $x =~ /\/\w+-(\d+)-link/; return $1; }
 
     my @links = sort
-        { nrFromGen($b) <=> nrFromGen($a) }
-        (glob "$profile-*-link");
+    { nrFromGen($b) <=> nrFromGen($a) }
+    (glob "$profile-*-link");
 
     my $curEntry = 0;
     foreach my $link (@links) {
@@ -542,9 +541,9 @@ sub addProfile {
         }
         my $date = strftime("%F", localtime(lstat($link)->mtime));
         my $version =
-            -e "$link/nixos-version"
-            ? readFile("$link/nixos-version")
-            : basename((glob(dirname(Cwd::abs_path("$link/kernel")) . "/lib/modules/*"))[0]);
+        -e "$link/nixos-version"
+        ? readFile("$link/nixos-version")
+        : basename((glob(dirname(Cwd::abs_path("$link/kernel")) . "/lib/modules/*"))[0]);
         addEntry("NixOS - Configuration " . nrFromGen($link) . " ($date - $version)", $link);
     }
 
@@ -566,7 +565,7 @@ $extraPrepareConfig =~ s/\@bootPath\@/$bootPath/g;
 
 # Run extraPrepareConfig in sh
 if ($extraPrepareConfig ne "") {
-  system((get("shell"), "-c", $extraPrepareConfig));
+    system((get("shell"), "-c", $extraPrepareConfig));
 }
 
 # write the GRUB config.
@@ -627,13 +626,13 @@ foreach my $fn (glob "$bootPath/kernels/*") {
 #
 
 struct(GrubState => {
-    name => '$',
-    version => '$',
-    efi => '$',
-    devices => '$',
-    efiMountPoint => '$',
-    extraGrubInstallArgs => '@',
-});
+        name => '$',
+        version => '$',
+        efi => '$',
+        devices => '$',
+        efiMountPoint => '$',
+        extraGrubInstallArgs => '@',
+    });
 # If you add something to the state file, only add it to the end
 # because it is read line-by-line.
 sub readGrubState {
diff --git a/nixos/modules/system/boot/loader/init-script/init-script-builder.sh b/nixos/modules/system/boot/loader/init-script/init-script-builder.sh
index 2a1ec479fea04..bd3fc64999da5 100644
--- a/nixos/modules/system/boot/loader/init-script/init-script-builder.sh
+++ b/nixos/modules/system/boot/loader/init-script/init-script-builder.sh
@@ -49,7 +49,6 @@ addEntry() {
       echo "#!/bin/sh"
       echo "# $name"
       echo "# created by init-script-builder.sh"
-      echo "export systemConfig=$(readlink -f $path)"
       echo "exec $stage2"
     )"
 
diff --git a/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py b/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py
index 97e824fe629ce..6bee900c68304 100644
--- a/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py
+++ b/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py
@@ -101,7 +101,7 @@ def write_entry(profile, generation, machine_id):
         entry_file = "@efiSysMountPoint@/loader/entries/nixos-generation-%d.conf" % (generation)
     generation_dir = os.readlink(system_dir(profile, generation))
     tmp_path = "%s.tmp" % (entry_file)
-    kernel_params = "systemConfig=%s init=%s/init " % (generation_dir, generation_dir)
+    kernel_params = "init=%s/init " % generation_dir
 
     with open("%s/kernel-params" % (generation_dir)) as params_file:
         kernel_params = kernel_params + params_file.read()
diff --git a/nixos/modules/system/boot/luksroot.nix b/nixos/modules/system/boot/luksroot.nix
index 8dd2ea20519a1..fa14d86e253df 100644
--- a/nixos/modules/system/boot/luksroot.nix
+++ b/nixos/modules/system/boot/luksroot.nix
@@ -56,7 +56,7 @@ let
 
         ykinfo -v 1>/dev/null 2>&1
         if [ $? != 0 ]; then
-            echo -n "Waiting $secs seconds for Yubikey to appear..."
+            echo -n "Waiting $secs seconds for YubiKey to appear..."
             local success=false
             for try in $(seq $secs); do
                 echo -n .
@@ -118,7 +118,7 @@ let
     # Cryptsetup locking directory
     mkdir -p /run/cryptsetup
 
-    # For Yubikey salt storage
+    # For YubiKey salt storage
     mkdir -p /crypt-storage
 
     ${optionalString luks.gpgSupport ''
@@ -218,7 +218,7 @@ let
     }
 
     ${optionalString (luks.yubikeySupport && (yubikey != null)) ''
-    # Yubikey
+    # YubiKey
     rbtohex() {
         ( od -An -vtx1 | tr -d ' \n' )
     }
@@ -244,7 +244,7 @@ let
         local new_k_luks
 
         mount -t ${yubikey.storage.fsType} ${yubikey.storage.device} /crypt-storage || \
-          die "Failed to mount Yubikey salt storage device"
+          die "Failed to mount YubiKey salt storage device"
 
         salt="$(cat /crypt-storage${yubikey.storage.path} | sed -n 1p | tr -d '\n')"
         iterations="$(cat /crypt-storage${yubikey.storage.path} | sed -n 2p | tr -d '\n')"
@@ -254,8 +254,27 @@ let
         for try in $(seq 3); do
             ${optionalString yubikey.twoFactor ''
             echo -n "Enter two-factor passphrase: "
-            read -r k_user
-            echo
+            k_user=
+            while true; do
+                if [ -e /crypt-ramfs/passphrase ]; then
+                    echo "reused"
+                    k_user=$(cat /crypt-ramfs/passphrase)
+                    break
+                else
+                    # Try reading it from /dev/console with a timeout
+                    IFS= read -t 1 -r k_user
+                    if [ -n "$k_user" ]; then
+                       ${if luks.reusePassphrases then ''
+                         # Remember it for the next device
+                         echo -n "$k_user" > /crypt-ramfs/passphrase
+                       '' else ''
+                         # Don't save it to ramfs. We are very paranoid
+                       ''}
+                       echo
+                       break
+                    fi
+                fi
+            done
             ''}
 
             if [ ! -z "$k_user" ]; then
@@ -268,6 +287,11 @@ let
 
             if [ $? == 0 ]; then
                 opened=true
+                ${if luks.reusePassphrases then ''
+                  # We don't rm here because we might reuse it for the next device
+                '' else ''
+                  rm -f /crypt-ramfs/passphrase
+                ''}
                 break
             else
                 opened=false
@@ -317,7 +341,7 @@ let
         if wait_yubikey ${toString yubikey.gracePeriod}; then
             do_open_yubikey
         else
-            echo "No yubikey found, falling back to non-yubikey open procedure"
+            echo "No YubiKey found, falling back to non-YubiKey open procedure"
             open_normally
         fi
     }
@@ -665,8 +689,8 @@ in
           yubikey = mkOption {
             default = null;
             description = ''
-              The options to use for this LUKS device in Yubikey-PBA.
-              If null (the default), Yubikey-PBA will be disabled for this device.
+              The options to use for this LUKS device in YubiKey-PBA.
+              If null (the default), YubiKey-PBA will be disabled for this device.
             '';
 
             type = with types; nullOr (submodule {
@@ -674,13 +698,13 @@ in
                 twoFactor = mkOption {
                   default = true;
                   type = types.bool;
-                  description = "Whether to use a passphrase and a Yubikey (true), or only a Yubikey (false).";
+                  description = "Whether to use a passphrase and a YubiKey (true), or only a YubiKey (false).";
                 };
 
                 slot = mkOption {
                   default = 2;
                   type = types.int;
-                  description = "Which slot on the Yubikey to challenge.";
+                  description = "Which slot on the YubiKey to challenge.";
                 };
 
                 saltLength = mkOption {
@@ -704,7 +728,7 @@ in
                 gracePeriod = mkOption {
                   default = 10;
                   type = types.int;
-                  description = "Time in seconds to wait for the Yubikey.";
+                  description = "Time in seconds to wait for the YubiKey.";
                 };
 
                 /* TODO: Add to the documentation of the current module:
@@ -779,9 +803,9 @@ in
       default = false;
       type = types.bool;
       description = ''
-            Enables support for authenticating with a Yubikey on LUKS devices.
+            Enables support for authenticating with a YubiKey on LUKS devices.
             See the NixOS wiki for information on how to properly setup a LUKS device
-            and a Yubikey to work with this feature.
+            and a YubiKey to work with this feature.
           '';
     };
 
@@ -799,7 +823,7 @@ in
 
     assertions =
       [ { assertion = !(luks.gpgSupport && luks.yubikeySupport);
-          message = "Yubikey and GPG Card may not be used at the same time.";
+          message = "YubiKey and GPG Card may not be used at the same time.";
         }
 
         { assertion = !(luks.gpgSupport && luks.fido2Support);
@@ -807,7 +831,7 @@ in
         }
 
         { assertion = !(luks.fido2Support && luks.yubikeySupport);
-          message = "FIDO2 and Yubikey may not be used at the same time.";
+          message = "FIDO2 and YubiKey may not be used at the same time.";
         }
       ];
 
diff --git a/nixos/modules/system/boot/networkd.nix b/nixos/modules/system/boot/networkd.nix
index 3b01bc00bafa2..bbdd5a4070600 100644
--- a/nixos/modules/system/boot/networkd.nix
+++ b/nixos/modules/system/boot/networkd.nix
@@ -436,7 +436,8 @@ let
           "IPv4ProxyARP"
           "IPv6ProxyNDP"
           "IPv6ProxyNDPAddress"
-          "IPv6PrefixDelegation"
+          "IPv6SendRA"
+          "DHCPv6PrefixDelegation"
           "IPv6MTUBytes"
           "Bridge"
           "Bond"
@@ -477,7 +478,8 @@ let
         (assertMinimum "IPv6HopLimit" 0)
         (assertValueOneOf "IPv4ProxyARP" boolValues)
         (assertValueOneOf "IPv6ProxyNDP" boolValues)
-        (assertValueOneOf "IPv6PrefixDelegation" ["static" "dhcpv6" "yes" "false"])
+        (assertValueOneOf "IPv6SendRA" boolValues)
+        (assertValueOneOf "DHCPv6PrefixDelegation" boolValues)
         (assertByteFormat "IPv6MTUBytes")
         (assertValueOneOf "ActiveSlave" boolValues)
         (assertValueOneOf "PrimarySlave" boolValues)
@@ -643,18 +645,63 @@ let
 
       sectionDHCPv6 = checkUnitConfig "DHCPv6" [
         (assertOnlyFields [
+          "UseAddress"
           "UseDNS"
           "UseNTP"
+          "RouteMetric"
           "RapidCommit"
+          "MUDURL"
+          "RequestOptions"
+          "SendVendorOption"
           "ForceDHCPv6PDOtherInformation"
           "PrefixDelegationHint"
-          "RouteMetric"
+          "WithoutRA"
+          "SendOption"
+          "UserClass"
+          "VendorClass"
         ])
+        (assertValueOneOf "UseAddress" boolValues)
         (assertValueOneOf "UseDNS" boolValues)
         (assertValueOneOf "UseNTP" boolValues)
+        (assertInt "RouteMetric")
         (assertValueOneOf "RapidCommit" boolValues)
         (assertValueOneOf "ForceDHCPv6PDOtherInformation" boolValues)
-        (assertInt "RouteMetric")
+        (assertValueOneOf "WithoutRA" ["solicit" "information-request"])
+        (assertRange "SendOption" 1 65536)
+      ];
+
+      sectionDHCPv6PrefixDelegation = checkUnitConfig "DHCPv6PrefixDelegation" [
+        (assertOnlyFields [
+          "SubnetId"
+          "Announce"
+          "Assign"
+          "Token"
+        ])
+        (assertValueOneOf "Announce" boolValues)
+        (assertValueOneOf "Assign" boolValues)
+      ];
+
+      sectionIPv6AcceptRA = checkUnitConfig "IPv6AcceptRA" [
+        (assertOnlyFields [
+          "UseDNS"
+          "UseDomains"
+          "RouteTable"
+          "UseAutonomousPrefix"
+          "UseOnLinkPrefix"
+          "RouterDenyList"
+          "RouterAllowList"
+          "PrefixDenyList"
+          "PrefixAllowList"
+          "RouteDenyList"
+          "RouteAllowList"
+          "DHCPv6Client"
+        ])
+        (assertValueOneOf "UseDNS" boolValues)
+        (assertValueOneOf "UseDomains" (boolValues ++ ["route"]))
+        (assertRange "RouteTable" 0 4294967295)
+        (assertValueOneOf "UseAutonomousPrefix" boolValues)
+        (assertValueOneOf "UseOnLinkPrefix" boolValues)
+        (assertValueOneOf "DHCPv6Client" (boolValues ++ ["always"]))
       ];
 
       sectionDHCPServer = checkUnitConfig "DHCPServer" [
@@ -685,7 +732,7 @@ let
         (assertValueOneOf "EmitTimezone" boolValues)
       ];
 
-      sectionIPv6PrefixDelegation = checkUnitConfig "IPv6PrefixDelegation" [
+      sectionIPv6SendRA = checkUnitConfig "IPv6SendRA" [
         (assertOnlyFields [
           "Managed"
           "OtherInformation"
@@ -1090,6 +1137,30 @@ let
       '';
     };
 
+    dhcpV6PrefixDelegationConfig = mkOption {
+      default = {};
+      example = { SubnetId = "auto"; Announce = true; };
+      type = types.addCheck (types.attrsOf unitOption) check.network.sectionDHCPv6PrefixDelegation;
+      description = ''
+        Each attribute in this set specifies an option in the
+        <literal>[DHCPv6PrefixDelegation]</literal> section of the unit. See
+        <citerefentry><refentrytitle>systemd.network</refentrytitle>
+        <manvolnum>5</manvolnum></citerefentry> for details.
+      '';
+    };
+
+    ipv6AcceptRAConfig = mkOption {
+      default = {};
+      example = { UseDNS = true; DHCPv6Client = "always"; };
+      type = types.addCheck (types.attrsOf unitOption) check.network.sectionIPv6AcceptRA;
+      description = ''
+        Each attribute in this set specifies an option in the
+        <literal>[IPv6AcceptRA]</literal> section of the unit. See
+        <citerefentry><refentrytitle>systemd.network</refentrytitle>
+        <manvolnum>5</manvolnum></citerefentry> for details.
+      '';
+    };
+
     dhcpServerConfig = mkOption {
       default = {};
       example = { PoolOffset = 50; EmitDNS = false; };
@@ -1102,13 +1173,20 @@ let
       '';
     };
 
+    # systemd.network.networks.*.ipv6PrefixDelegationConfig has been deprecated
+    # in 247 in favor of systemd.network.networks.*.ipv6SendRAConfig.
     ipv6PrefixDelegationConfig = mkOption {
+      visible = false;
+      apply = _: throw "The option `systemd.network.networks.*.ipv6PrefixDelegationConfig` has been replaced by `systemd.network.networks.*.ipv6SendRAConfig`.";
+    };
+
+    ipv6SendRAConfig = mkOption {
       default = {};
       example = { EmitDNS = true; Managed = true; OtherInformation = true; };
-      type = types.addCheck (types.attrsOf unitOption) check.network.sectionIPv6PrefixDelegation;
+      type = types.addCheck (types.attrsOf unitOption) check.network.sectionIPv6SendRA;
       description = ''
         Each attribute in this set specifies an option in the
-        <literal>[IPv6PrefixDelegation]</literal> section of the unit.  See
+        <literal>[IPv6SendRA]</literal> section of the unit.  See
         <citerefentry><refentrytitle>systemd.network</refentrytitle>
         <manvolnum>5</manvolnum></citerefentry> for details.
       '';
@@ -1457,13 +1535,21 @@ let
           [DHCPv6]
           ${attrsToSection def.dhcpV6Config}
         ''
+        + optionalString (def.dhcpV6PrefixDelegationConfig != { }) ''
+          [DHCPv6PrefixDelegation]
+          ${attrsToSection def.dhcpV6PrefixDelegationConfig}
+        ''
+        + optionalString (def.ipv6AcceptRAConfig != { }) ''
+          [IPv6AcceptRA]
+          ${attrsToSection def.ipv6AcceptRAConfig}
+        ''
         + optionalString (def.dhcpServerConfig != { }) ''
           [DHCPServer]
           ${attrsToSection def.dhcpServerConfig}
         ''
-        + optionalString (def.ipv6PrefixDelegationConfig != { }) ''
-          [IPv6PrefixDelegation]
-          ${attrsToSection def.ipv6PrefixDelegationConfig}
+        + optionalString (def.ipv6SendRAConfig != { }) ''
+          [IPv6SendRA]
+          ${attrsToSection def.ipv6SendRAConfig}
         ''
         + flip concatMapStrings def.ipv6Prefixes (x: ''
           [IPv6Prefix]
@@ -1479,7 +1565,6 @@ let
 in
 
 {
-
   options = {
 
     systemd.network.enable = mkOption {
@@ -1553,9 +1638,6 @@ in
         wantedBy = [ "multi-user.target" ];
         aliases = [ "dbus-org.freedesktop.network1.service" ];
         restartTriggers = map (x: x.source) (attrValues unitFiles);
-        # prevent race condition with interface renaming (#39069)
-        requires = [ "systemd-udev-settle.service" ];
-        after = [ "systemd-udev-settle.service" ];
       };
 
       systemd.services.systemd-networkd-wait-online = {
diff --git a/nixos/modules/system/boot/plymouth.nix b/nixos/modules/system/boot/plymouth.nix
index 662576888fc20..2a545e5525135 100644
--- a/nixos/modules/system/boot/plymouth.nix
+++ b/nixos/modules/system/boot/plymouth.nix
@@ -4,8 +4,7 @@ with lib;
 
 let
 
-  inherit (pkgs) plymouth;
-  inherit (pkgs) nixos-icons;
+  inherit (pkgs) plymouth nixos-icons;
 
   cfg = config.boot.plymouth;
 
@@ -16,14 +15,37 @@ let
     osVersion = config.system.nixos.release;
   };
 
+  plymouthLogos = pkgs.runCommand "plymouth-logos" { inherit (cfg) logo; } ''
+    mkdir -p $out
+
+    # For themes that are compiled with PLYMOUTH_LOGO_FILE
+    mkdir -p $out/etc/plymouth
+    ln -s $logo $out/etc/plymouth/logo.png
+
+    # Logo for bgrt theme
+    # Note this is technically an abuse of watermark for the bgrt theme
+    # See: https://gitlab.freedesktop.org/plymouth/plymouth/-/issues/95#note_813768
+    mkdir -p $out/share/plymouth/themes/spinner
+    ln -s $logo $out/share/plymouth/themes/spinner/watermark.png
+
+    # Logo for spinfinity theme
+    # See: https://gitlab.freedesktop.org/plymouth/plymouth/-/issues/106
+    mkdir -p $out/share/plymouth/themes/spinfinity
+    ln -s $logo $out/share/plymouth/themes/spinfinity/header-image.png
+  '';
+
   themesEnv = pkgs.buildEnv {
     name = "plymouth-themes";
-    paths = [ plymouth ] ++ cfg.themePackages;
+    paths = [
+      plymouth
+      plymouthLogos
+    ] ++ cfg.themePackages;
   };
 
   configFile = pkgs.writeText "plymouthd.conf" ''
     [Daemon]
     ShowDelay=0
+    DeviceTimeout=8
     Theme=${cfg.theme}
     ${cfg.extraConfig}
   '';
@@ -38,8 +60,16 @@ in
 
       enable = mkEnableOption "Plymouth boot splash screen";
 
+      font = mkOption {
+        default = "${pkgs.dejavu_fonts.minimal}/share/fonts/truetype/DejaVuSans.ttf";
+        type = types.path;
+        description = ''
+          Font file made available for displaying text on the splash screen.
+        '';
+      };
+
       themePackages = mkOption {
-        default = [ nixosBreezePlymouth ];
+        default = lib.optional (cfg.theme == "breeze") nixosBreezePlymouth;
         type = types.listOf types.package;
         description = ''
           Extra theme packages for plymouth.
@@ -47,7 +77,7 @@ in
       };
 
       theme = mkOption {
-        default = "breeze";
+        default = "bgrt";
         type = types.str;
         description = ''
           Splash screen theme.
@@ -56,7 +86,8 @@ in
 
       logo = mkOption {
         type = types.path;
-        default = "${nixos-icons}/share/icons/hicolor/128x128/apps/nix-snowflake.png";
+        # Dimensions are 48x48 to match GDM logo
+        default = "${nixos-icons}/share/icons/hicolor/48x48/apps/nix-snowflake-white.png";
         defaultText = ''pkgs.fetchurl {
           url = "https://nixos.org/logo/nixos-hires.png";
           sha256 = "1ivzgd7iz0i06y36p8m5w48fd8pjqwxhdaavc0pxs7w1g7mcy5si";
@@ -102,37 +133,62 @@ in
     systemd.services.plymouth-poweroff.wantedBy = [ "poweroff.target" ];
     systemd.services.plymouth-reboot.wantedBy = [ "reboot.target" ];
     systemd.services.plymouth-read-write.wantedBy = [ "sysinit.target" ];
-    systemd.services.systemd-ask-password-plymouth.wantedBy = ["multi-user.target"];
-    systemd.paths.systemd-ask-password-plymouth.wantedBy = ["multi-user.target"];
+    systemd.services.systemd-ask-password-plymouth.wantedBy = [ "multi-user.target" ];
+    systemd.paths.systemd-ask-password-plymouth.wantedBy = [ "multi-user.target" ];
 
     boot.initrd.extraUtilsCommands = ''
-      copy_bin_and_libs ${pkgs.plymouth}/bin/plymouthd
-      copy_bin_and_libs ${pkgs.plymouth}/bin/plymouth
+      copy_bin_and_libs ${plymouth}/bin/plymouth
+      copy_bin_and_libs ${plymouth}/bin/plymouthd
+
+      # Check if the actual requested theme is here
+      if [[ ! -d ${themesEnv}/share/plymouth/themes/${cfg.theme} ]]; then
+          echo "The requested theme: ${cfg.theme} is not provided by any of the packages in boot.plymouth.themePackages"
+          exit 1
+      fi
 
       moduleName="$(sed -n 's,ModuleName *= *,,p' ${themesEnv}/share/plymouth/themes/${cfg.theme}/${cfg.theme}.plymouth)"
 
       mkdir -p $out/lib/plymouth/renderers
       # module might come from a theme
-      cp ${themesEnv}/lib/plymouth/{text,details,$moduleName}.so $out/lib/plymouth
+      cp ${themesEnv}/lib/plymouth/{text,details,label,$moduleName}.so $out/lib/plymouth
       cp ${plymouth}/lib/plymouth/renderers/{drm,frame-buffer}.so $out/lib/plymouth/renderers
 
       mkdir -p $out/share/plymouth/themes
       cp ${plymouth}/share/plymouth/plymouthd.defaults $out/share/plymouth
 
-      # copy themes into working directory for patching
+      # Copy themes into working directory for patching
       mkdir themes
-      # use -L to copy the directories proper, not the symlinks to them
-      cp -r -L ${themesEnv}/share/plymouth/themes/{text,details,${cfg.theme}} themes
 
-      # patch out any attempted references to the theme or plymouth's themes directory
+      # Use -L to copy the directories proper, not the symlinks to them.
+      # Copy all themes because they're not large assets, and bgrt depends on the ImageDir of
+      # the spinner theme.
+      cp -r -L ${themesEnv}/share/plymouth/themes/* themes
+
+      # Patch out any attempted references to the theme or plymouth's themes directory
       chmod -R +w themes
       find themes -type f | while read file
       do
         sed -i "s,/nix/.*/share/plymouth/themes,$out/share/plymouth/themes,g" $file
       done
 
+      # Install themes
       cp -r themes/* $out/share/plymouth/themes
-      cp ${cfg.logo} $out/share/plymouth/logo.png
+
+      # Install logo
+      mkdir -p $out/etc/plymouth
+      cp -r -L ${themesEnv}/etc/plymouth $out
+
+      # Setup font
+      mkdir -p $out/share/fonts
+      cp ${cfg.font} $out/share/fonts
+      mkdir -p $out/etc/fonts
+      cat > $out/etc/fonts/fonts.conf <<EOF
+      <?xml version="1.0"?>
+      <!DOCTYPE fontconfig SYSTEM "urn:fontconfig:fonts.dtd">
+      <fontconfig>
+          <dir>$out/share/fonts</dir>
+      </fontconfig>
+      EOF
     '';
 
     boot.initrd.extraUtilsCommandsTest = ''
@@ -154,6 +210,7 @@ in
       ln -s $extraUtils/share/plymouth/logo.png /etc/plymouth/logo.png
       ln -s $extraUtils/share/plymouth/themes /etc/plymouth/themes
       ln -s $extraUtils/lib/plymouth /etc/plymouth/plugins
+      ln -s $extraUtils/etc/fonts /etc/fonts
 
       plymouthd --mode=boot --pid-file=/run/plymouth/pid --attach-to-session
       plymouth show-splash
diff --git a/nixos/modules/system/boot/stage-1-init.sh b/nixos/modules/system/boot/stage-1-init.sh
index 5b39f34200cdc..ddaf985878e04 100644
--- a/nixos/modules/system/boot/stage-1-init.sh
+++ b/nixos/modules/system/boot/stage-1-init.sh
@@ -614,11 +614,16 @@ echo /sbin/modprobe > /proc/sys/kernel/modprobe
 
 
 # Start stage 2.  `switch_root' deletes all files in the ramfs on the
-# current root.  Note that $stage2Init might be an absolute symlink,
-# in which case "-e" won't work because we're not in the chroot yet.
-if [ ! -e "$targetRoot/$stage2Init" ] && [ ! -L "$targetRoot/$stage2Init" ] ; then
-    echo "stage 2 init script ($targetRoot/$stage2Init) not found"
-    fail
+# current root.  The path has to be valid in the chroot not outside.
+if [ ! -e "$targetRoot/$stage2Init" ]; then
+    stage2Check=${stage2Init}
+    while [ "$stage2Check" != "${stage2Check%/*}" ] && [ ! -L "$targetRoot/$stage2Check" ]; do
+        stage2Check=${stage2Check%/*}
+    done
+    if [ ! -L "$targetRoot/$stage2Check" ]; then
+        echo "stage 2 init script ($targetRoot/$stage2Init) not found"
+        fail
+    fi
 fi
 
 mkdir -m 0755 -p $targetRoot/proc $targetRoot/sys $targetRoot/dev $targetRoot/run
diff --git a/nixos/modules/system/boot/stage-1.nix b/nixos/modules/system/boot/stage-1.nix
index 44287f3cf09b3..4074f2e02352d 100644
--- a/nixos/modules/system/boot/stage-1.nix
+++ b/nixos/modules/system/boot/stage-1.nix
@@ -205,13 +205,22 @@ let
     ''; # */
 
 
+  # Networkd link files are used early by udev to set up interfaces early.
+  # This must be done in stage 1 to avoid race conditions between udev and
+  # network daemons.
   linkUnits = pkgs.runCommand "link-units" {
       allowedReferences = [ extraUtils ];
       preferLocalBuild = true;
-    } ''
+    } (''
       mkdir -p $out
       cp -v ${udev}/lib/systemd/network/*.link $out/
-    '';
+      '' + (
+      let
+        links = filterAttrs (n: v: hasSuffix ".link" n) config.systemd.network.units;
+        files = mapAttrsToList (n: v: "${v.unit}/${n}") links;
+      in
+        concatMapStringsSep "\n" (file: "cp -v ${file} $out/") files
+      ));
 
   udevRules = pkgs.runCommand "udev-rules" {
       allowedReferences = [ extraUtils ];
diff --git a/nixos/modules/tasks/network-interfaces-systemd.nix b/nixos/modules/tasks/network-interfaces-systemd.nix
index 088bffd7c5084..23e1e611a71e9 100644
--- a/nixos/modules/tasks/network-interfaces-systemd.nix
+++ b/nixos/modules/tasks/network-interfaces-systemd.nix
@@ -93,17 +93,7 @@ in
             (if i.useDHCP != null then i.useDHCP else false));
           address = forEach (interfaceIps i)
             (ip: "${ip.address}/${toString ip.prefixLength}");
-          # IPv6PrivacyExtensions=kernel seems to be broken with networkd.
-          # Instead of using IPv6PrivacyExtensions=kernel, configure it according to the value of
-          # `tempAddress`:
-          networkConfig.IPv6PrivacyExtensions = {
-            # generate temporary addresses and use them by default
-            "default" = true;
-            # generate temporary addresses but keep using the standard EUI-64 ones by default
-            "enabled" = "prefer-public";
-            # completely disable temporary addresses
-            "disabled" = false;
-          }.${i.tempAddress};
+          networkConfig.IPv6PrivacyExtensions = "kernel";
           linkConfig = optionalAttrs (i.macAddress != null) {
             MACAddress = i.macAddress;
           } // optionalAttrs (i.mtu != null) {
diff --git a/nixos/modules/virtualisation/amazon-init.nix b/nixos/modules/virtualisation/amazon-init.nix
index c5470b7af09b0..be83607c0af72 100644
--- a/nixos/modules/virtualisation/amazon-init.nix
+++ b/nixos/modules/virtualisation/amazon-init.nix
@@ -1,6 +1,10 @@
-{ config, pkgs, ... }:
+{ config, lib, pkgs, ... }:
+
+with lib;
 
 let
+  cfg = config.virtualisation.amazon-init;
+
   script = ''
     #!${pkgs.runtimeShell} -eu
 
@@ -41,20 +45,33 @@ let
     nixos-rebuild switch
   '';
 in {
-  systemd.services.amazon-init = {
-    inherit script;
-    description = "Reconfigure the system from EC2 userdata on startup";
 
-    wantedBy = [ "multi-user.target" ];
-    after = [ "multi-user.target" ];
-    requires = [ "network-online.target" ];
+  options.virtualisation.amazon-init = {
+    enable = mkOption {
+      default = true;
+      type = types.bool;
+      description = ''
+        Enable or disable the amazon-init service.
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.amazon-init = {
+      inherit script;
+      description = "Reconfigure the system from EC2 userdata on startup";
+
+      wantedBy = [ "multi-user.target" ];
+      after = [ "multi-user.target" ];
+      requires = [ "network-online.target" ];
 
-    restartIfChanged = false;
-    unitConfig.X-StopOnRemoval = false;
+      restartIfChanged = false;
+      unitConfig.X-StopOnRemoval = false;
 
-    serviceConfig = {
-      Type = "oneshot";
-      RemainAfterExit = true;
+      serviceConfig = {
+        Type = "oneshot";
+        RemainAfterExit = true;
+      };
     };
   };
 }
diff --git a/nixos/modules/virtualisation/anbox.nix b/nixos/modules/virtualisation/anbox.nix
index da5df35807346..7b096bd1a9fbb 100644
--- a/nixos/modules/virtualisation/anbox.nix
+++ b/nixos/modules/virtualisation/anbox.nix
@@ -98,7 +98,6 @@ in
       environment.XDG_RUNTIME_DIR="${anboxloc}";
 
       wantedBy = [ "multi-user.target" ];
-      after = [ "systemd-udev-settle.service" ];
       preStart = let
         initsh = pkgs.writeText "nixos-init" (''
           #!/system/bin/sh
diff --git a/nixos/modules/virtualisation/containerd.nix b/nixos/modules/virtualisation/containerd.nix
new file mode 100644
index 0000000000000..194276d169588
--- /dev/null
+++ b/nixos/modules/virtualisation/containerd.nix
@@ -0,0 +1,60 @@
+{ pkgs, lib, config, ... }:
+let
+  cfg = config.virtualisation.containerd;
+  containerdConfigChecked = pkgs.runCommand "containerd-config-checked.toml" { nativeBuildInputs = [pkgs.containerd]; } ''
+    containerd -c ${cfg.configFile} config dump >/dev/null
+    ln -s ${cfg.configFile} $out
+  '';
+in
+{
+
+  options.virtualisation.containerd = with lib.types; {
+    enable = lib.mkEnableOption "containerd container runtime";
+
+    configFile = lib.mkOption {
+      default = null;
+      description = "path to containerd config file";
+      type = nullOr path;
+    };
+
+    args = lib.mkOption {
+      default = {};
+      description = "extra args to append to the containerd cmdline";
+      type = attrsOf str;
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    virtualisation.containerd.args.config = lib.mkIf (cfg.configFile != null) (toString containerdConfigChecked);
+
+    environment.systemPackages = [pkgs.containerd];
+
+    systemd.services.containerd = {
+      description = "containerd - container runtime";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+      path = with pkgs; [
+        containerd
+        runc
+        iptables
+      ];
+      serviceConfig = {
+        ExecStart = ''${pkgs.containerd}/bin/containerd ${lib.concatStringsSep " " (lib.cli.toGNUCommandLine {} cfg.args)}'';
+        Delegate = "yes";
+        KillMode = "process";
+        Type = "notify";
+        Restart = "always";
+        RestartSec = "5";
+        StartLimitBurst = "8";
+        StartLimitIntervalSec = "120s";
+
+        # "limits" defined below are adopted from upstream: https://github.com/containerd/containerd/blob/master/containerd.service
+        LimitNPROC = "infinity";
+        LimitCORE = "infinity";
+        LimitNOFILE = "infinity";
+        TasksMax = "infinity";
+        OOMScoreAdjust = "-999";
+      };
+    };
+  };
+}
diff --git a/nixos/modules/virtualisation/fetch-instance-ssh-keys.bash b/nixos/modules/virtualisation/fetch-instance-ssh-keys.bash
new file mode 100644
index 0000000000000..4a8601961115b
--- /dev/null
+++ b/nixos/modules/virtualisation/fetch-instance-ssh-keys.bash
@@ -0,0 +1,36 @@
+#!/usr/bin/env bash
+
+set -euo pipefail
+
+WGET() {
+    wget --retry-connrefused -t 15 --waitretry=10 --header='Metadata-Flavor: Google' "$@"
+}
+
+# When dealing with cryptographic keys, we want to keep things private.
+umask 077
+mkdir -p /root/.ssh
+
+echo "Fetching authorized keys..."
+WGET -O /tmp/auth_keys http://metadata.google.internal/computeMetadata/v1/instance/attributes/sshKeys
+
+# Read keys one by one, split in case Google decided
+# to append metadata (it does sometimes) and add to
+# authorized_keys if not already present.
+touch /root/.ssh/authorized_keys
+while IFS='' read -r line || [[ -n "$line" ]]; do
+    keyLine=$(echo -n "$line" | cut -d ':' -f2)
+    IFS=' ' read -r -a array <<<"$keyLine"
+    if [[ ${#array[@]} -ge 3 ]]; then
+        echo "${array[@]:0:3}" >>/tmp/new_keys
+        echo "Added ${array[*]:2} to authorized_keys"
+    fi
+done </tmp/auth_keys
+mv /tmp/new_keys /root/.ssh/authorized_keys
+chmod 600 /root/.ssh/authorized_keys
+
+echo "Fetching host keys..."
+WGET -O /tmp/ssh_host_ed25519_key http://metadata.google.internal/computeMetadata/v1/instance/attributes/ssh_host_ed25519_key
+WGET -O /tmp/ssh_host_ed25519_key.pub http://metadata.google.internal/computeMetadata/v1/instance/attributes/ssh_host_ed25519_key_pub
+mv -f /tmp/ssh_host_ed25519_key* /etc/ssh/
+chmod 600 /etc/ssh/ssh_host_ed25519_key
+chmod 644 /etc/ssh/ssh_host_ed25519_key.pub
diff --git a/nixos/modules/virtualisation/google-compute-config.nix b/nixos/modules/virtualisation/google-compute-config.nix
index 327324f2921da..b6b1ffa395814 100644
--- a/nixos/modules/virtualisation/google-compute-config.nix
+++ b/nixos/modules/virtualisation/google-compute-config.nix
@@ -69,6 +69,31 @@ in
   # GC has 1460 MTU
   networking.interfaces.eth0.mtu = 1460;
 
+  # Used by NixOps
+  systemd.services.fetch-instance-ssh-keys = {
+    description = "Fetch host keys and authorized_keys for root user";
+
+    wantedBy = [ "sshd.service" ];
+    before = [ "sshd.service" ];
+    after = [ "network-online.target" ];
+    wants = [ "network-online.target" ];
+    path = [ pkgs.wget ];
+
+    serviceConfig = {
+      Type = "oneshot";
+      ExecStart = pkgs.runCommand "fetch-instance-ssh-keys" { } ''
+        cp ${./fetch-instance-ssh-keys.bash} $out
+        chmod +x $out
+        ${pkgs.shfmt}/bin/shfmt -i 4 -d $out
+        ${pkgs.shellcheck}/bin/shellcheck $out
+        patchShebangs $out
+      '';
+      PrivateTmp = true;
+      StandardError = "journal+console";
+      StandardOutput = "journal+console";
+    };
+  };
+
   systemd.services.google-instance-setup = {
     description = "Google Compute Engine Instance Setup";
     after = [ "network-online.target" "network.target" "rsyslog.service" ];
diff --git a/nixos/modules/virtualisation/hyperv-guest.nix b/nixos/modules/virtualisation/hyperv-guest.nix
index adc2810a99398..a3656c307f963 100644
--- a/nixos/modules/virtualisation/hyperv-guest.nix
+++ b/nixos/modules/virtualisation/hyperv-guest.nix
@@ -31,6 +31,8 @@ in {
         "hv_balloon" "hv_netvsc" "hv_storvsc" "hv_utils" "hv_vmbus"
       ];
 
+      initrd.availableKernelModules = [ "hyperv_keyboard" ];
+
       kernelParams = [
         "video=hyperv_fb:${cfg.videoMode} elevator=noop"
       ];
@@ -38,8 +40,6 @@ in {
 
     environment.systemPackages = [ config.boot.kernelPackages.hyperv-daemons.bin ];
 
-    security.rngd.enable = false;
-
     # enable hotadding cpu/memory
     services.udev.packages = lib.singleton (pkgs.writeTextFile {
       name = "hyperv-cpu-and-memory-hotadd-udev-rules";
diff --git a/nixos/modules/virtualisation/libvirtd.nix b/nixos/modules/virtualisation/libvirtd.nix
index 1d6a9457dde41..6357baf29e012 100644
--- a/nixos/modules/virtualisation/libvirtd.nix
+++ b/nixos/modules/virtualisation/libvirtd.nix
@@ -213,7 +213,7 @@ in {
 
     systemd.services.libvirtd = {
       requires = [ "libvirtd-config.service" ];
-      after = [ "systemd-udev-settle.service" "libvirtd-config.service" ]
+      after = [ "libvirtd-config.service" ]
               ++ optional vswitch.enable "ovs-vswitchd.service";
 
       environment.LIBVIRTD_ARGS = escapeShellArgs (
diff --git a/nixos/modules/virtualisation/lxd.nix b/nixos/modules/virtualisation/lxd.nix
index 4b2adf4cc699b..96e8d68ae50e9 100644
--- a/nixos/modules/virtualisation/lxd.nix
+++ b/nixos/modules/virtualisation/lxd.nix
@@ -66,7 +66,7 @@ in {
         type = types.bool;
         default = false;
         description = ''
-          enables various settings to avoid common pitfalls when
+          Enables various settings to avoid common pitfalls when
           running containers requiring many file operations.
           Fixes errors like "Too many open files" or
           "neighbour: ndisc_cache: neighbor table overflow!".
@@ -74,6 +74,17 @@ in {
           for details.
         '';
       };
+
+      startTimeout = mkOption {
+        type = types.int;
+        default = 600;
+        apply = toString;
+        description = ''
+          Time to wait (in seconds) for LXD to become ready to process requests.
+          If LXD does not reply within the configured time, lxd.service will be
+          considered failed and systemd will attempt to restart it.
+        '';
+      };
     };
   };
 
@@ -81,40 +92,58 @@ in {
   config = mkIf cfg.enable {
     environment.systemPackages = [ cfg.package ];
 
-    security.apparmor = {
-      enable = true;
-      profiles = [
-        "${cfg.lxcPackage}/etc/apparmor.d/usr.bin.lxc-start"
-        "${cfg.lxcPackage}/etc/apparmor.d/lxc-containers"
-      ];
-      packages = [ cfg.lxcPackage ];
-    };
+    # Note: the following options are also declared in virtualisation.lxc, but
+    # the latter can't be simply enabled to reuse the formers, because it
+    # does a bunch of unrelated things.
+    systemd.tmpfiles.rules = [ "d /var/lib/lxc/rootfs 0755 root root -" ];
+
+    security.apparmor.packages = [ cfg.lxcPackage ];
+    security.apparmor.profiles = [
+      "${cfg.lxcPackage}/etc/apparmor.d/lxc-containers"
+      "${cfg.lxcPackage}/etc/apparmor.d/usr.bin.lxc-start"
+    ];
 
     # TODO: remove once LXD gets proper support for cgroupsv2
     # (currently most of the e.g. CPU accounting stuff doesn't work)
     systemd.enableUnifiedCgroupHierarchy = false;
 
+    systemd.sockets.lxd = {
+      description = "LXD UNIX socket";
+      wantedBy = [ "sockets.target" ];
+
+      socketConfig = {
+        ListenStream = "/var/lib/lxd/unix.socket";
+        SocketMode = "0660";
+        SocketGroup = "lxd";
+        Service = "lxd.service";
+      };
+    };
+
     systemd.services.lxd = {
       description = "LXD Container Management Daemon";
 
       wantedBy = [ "multi-user.target" ];
-      after = [ "systemd-udev-settle.service" ];
+      after = [ "network-online.target" "lxcfs.service" ];
+      requires = [ "network-online.target" "lxd.socket"  "lxcfs.service" ];
+      documentation = [ "man:lxd(1)" ];
 
-      path = lib.optional config.boot.zfs.enabled config.boot.zfs.package;
-
-      preStart = ''
-        mkdir -m 0755 -p /var/lib/lxc/rootfs
-      '';
+      path = optional cfg.zfsSupport config.boot.zfs.package;
 
       serviceConfig = {
         ExecStart = "@${cfg.package}/bin/lxd lxd --group lxd";
-        Type = "simple";
+        ExecStartPost = "${cfg.package}/bin/lxd waitready --timeout=${cfg.startTimeout}";
+        ExecStop = "${cfg.package}/bin/lxd shutdown";
+
         KillMode = "process"; # when stopping, leave the containers alone
         LimitMEMLOCK = "infinity";
         LimitNOFILE = "1048576";
         LimitNPROC = "infinity";
         TasksMax = "infinity";
 
+        Restart = "on-failure";
+        TimeoutStartSec = "${cfg.startTimeout}s";
+        TimeoutStopSec = "30s";
+
         # By default, `lxd` loads configuration files from hard-coded
         # `/usr/share/lxc/config` - since this is a no-go for us, we have to
         # explicitly tell it where the actual configuration files are
diff --git a/nixos/modules/virtualisation/nixos-containers.nix b/nixos/modules/virtualisation/nixos-containers.nix
index f06977f88fc16..3754fe6dac6da 100644
--- a/nixos/modules/virtualisation/nixos-containers.nix
+++ b/nixos/modules/virtualisation/nixos-containers.nix
@@ -271,8 +271,8 @@ let
     DeviceAllow = map (d: "${d.node} ${d.modifier}") cfg.allowedDevices;
   };
 
-
   system = config.nixpkgs.localSystem.system;
+  kernelVersion = config.boot.kernelPackages.kernel.version;
 
   bindMountOpts = { name, ... }: {
 
@@ -321,7 +321,6 @@ let
     };
   };
 
-
   mkBindFlag = d:
                let flagPrefix = if d.isReadOnly then " --bind-ro=" else " --bind=";
                    mountstr = if d.hostPath != null then "${d.hostPath}:${d.mountPoint}" else "${d.mountPoint}";
@@ -482,11 +481,16 @@ in
                           networking.useDHCP = false;
                           assertions = [
                             {
-                              assertion =  config.privateNetwork -> stringLength name < 12;
+                              assertion =
+                                (builtins.compareVersions kernelVersion "5.8" <= 0)
+                                -> config.privateNetwork
+                                -> stringLength name <= 11;
                               message = ''
                                 Container name `${name}` is too long: When `privateNetwork` is enabled, container names can
                                 not be longer than 11 characters, because the container's interface name is derived from it.
-                                This might be fixed in the future. See https://github.com/NixOS/nixpkgs/issues/38509
+                                You should either make the container name shorter or upgrade to a more recent kernel that
+                                supports interface altnames (i.e. at least Linux 5.8 - please see https://github.com/NixOS/nixpkgs/issues/38509
+                                for details).
                               '';
                             }
                           ];
diff --git a/nixos/modules/virtualisation/podman.nix b/nixos/modules/virtualisation/podman.nix
index 98da5a096d911..0223c0df1f222 100644
--- a/nixos/modules/virtualisation/podman.nix
+++ b/nixos/modules/virtualisation/podman.nix
@@ -105,6 +105,16 @@ in
           }));
       };
 
+      systemd.packages = [ cfg.package ];
+
+      systemd.services.podman.serviceConfig = {
+        ExecStart = [ "" "${cfg.package}/bin/podman $LOGGING system service" ];
+      };
+
+      systemd.sockets.podman.wantedBy = [ "sockets.target" ];
+
+      systemd.tmpfiles.packages = [ cfg.package ];
+
       assertions = [
         {
           assertion = cfg.dockerCompat -> !config.virtualisation.docker.enable;
diff --git a/nixos/modules/virtualisation/qemu-vm.nix b/nixos/modules/virtualisation/qemu-vm.nix
index bf3615f2fe71b..d9935bcafb716 100644
--- a/nixos/modules/virtualisation/qemu-vm.nix
+++ b/nixos/modules/virtualisation/qemu-vm.nix
@@ -7,7 +7,7 @@
 # the VM in the host.  On the other hand, the root filesystem is a
 # read/writable disk image persistent across VM reboots.
 
-{ config, lib, pkgs, ... }:
+{ config, lib, pkgs, options, ... }:
 
 with lib;
 with import ../../lib/qemu-flags.nix { inherit pkgs; };
@@ -266,6 +266,8 @@ in
 
   options = {
 
+    virtualisation.fileSystems = options.fileSystems;
+
     virtualisation.memorySize =
       mkOption {
         default = 384;
@@ -275,6 +277,18 @@ in
           '';
       };
 
+    virtualisation.msize =
+      mkOption {
+        default = null;
+        type = types.nullOr types.ints.unsigned;
+        description =
+          ''
+            msize (maximum packet size) option passed to 9p file systems, in
+            bytes. Increasing this should increase performance significantly,
+            at the cost of higher RAM usage.
+          '';
+      };
+
     virtualisation.diskSize =
       mkOption {
         default = 512;
@@ -659,11 +673,12 @@ in
     # attribute should be disregarded for the purpose of building a VM
     # test image (since those filesystems don't exist in the VM).
     fileSystems = mkVMOverride (
+      cfg.fileSystems //
       { "/".device = cfg.bootDevice;
         ${if cfg.writableStore then "/nix/.ro-store" else "/nix/store"} =
           { device = "store";
             fsType = "9p";
-            options = [ "trans=virtio" "version=9p2000.L" "cache=loose" ];
+            options = [ "trans=virtio" "version=9p2000.L" "cache=loose" ] ++ lib.optional (cfg.msize != null) "msize=${toString cfg.msize}";
             neededForBoot = true;
           };
         "/tmp" = mkIf config.boot.tmpOnTmpfs
@@ -676,13 +691,13 @@ in
         "/tmp/xchg" =
           { device = "xchg";
             fsType = "9p";
-            options = [ "trans=virtio" "version=9p2000.L" ];
+            options = [ "trans=virtio" "version=9p2000.L" ] ++ lib.optional (cfg.msize != null) "msize=${toString cfg.msize}";
             neededForBoot = true;
           };
         "/tmp/shared" =
           { device = "shared";
             fsType = "9p";
-            options = [ "trans=virtio" "version=9p2000.L" ];
+            options = [ "trans=virtio" "version=9p2000.L" ] ++ lib.optional (cfg.msize != null) "msize=${toString cfg.msize}";
             neededForBoot = true;
           };
       } // optionalAttrs (cfg.writableStore && cfg.writableStoreUseTmpfs)
diff --git a/nixos/release.nix b/nixos/release.nix
index 109747945f78a..327a259de7faf 100644
--- a/nixos/release.nix
+++ b/nixos/release.nix
@@ -171,23 +171,23 @@ in rec {
 
   sd_image = forMatchingSystems [ "armv6l-linux" "armv7l-linux" "aarch64-linux" ] (system: makeSdImage {
     module = {
-        armv6l-linux = ./modules/installer/cd-dvd/sd-image-raspberrypi.nix;
-        armv7l-linux = ./modules/installer/cd-dvd/sd-image-armv7l-multiplatform.nix;
-        aarch64-linux = ./modules/installer/cd-dvd/sd-image-aarch64.nix;
+        armv6l-linux = ./modules/installer/sd-card/sd-image-raspberrypi-installer.nix;
+        armv7l-linux = ./modules/installer/sd-card/sd-image-armv7l-multiplatform-installer.nix;
+        aarch64-linux = ./modules/installer/sd-card/sd-image-aarch64-installer.nix;
       }.${system};
     inherit system;
   });
 
   sd_image_new_kernel = forMatchingSystems [ "aarch64-linux" ] (system: makeSdImage {
     module = {
-        aarch64-linux = ./modules/installer/cd-dvd/sd-image-aarch64-new-kernel.nix;
+        aarch64-linux = ./modules/installer/sd-card/sd-image-aarch64-new-kernel-installer.nix;
       }.${system};
     type = "minimal-new-kernel";
     inherit system;
   });
 
   sd_image_raspberrypi4 = forMatchingSystems [ "aarch64-linux" ] (system: makeSdImage {
-    module = ./modules/installer/cd-dvd/sd-image-raspberrypi4.nix;
+    module = ./modules/installer/sd-card/sd-image-raspberrypi4-installer.nix;
     inherit system;
   });
 
diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix
index 444580bc0bed6..00e84a9df82c0 100644
--- a/nixos/tests/all-tests.nix
+++ b/nixos/tests/all-tests.nix
@@ -55,6 +55,7 @@ in
   cassandra_3_11 = handleTest ./cassandra.nix { testPackage = pkgs.cassandra_3_11; };
   ceph-multi-node = handleTestOn ["x86_64-linux"] ./ceph-multi-node.nix {};
   ceph-single-node = handleTestOn ["x86_64-linux"] ./ceph-single-node.nix {};
+  ceph-single-node-bluestore = handleTestOn ["x86_64-linux"] ./ceph-single-node-bluestore.nix {};
   certmgr = handleTest ./certmgr.nix {};
   cfssl = handleTestOn ["x86_64-linux"] ./cfssl.nix {};
   charliecloud = handleTest ./charliecloud.nix {};
@@ -72,6 +73,7 @@ in
   containers-imperative = handleTest ./containers-imperative.nix {};
   containers-ip = handleTest ./containers-ip.nix {};
   containers-macvlans = handleTest ./containers-macvlans.nix {};
+  containers-names = handleTest ./containers-names.nix {};
   containers-physical_interfaces = handleTest ./containers-physical_interfaces.nix {};
   containers-portforward = handleTest ./containers-portforward.nix {};
   containers-reloadable = handleTest ./containers-reloadable.nix {};
@@ -81,6 +83,7 @@ in
   corerad = handleTest ./corerad.nix {};
   couchdb = handleTest ./couchdb.nix {};
   cri-o = handleTestOn ["x86_64-linux"] ./cri-o.nix {};
+  custom-ca = handleTest ./custom-ca.nix {};
   deluge = handleTest ./deluge.nix {};
   dhparams = handleTest ./dhparams.nix {};
   dnscrypt-proxy2 = handleTestOn ["x86_64-linux"] ./dnscrypt-proxy2.nix {};
@@ -181,10 +184,10 @@ in
   jenkins = handleTest ./jenkins.nix {};
   jirafeau = handleTest ./jirafeau.nix {};
   jitsi-meet = handleTest ./jitsi-meet.nix {};
-  jq = handleTest ./jq.nix {};
   k3s = handleTest ./k3s.nix {};
   kafka = handleTest ./kafka.nix {};
   keepalived = handleTest ./keepalived.nix {};
+  keepassxc = handleTest ./keepassxc.nix {};
   kerberos = handleTest ./kerberos/default.nix {};
   kernel-latest = handleTest ./kernel-latest.nix {};
   kernel-lts = handleTest ./kernel-lts.nix {};
@@ -194,6 +197,7 @@ in
   keymap = handleTest ./keymap.nix {};
   knot = handleTest ./knot.nix {};
   krb5 = discoverTests (import ./krb5 {});
+  ksm = handleTest ./ksm.nix {};
   kubernetes.dns = handleTestOn ["x86_64-linux"] ./kubernetes/dns.nix {};
   # kubernetes.e2e should eventually replace kubernetes.rbac when it works
   #kubernetes.e2e = handleTestOn ["x86_64-linux"] ./kubernetes/e2e.nix {};
@@ -236,6 +240,7 @@ in
   mosquitto = handleTest ./mosquitto.nix {};
   mpd = handleTest ./mpd.nix {};
   mumble = handleTest ./mumble.nix {};
+  musescore = handleTest ./musescore.nix {};
   munin = handleTest ./munin.nix {};
   mutableUsers = handleTest ./mutable-users.nix {};
   mxisd = handleTest ./mxisd.nix {};
@@ -302,9 +307,13 @@ in
   pgjwt = handleTest ./pgjwt.nix {};
   pgmanage = handleTest ./pgmanage.nix {};
   php = handleTest ./php {};
+  php73 = handleTest ./php { php = pkgs.php73; };
+  php74 = handleTest ./php { php = pkgs.php74; };
+  php80 = handleTest ./php { php = pkgs.php80; };
   pinnwand = handleTest ./pinnwand.nix {};
   plasma5 = handleTest ./plasma5.nix {};
   pleroma = handleTestOn [ "x86_64-linux" "aarch64-linux" ] ./pleroma.nix {};
+  plikd = handleTest ./plikd.nix {};
   plotinus = handleTest ./plotinus.nix {};
   podman = handleTestOn ["x86_64-linux"] ./podman.nix {};
   postfix = handleTest ./postfix.nix {};
@@ -317,6 +326,7 @@ in
   predictable-interface-names = handleTest ./predictable-interface-names.nix {};
   printing = handleTest ./printing.nix {};
   privacyidea = handleTest ./privacyidea.nix {};
+  privoxy = handleTest ./privoxy.nix {};
   prometheus = handleTest ./prometheus.nix {};
   prometheus-exporters = handleTest ./prometheus-exporters.nix {};
   prosody = handleTest ./xmpp/prosody.nix {};
@@ -332,7 +342,6 @@ in
   redis = handleTest ./redis.nix {};
   redmine = handleTest ./redmine.nix {};
   restic = handleTest ./restic.nix {};
-  ripgrep = handleTest ./ripgrep.nix {};
   robustirc-bridge = handleTest ./robustirc-bridge.nix {};
   roundcube = handleTest ./roundcube.nix {};
   rspamd = handleTest ./rspamd.nix {};
@@ -343,8 +352,6 @@ in
   samba = handleTest ./samba.nix {};
   samba-wsdd = handleTest ./samba-wsdd.nix {};
   sanoid = handleTest ./sanoid.nix {};
-  sbt = handleTest ./sbt.nix {};
-  sbt-extras = handleTest ./sbt-extras.nix {};
   sddm = handleTest ./sddm.nix {};
   searx = handleTest ./searx.nix {};
   service-runner = handleTest ./service-runner.nix {};
diff --git a/nixos/tests/bees.nix b/nixos/tests/bees.nix
index 6e6a9c3446b06..58a9c29513567 100644
--- a/nixos/tests/bees.nix
+++ b/nixos/tests/bees.nix
@@ -8,7 +8,7 @@ import ./make-test-python.nix ({ lib, pkgs, ... }:
       ${pkgs.btrfs-progs}/bin/mkfs.btrfs -f -L aux2 /dev/vdc
     '';
     virtualisation.emptyDiskImages = [ 4096 4096 ];
-    fileSystems = lib.mkVMOverride {
+    virtualisation.fileSystems = {
       "/aux1" = { # filesystem configured to be deduplicated
         device = "/dev/disk/by-label/aux1";
         fsType = "btrfs";
diff --git a/nixos/tests/bind.nix b/nixos/tests/bind.nix
index 09917b15a8e06..7234f56a1c3a8 100644
--- a/nixos/tests/bind.nix
+++ b/nixos/tests/bind.nix
@@ -6,6 +6,7 @@ import ./make-test-python.nix {
     services.bind.extraOptions = "empty-zones-enable no;";
     services.bind.zones = lib.singleton {
       name = ".";
+      master = true;
       file = pkgs.writeText "root.zone" ''
         $TTL 3600
         . IN SOA ns.example.org. admin.example.org. ( 1 3h 1h 1w 1d )
diff --git a/nixos/tests/ceph-single-node-bluestore.nix b/nixos/tests/ceph-single-node-bluestore.nix
new file mode 100644
index 0000000000000..cc873e8aee576
--- /dev/null
+++ b/nixos/tests/ceph-single-node-bluestore.nix
@@ -0,0 +1,196 @@
+import ./make-test-python.nix ({pkgs, lib, ...}:
+
+let
+  cfg = {
+    clusterId = "066ae264-2a5d-4729-8001-6ad265f50b03";
+    monA = {
+      name = "a";
+      ip = "192.168.1.1";
+    };
+    osd0 = {
+      name = "0";
+      key = "AQBCEJNa3s8nHRAANvdsr93KqzBznuIWm2gOGg==";
+      uuid = "55ba2294-3e24-478f-bee0-9dca4c231dd9";
+    };
+    osd1 = {
+      name = "1";
+      key = "AQBEEJNac00kExAAXEgy943BGyOpVH1LLlHafQ==";
+      uuid = "5e97a838-85b6-43b0-8950-cb56d554d1e5";
+    };
+    osd2 = {
+      name = "2";
+      key = "AQAdyhZeIaUlARAAGRoidDAmS6Vkp546UFEf5w==";
+      uuid = "ea999274-13d0-4dd5-9af9-ad25a324f72f";
+    };
+  };
+  generateCephConfig = { daemonConfig }: {
+    enable = true;
+    global = {
+      fsid = cfg.clusterId;
+      monHost = cfg.monA.ip;
+      monInitialMembers = cfg.monA.name;
+    };
+  } // daemonConfig;
+
+  generateHost = { pkgs, cephConfig, networkConfig, ... }: {
+    virtualisation = {
+      memorySize = 512;
+      emptyDiskImages = [ 20480 20480 20480 ];
+      vlans = [ 1 ];
+    };
+
+    networking = networkConfig;
+
+    environment.systemPackages = with pkgs; [
+      bash
+      sudo
+      ceph
+      xfsprogs
+    ];
+
+    boot.kernelModules = [ "xfs" ];
+
+    services.ceph = cephConfig;
+  };
+
+  networkMonA = {
+    dhcpcd.enable = false;
+    interfaces.eth1.ipv4.addresses = pkgs.lib.mkOverride 0 [
+      { address = cfg.monA.ip; prefixLength = 24; }
+    ];
+  };
+  cephConfigMonA = generateCephConfig { daemonConfig = {
+    mon = {
+      enable = true;
+      daemons = [ cfg.monA.name ];
+    };
+    mgr = {
+      enable = true;
+      daemons = [ cfg.monA.name ];
+    };
+    osd = {
+      enable = true;
+      daemons = [ cfg.osd0.name cfg.osd1.name cfg.osd2.name ];
+    };
+  }; };
+
+  # Following deployment is based on the manual deployment described here:
+  # https://docs.ceph.com/docs/master/install/manual-deployment/
+  # For other ways to deploy a ceph cluster, look at the documentation at
+  # https://docs.ceph.com/docs/master/
+  testscript = { ... }: ''
+    start_all()
+
+    monA.wait_for_unit("network.target")
+
+    # Bootstrap ceph-mon daemon
+    monA.succeed(
+        "sudo -u ceph ceph-authtool --create-keyring /tmp/ceph.mon.keyring --gen-key -n mon. --cap mon 'allow *'",
+        "sudo -u ceph ceph-authtool --create-keyring /etc/ceph/ceph.client.admin.keyring --gen-key -n client.admin --cap mon 'allow *' --cap osd 'allow *' --cap mds 'allow *' --cap mgr 'allow *'",
+        "sudo -u ceph ceph-authtool /tmp/ceph.mon.keyring --import-keyring /etc/ceph/ceph.client.admin.keyring",
+        "monmaptool --create --add ${cfg.monA.name} ${cfg.monA.ip} --fsid ${cfg.clusterId} /tmp/monmap",
+        "sudo -u ceph ceph-mon --mkfs -i ${cfg.monA.name} --monmap /tmp/monmap --keyring /tmp/ceph.mon.keyring",
+        "sudo -u ceph touch /var/lib/ceph/mon/ceph-${cfg.monA.name}/done",
+        "systemctl start ceph-mon-${cfg.monA.name}",
+    )
+    monA.wait_for_unit("ceph-mon-${cfg.monA.name}")
+    monA.succeed("ceph mon enable-msgr2")
+
+    # Can't check ceph status until a mon is up
+    monA.succeed("ceph -s | grep 'mon: 1 daemons'")
+
+    # Start the ceph-mgr daemon, after copying in the keyring
+    monA.succeed(
+        "sudo -u ceph mkdir -p /var/lib/ceph/mgr/ceph-${cfg.monA.name}/",
+        "ceph auth get-or-create mgr.${cfg.monA.name} mon 'allow profile mgr' osd 'allow *' mds 'allow *' > /var/lib/ceph/mgr/ceph-${cfg.monA.name}/keyring",
+        "systemctl start ceph-mgr-${cfg.monA.name}",
+    )
+    monA.wait_for_unit("ceph-mgr-a")
+    monA.wait_until_succeeds("ceph -s | grep 'quorum ${cfg.monA.name}'")
+    monA.wait_until_succeeds("ceph -s | grep 'mgr: ${cfg.monA.name}(active,'")
+
+    # Bootstrap OSDs
+    monA.succeed(
+        "mkdir -p /var/lib/ceph/osd/ceph-${cfg.osd0.name}",
+        "echo bluestore > /var/lib/ceph/osd/ceph-${cfg.osd0.name}/type",
+        "ln -sf /dev/vdb /var/lib/ceph/osd/ceph-${cfg.osd0.name}/block",
+        "mkdir -p /var/lib/ceph/osd/ceph-${cfg.osd1.name}",
+        "echo bluestore > /var/lib/ceph/osd/ceph-${cfg.osd1.name}/type",
+        "ln -sf /dev/vdc /var/lib/ceph/osd/ceph-${cfg.osd1.name}/block",
+        "mkdir -p /var/lib/ceph/osd/ceph-${cfg.osd2.name}",
+        "echo bluestore > /var/lib/ceph/osd/ceph-${cfg.osd2.name}/type",
+        "ln -sf /dev/vdd /var/lib/ceph/osd/ceph-${cfg.osd2.name}/block",
+        "ceph-authtool --create-keyring /var/lib/ceph/osd/ceph-${cfg.osd0.name}/keyring --name osd.${cfg.osd0.name} --add-key ${cfg.osd0.key}",
+        "ceph-authtool --create-keyring /var/lib/ceph/osd/ceph-${cfg.osd1.name}/keyring --name osd.${cfg.osd1.name} --add-key ${cfg.osd1.key}",
+        "ceph-authtool --create-keyring /var/lib/ceph/osd/ceph-${cfg.osd2.name}/keyring --name osd.${cfg.osd2.name} --add-key ${cfg.osd2.key}",
+        'echo \'{"cephx_secret": "${cfg.osd0.key}"}\' | ceph osd new ${cfg.osd0.uuid} -i -',
+        'echo \'{"cephx_secret": "${cfg.osd1.key}"}\' | ceph osd new ${cfg.osd1.uuid} -i -',
+        'echo \'{"cephx_secret": "${cfg.osd2.key}"}\' | ceph osd new ${cfg.osd2.uuid} -i -',
+    )
+
+    # Initialize the OSDs with regular filestore
+    monA.succeed(
+        "ceph-osd -i ${cfg.osd0.name} --mkfs --osd-uuid ${cfg.osd0.uuid}",
+        "ceph-osd -i ${cfg.osd1.name} --mkfs --osd-uuid ${cfg.osd1.uuid}",
+        "ceph-osd -i ${cfg.osd2.name} --mkfs --osd-uuid ${cfg.osd2.uuid}",
+        "chown -R ceph:ceph /var/lib/ceph/osd",
+        "systemctl start ceph-osd-${cfg.osd0.name}",
+        "systemctl start ceph-osd-${cfg.osd1.name}",
+        "systemctl start ceph-osd-${cfg.osd2.name}",
+    )
+    monA.wait_until_succeeds("ceph osd stat | grep -e '3 osds: 3 up[^,]*, 3 in'")
+    monA.wait_until_succeeds("ceph -s | grep 'mgr: ${cfg.monA.name}(active,'")
+    monA.wait_until_succeeds("ceph -s | grep 'HEALTH_OK'")
+
+    monA.succeed(
+        "ceph osd pool create single-node-test 32 32",
+        "ceph osd pool ls | grep 'single-node-test'",
+        "ceph osd pool rename single-node-test single-node-other-test",
+        "ceph osd pool ls | grep 'single-node-other-test'",
+    )
+    monA.wait_until_succeeds("ceph -s | grep '2 pools, 33 pgs'")
+    monA.succeed(
+        "ceph osd getcrushmap -o crush",
+        "crushtool -d crush -o decrushed",
+        "sed 's/step chooseleaf firstn 0 type host/step chooseleaf firstn 0 type osd/' decrushed > modcrush",
+        "crushtool -c modcrush -o recrushed",
+        "ceph osd setcrushmap -i recrushed",
+        "ceph osd pool set single-node-other-test size 2",
+    )
+    monA.wait_until_succeeds("ceph -s | grep 'HEALTH_OK'")
+    monA.wait_until_succeeds("ceph -s | grep '33 active+clean'")
+    monA.fail(
+        "ceph osd pool ls | grep 'multi-node-test'",
+        "ceph osd pool delete single-node-other-test single-node-other-test --yes-i-really-really-mean-it",
+    )
+
+    # Shut down ceph by stopping ceph.target.
+    monA.succeed("systemctl stop ceph.target")
+
+    # Start it up
+    monA.succeed("systemctl start ceph.target")
+    monA.wait_for_unit("ceph-mon-${cfg.monA.name}")
+    monA.wait_for_unit("ceph-mgr-${cfg.monA.name}")
+    monA.wait_for_unit("ceph-osd-${cfg.osd0.name}")
+    monA.wait_for_unit("ceph-osd-${cfg.osd1.name}")
+    monA.wait_for_unit("ceph-osd-${cfg.osd2.name}")
+
+    # Ensure the cluster comes back up again
+    monA.succeed("ceph -s | grep 'mon: 1 daemons'")
+    monA.wait_until_succeeds("ceph -s | grep 'quorum ${cfg.monA.name}'")
+    monA.wait_until_succeeds("ceph osd stat | grep -e '3 osds: 3 up[^,]*, 3 in'")
+    monA.wait_until_succeeds("ceph -s | grep 'mgr: ${cfg.monA.name}(active,'")
+    monA.wait_until_succeeds("ceph -s | grep 'HEALTH_OK'")
+  '';
+in {
+  name = "basic-single-node-ceph-cluster-bluestore";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ lukegb ];
+  };
+
+  nodes = {
+    monA = generateHost { pkgs = pkgs; cephConfig = cephConfigMonA; networkConfig = networkMonA; };
+  };
+
+  testScript = testscript;
+})
diff --git a/nixos/tests/containers-bridge.nix b/nixos/tests/containers-bridge.nix
index 1208aa8fced7e..12fa67c8b015b 100644
--- a/nixos/tests/containers-bridge.nix
+++ b/nixos/tests/containers-bridge.nix
@@ -1,5 +1,3 @@
-# Test for NixOS' container support.
-
 let
   hostIp = "192.168.0.1";
   containerIp = "192.168.0.100/24";
@@ -7,10 +5,10 @@ let
   containerIp6 = "fc00::2/7";
 in
 
-import ./make-test-python.nix ({ pkgs, ...} : {
+import ./make-test-python.nix ({ pkgs, lib, ... }: {
   name = "containers-bridge";
-  meta = with pkgs.lib.maintainers; {
-    maintainers = [ aristid aszlig eelco kampfschlaefer ];
+  meta = {
+    maintainers = with lib.maintainers; [ aristid aszlig eelco kampfschlaefer ];
   };
 
   machine =
diff --git a/nixos/tests/containers-custom-pkgs.nix b/nixos/tests/containers-custom-pkgs.nix
index 1412c32bfb5f1..c050e49bc29d2 100644
--- a/nixos/tests/containers-custom-pkgs.nix
+++ b/nixos/tests/containers-custom-pkgs.nix
@@ -1,4 +1,4 @@
-import ./make-test-python.nix ({ pkgs, lib, ...} : let
+import ./make-test-python.nix ({ pkgs, lib, ... }: let
 
   customPkgs = pkgs.appendOverlays [ (self: super: {
     hello = super.hello.overrideAttrs (old: {
@@ -8,8 +8,8 @@ import ./make-test-python.nix ({ pkgs, lib, ...} : let
 
 in {
   name = "containers-custom-pkgs";
-  meta = with lib.maintainers; {
-    maintainers = [ adisbladis earvstedt ];
+  meta = {
+    maintainers = with lib.maintainers; [ adisbladis earvstedt ];
   };
 
   machine = { config, ... }: {
diff --git a/nixos/tests/containers-ephemeral.nix b/nixos/tests/containers-ephemeral.nix
index 692554ac0ba25..fabf0593f23af 100644
--- a/nixos/tests/containers-ephemeral.nix
+++ b/nixos/tests/containers-ephemeral.nix
@@ -1,7 +1,8 @@
-# Test for NixOS' container support.
-
-import ./make-test-python.nix ({ pkgs, ...} : {
+import ./make-test-python.nix ({ pkgs, lib, ... }: {
   name = "containers-ephemeral";
+  meta = {
+    maintainers = with lib.maintainers; [ patryk27 ];
+  };
 
   machine = { pkgs, ... }: {
     virtualisation.memorySize = 768;
diff --git a/nixos/tests/containers-extra_veth.nix b/nixos/tests/containers-extra_veth.nix
index 212f3d0f46cbc..cbbb252583254 100644
--- a/nixos/tests/containers-extra_veth.nix
+++ b/nixos/tests/containers-extra_veth.nix
@@ -1,9 +1,7 @@
-# Test for NixOS' container support.
-
-import ./make-test-python.nix ({ pkgs, ...} : {
+import ./make-test-python.nix ({ pkgs, lib, ... }: {
   name = "containers-extra_veth";
-  meta = with pkgs.lib.maintainers; {
-    maintainers = [ kampfschlaefer ];
+  meta = {
+    maintainers = with lib.maintainers; [ kampfschlaefer ];
   };
 
   machine =
diff --git a/nixos/tests/containers-hosts.nix b/nixos/tests/containers-hosts.nix
index 65a983c42a789..1f24ed1f3c2c4 100644
--- a/nixos/tests/containers-hosts.nix
+++ b/nixos/tests/containers-hosts.nix
@@ -1,9 +1,7 @@
-# Test for NixOS' container support.
-
-import ./make-test-python.nix ({ pkgs, ...} : {
+import ./make-test-python.nix ({ pkgs, lib, ... }: {
   name = "containers-hosts";
-  meta = with pkgs.lib.maintainers; {
-    maintainers = [ montag451 ];
+  meta = {
+    maintainers = with lib.maintainers; [ montag451 ];
   };
 
   machine =
diff --git a/nixos/tests/containers-imperative.nix b/nixos/tests/containers-imperative.nix
index 393b4a5135dd5..0ff0d3f954528 100644
--- a/nixos/tests/containers-imperative.nix
+++ b/nixos/tests/containers-imperative.nix
@@ -1,9 +1,7 @@
-# Test for NixOS' container support.
-
-import ./make-test-python.nix ({ pkgs, ...} : {
+import ./make-test-python.nix ({ pkgs, lib, ... }: {
   name = "containers-imperative";
-  meta = with pkgs.lib.maintainers; {
-    maintainers = [ aristid aszlig eelco kampfschlaefer ];
+  meta = {
+    maintainers = with lib.maintainers; [ aristid aszlig eelco kampfschlaefer ];
   };
 
   machine =
diff --git a/nixos/tests/containers-ip.nix b/nixos/tests/containers-ip.nix
index 0265ed92d41cb..5abea2dbad9fa 100644
--- a/nixos/tests/containers-ip.nix
+++ b/nixos/tests/containers-ip.nix
@@ -1,5 +1,3 @@
-# Test for NixOS' container support.
-
 let
   webserverFor = hostAddress: localAddress: {
     inherit hostAddress localAddress;
@@ -13,10 +11,10 @@ let
     };
   };
 
-in import ./make-test-python.nix ({ pkgs, ...} : {
+in import ./make-test-python.nix ({ pkgs, lib, ... }: {
   name = "containers-ipv4-ipv6";
-  meta = with pkgs.lib.maintainers; {
-    maintainers = [ aristid aszlig eelco kampfschlaefer ];
+  meta = {
+    maintainers = with lib.maintainers; [ aristid aszlig eelco kampfschlaefer ];
   };
 
   machine =
diff --git a/nixos/tests/containers-macvlans.nix b/nixos/tests/containers-macvlans.nix
index 9425252cb8862..d0f41be8c1251 100644
--- a/nixos/tests/containers-macvlans.nix
+++ b/nixos/tests/containers-macvlans.nix
@@ -1,15 +1,13 @@
-# Test for NixOS' container support.
-
 let
   # containers IP on VLAN 1
   containerIp1 = "192.168.1.253";
   containerIp2 = "192.168.1.254";
 in
 
-import ./make-test-python.nix ({ pkgs, ...} : {
+import ./make-test-python.nix ({ pkgs, lib, ... }: {
   name = "containers-macvlans";
-  meta = with pkgs.lib.maintainers; {
-    maintainers = [ montag451 ];
+  meta = {
+    maintainers = with lib.maintainers; [ montag451 ];
   };
 
   nodes = {
diff --git a/nixos/tests/containers-names.nix b/nixos/tests/containers-names.nix
new file mode 100644
index 0000000000000..9ad2bfb748a8d
--- /dev/null
+++ b/nixos/tests/containers-names.nix
@@ -0,0 +1,37 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }: {
+  name = "containers-names";
+  meta = {
+    maintainers = with lib.maintainers; [ patryk27 ];
+  };
+
+  machine = { ... }: {
+    # We're using the newest kernel, so that we can test containers with long names.
+    # Please see https://github.com/NixOS/nixpkgs/issues/38509 for details.
+    boot.kernelPackages = pkgs.linuxPackages_latest;
+
+    containers = let
+      container = subnet: {
+        autoStart = true;
+        privateNetwork = true;
+        hostAddress = "192.168.${subnet}.1";
+        localAddress = "192.168.${subnet}.2";
+        config = { };
+      };
+
+     in {
+      first = container "1";
+      second = container "2";
+      really-long-name = container "3";
+      really-long-long-name-2 = container "4";
+    };
+  };
+
+  testScript = ''
+    machine.wait_for_unit("default.target")
+
+    machine.succeed("ip link show | grep ve-first")
+    machine.succeed("ip link show | grep ve-second")
+    machine.succeed("ip link show | grep ve-really-lFYWO")
+    machine.succeed("ip link show | grep ve-really-l3QgY")
+  '';
+})
diff --git a/nixos/tests/containers-physical_interfaces.nix b/nixos/tests/containers-physical_interfaces.nix
index 0b55c3418edfb..57bd0eedcc335 100644
--- a/nixos/tests/containers-physical_interfaces.nix
+++ b/nixos/tests/containers-physical_interfaces.nix
@@ -1,8 +1,7 @@
-
-import ./make-test-python.nix ({ pkgs, ...} : {
+import ./make-test-python.nix ({ pkgs, lib, ... }: {
   name = "containers-physical_interfaces";
-  meta = with pkgs.lib.maintainers; {
-    maintainers = [ kampfschlaefer ];
+  meta = {
+    maintainers = with lib.maintainers; [ kampfschlaefer ];
   };
 
   nodes = {
diff --git a/nixos/tests/containers-portforward.nix b/nixos/tests/containers-portforward.nix
index d0be3c7d43ec6..221a6f50efd1d 100644
--- a/nixos/tests/containers-portforward.nix
+++ b/nixos/tests/containers-portforward.nix
@@ -1,5 +1,3 @@
-# Test for NixOS' container support.
-
 let
   hostIp = "192.168.0.1";
   hostPort = 10080;
@@ -7,10 +5,10 @@ let
   containerPort = 80;
 in
 
-import ./make-test-python.nix ({ pkgs, ...} : {
+import ./make-test-python.nix ({ pkgs, lib, ... }: {
   name = "containers-portforward";
-  meta = with pkgs.lib.maintainers; {
-    maintainers = [ aristid aszlig eelco kampfschlaefer ianwookim ];
+  meta = {
+    maintainers = with lib.maintainers; [ aristid aszlig eelco kampfschlaefer ianwookim ];
   };
 
   machine =
diff --git a/nixos/tests/containers-reloadable.nix b/nixos/tests/containers-reloadable.nix
index 8772469176727..876e62c1da9ea 100644
--- a/nixos/tests/containers-reloadable.nix
+++ b/nixos/tests/containers-reloadable.nix
@@ -1,7 +1,6 @@
-import ./make-test-python.nix ({ pkgs, lib, ...} :
+import ./make-test-python.nix ({ pkgs, lib, ... }:
 let
   client_base = {
-
     containers.test1 = {
       autoStart = true;
       config = {
@@ -16,8 +15,8 @@ let
   };
 in {
   name = "containers-reloadable";
-  meta = with pkgs.lib.maintainers; {
-    maintainers = [ danbst ];
+  meta = {
+    maintainers = with lib.maintainers; [ danbst ];
   };
 
   nodes = {
diff --git a/nixos/tests/containers-restart_networking.nix b/nixos/tests/containers-restart_networking.nix
index b35552b5b1919..e1ad8157b2883 100644
--- a/nixos/tests/containers-restart_networking.nix
+++ b/nixos/tests/containers-restart_networking.nix
@@ -1,5 +1,3 @@
-# Test for NixOS' container support.
-
 let
   client_base = {
     networking.firewall.enable = false;
@@ -16,11 +14,11 @@ let
       };
     };
   };
-in import ./make-test-python.nix ({ pkgs, ...} :
+in import ./make-test-python.nix ({ pkgs, lib, ... }:
 {
   name = "containers-restart_networking";
-  meta = with pkgs.lib.maintainers; {
-    maintainers = [ kampfschlaefer ];
+  meta = {
+    maintainers = with lib.maintainers; [ kampfschlaefer ];
   };
 
   nodes = {
diff --git a/nixos/tests/containers-tmpfs.nix b/nixos/tests/containers-tmpfs.nix
index 7ebf0d02a2406..fd9f9a252ca8c 100644
--- a/nixos/tests/containers-tmpfs.nix
+++ b/nixos/tests/containers-tmpfs.nix
@@ -1,9 +1,7 @@
-# Test for NixOS' container support.
-
-import ./make-test-python.nix ({ pkgs, ...} : {
+import ./make-test-python.nix ({ pkgs, lib, ... }: {
   name = "containers-tmpfs";
-  meta = with pkgs.lib.maintainers; {
-    maintainers = [ ];
+  meta = {
+    maintainers = with lib.maintainers; [ patryk27 ];
   };
 
   machine =
diff --git a/nixos/tests/custom-ca.nix b/nixos/tests/custom-ca.nix
new file mode 100644
index 0000000000000..67f7b3ff1f161
--- /dev/null
+++ b/nixos/tests/custom-ca.nix
@@ -0,0 +1,161 @@
+# Checks that `security.pki` options are working in curl and the main browser
+# engines: Gecko (via Firefox), Chromium, QtWebEngine (Falkon) and WebKitGTK
+# (via Midori). The test checks that certificates issued by a custom trusted
+# CA are accepted but those from an unknown CA are rejected.
+
+import ./make-test-python.nix ({ pkgs, lib, ... }:
+
+let
+  makeCert = { caName, domain }: pkgs.runCommand "example-cert"
+  { buildInputs = [ pkgs.gnutls ]; }
+  ''
+    mkdir $out
+
+    # CA cert template
+    cat >ca.template <<EOF
+    organization = "${caName}"
+    cn = "${caName}"
+    expiration_days = 365
+    ca
+    cert_signing_key
+    crl_signing_key
+    EOF
+
+    # server cert template
+    cat >server.template <<EOF
+    organization = "An example company"
+    cn = "${domain}"
+    expiration_days = 30
+    dns_name = "${domain}"
+    encryption_key
+    signing_key
+    EOF
+
+    # generate CA keypair
+    certtool                \
+      --generate-privkey    \
+      --key-type rsa        \
+      --sec-param High      \
+      --outfile $out/ca.key
+    certtool                     \
+      --generate-self-signed     \
+      --load-privkey $out/ca.key \
+      --template ca.template     \
+      --outfile $out/ca.crt
+
+    # generate server keypair
+    certtool                    \
+      --generate-privkey        \
+      --key-type rsa            \
+      --sec-param High          \
+      --outfile $out/server.key
+    certtool                            \
+      --generate-certificate            \
+      --load-privkey $out/server.key    \
+      --load-ca-privkey $out/ca.key     \
+      --load-ca-certificate $out/ca.crt \
+      --template server.template        \
+      --outfile $out/server.crt
+  '';
+
+  example-good-cert = makeCert
+    { caName = "Example good CA";
+      domain = "good.example.com";
+    };
+
+  example-bad-cert = makeCert
+    { caName = "Unknown CA";
+      domain = "bad.example.com";
+    };
+
+in
+
+{
+  name = "custom-ca";
+  meta.maintainers = with lib.maintainers; [ rnhmjoj ];
+
+  enableOCR = true;
+
+  machine = { pkgs, ... }:
+    { imports = [ ./common/user-account.nix ./common/x11.nix ];
+
+      # chromium-based browsers refuse to run as root
+      test-support.displayManager.auto.user = "alice";
+      # browsers may hang with the default memory
+      virtualisation.memorySize = "500";
+
+      networking.hosts."127.0.0.1" = [ "good.example.com" "bad.example.com" ];
+      security.pki.certificateFiles = [ "${example-good-cert}/ca.crt" ];
+
+      services.nginx.enable = true;
+      services.nginx.virtualHosts."good.example.com" =
+        { onlySSL = true;
+          sslCertificate = "${example-good-cert}/server.crt";
+          sslCertificateKey = "${example-good-cert}/server.key";
+          locations."/".extraConfig = "return 200 'It works!';";
+        };
+      services.nginx.virtualHosts."bad.example.com" =
+        { onlySSL = true;
+          sslCertificate = "${example-bad-cert}/server.crt";
+          sslCertificateKey = "${example-bad-cert}/server.key";
+          locations."/".extraConfig = "return 200 'It does not work!';";
+        };
+
+      environment.systemPackages = with pkgs;
+        [ xdotool firefox chromium falkon midori ];
+    };
+
+  testScript = ''
+    def execute_as(user: str, cmd: str) -> Tuple[int, str]:
+        """
+        Run a shell command as a specific user.
+        """
+        return machine.execute(f"sudo -u {user} {cmd}")
+
+
+    def wait_for_window_as(user: str, cls: str) -> None:
+        """
+        Wait until a X11 window of a given user appears.
+        """
+
+        def window_is_visible(last_try: bool) -> bool:
+            ret, stdout = execute_as(user, f"xdotool search --onlyvisible --class {cls}")
+            if last_try:
+                machine.log(f"Last chance to match {cls} on the window list")
+            return ret == 0
+
+        with machine.nested("Waiting for a window to appear"):
+            retry(window_is_visible)
+
+
+    machine.start()
+
+    with subtest("Good certificate is trusted in curl"):
+        machine.wait_for_unit("nginx")
+        machine.wait_for_open_port(443)
+        machine.succeed("curl -fv https://good.example.com")
+
+    with subtest("Unknown CA is untrusted in curl"):
+        machine.fail("curl -fv https://bad.example.com")
+
+    browsers = ["firefox", "chromium", "falkon", "midori"]
+    errors = ["Security Risk", "not private", "Certificate Error", "Security"]
+
+    machine.wait_for_x()
+    for browser, error in zip(browsers, errors):
+        with subtest("Good certificate is trusted in " + browser):
+            execute_as(
+                "alice", f"env P11_KIT_DEBUG=trust {browser} https://good.example.com & >&2"
+            )
+            wait_for_window_as("alice", browser)
+            machine.wait_for_text("It works!")
+            machine.screenshot("good" + browser)
+            execute_as("alice", "xdotool key ctrl+w")  # close tab
+
+        with subtest("Unknown CA is untrusted in " + browser):
+            execute_as("alice", f"{browser} https://bad.example.com & >&2")
+            machine.wait_for_text(error)
+            machine.screenshot("bad" + browser)
+            machine.succeed("pkill " + browser)
+  '';
+})
diff --git a/nixos/tests/fsck.nix b/nixos/tests/fsck.nix
index e522419fde2b9..5453f3bc48b59 100644
--- a/nixos/tests/fsck.nix
+++ b/nixos/tests/fsck.nix
@@ -4,7 +4,7 @@ import ./make-test-python.nix {
   machine = { lib, ... }: {
     virtualisation.emptyDiskImages = [ 1 ];
 
-    fileSystems = lib.mkVMOverride {
+    virtualisation.fileSystems = {
       "/mnt" = {
         device = "/dev/vdb";
         fsType = "ext4";
diff --git a/nixos/tests/geth.nix b/nixos/tests/geth.nix
new file mode 100644
index 0000000000000..10cbd6d9038ca
--- /dev/null
+++ b/nixos/tests/geth.nix
@@ -0,0 +1,41 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "geth";
+  meta = with pkgs.lib; {
+    maintainers = with maintainers; [bachp ];
+  };
+
+  machine = { ... }: {
+    services.geth."mainnet" = {
+      enable = true;
+      http = {
+        enable = true;
+      };
+    };
+    services.geth."testnet" = {
+      enable = true;
+      port = 30304;
+      network = "goerli";
+      http = {
+        enable = true;
+        port = 18545;
+      };
+    };
+  };
+
+  testScript = ''
+    start_all()
+
+    machine.wait_for_unit("geth-mainnet.service")
+    machine.wait_for_unit("geth-testnet.service")
+    machine.wait_for_open_port(8545)
+    machine.wait_for_open_port(18545)
+
+    machine.succeed(
+        'geth attach --exec "eth.chainId()" http://localhost:8545 | grep \'"0x0"\' '
+    )
+
+    machine.succeed(
+        'geth attach --exec "eth.chainId()" http://localhost:18545 | grep \'"0x5"\' '
+    )
+  '';
+})
diff --git a/nixos/tests/gitlab.nix b/nixos/tests/gitlab.nix
index ba085338944a0..baad675229cce 100644
--- a/nixos/tests/gitlab.nix
+++ b/nixos/tests/gitlab.nix
@@ -11,6 +11,8 @@ import ./make-test-python.nix ({ pkgs, lib, ...} : with lib; {
 
   nodes = {
     gitlab = { ... }: {
+      imports = [ common/user-account.nix ];
+
       virtualisation.memorySize = if pkgs.stdenv.is64bit then 4096 else 2047;
       systemd.services.gitlab.serviceConfig.Restart = mkForce "no";
       systemd.services.gitlab-workhorse.serviceConfig.Restart = mkForce "no";
@@ -27,11 +29,31 @@ import ./make-test-python.nix ({ pkgs, lib, ...} : with lib; {
         };
       };
 
+      services.dovecot2 = {
+        enable = true;
+        enableImap = true;
+      };
+
       services.gitlab = {
         enable = true;
         databasePasswordFile = pkgs.writeText "dbPassword" "xo0daiF4";
         initialRootPasswordFile = pkgs.writeText "rootPassword" initialRootPassword;
         smtp.enable = true;
+        extraConfig = {
+          incoming_email = {
+            enabled = true;
+            mailbox = "inbox";
+            address = "alice@localhost";
+            user = "alice";
+            password = "foobar";
+            host = "localhost";
+            port = 143;
+          };
+          pages = {
+            enabled = true;
+            host = "localhost";
+          };
+        };
         secrets = {
           secretFile = pkgs.writeText "secret" "r8X9keSKynU7p4aKlh4GO1Bo77g5a7vj";
           otpFile = pkgs.writeText "otpsecret" "Zu5hGx3YvQx40DvI8WoZJQpX2paSDOlG";
@@ -64,12 +86,16 @@ import ./make-test-python.nix ({ pkgs, lib, ...} : with lib; {
   in
   ''
     gitlab.start()
+
     gitlab.wait_for_unit("gitaly.service")
     gitlab.wait_for_unit("gitlab-workhorse.service")
+    gitlab.wait_for_unit("gitlab-pages.service")
+    gitlab.wait_for_unit("gitlab-mailroom.service")
     gitlab.wait_for_unit("gitlab.service")
     gitlab.wait_for_unit("gitlab-sidekiq.service")
     gitlab.wait_for_file("/var/gitlab/state/tmp/sockets/gitlab.socket")
     gitlab.wait_until_succeeds("curl -sSf http://gitlab/users/sign_in")
+
     gitlab.succeed(
         "curl -isSf http://gitlab | grep -i location | grep -q http://gitlab/users/sign_in"
     )
diff --git a/nixos/tests/glusterfs.nix b/nixos/tests/glusterfs.nix
index cb07bc09511dd..ef09264a02167 100644
--- a/nixos/tests/glusterfs.nix
+++ b/nixos/tests/glusterfs.nix
@@ -3,7 +3,7 @@ import ./make-test-python.nix ({pkgs, lib, ...}:
 let
   client = { pkgs, ... } : {
     environment.systemPackages = [ pkgs.glusterfs ];
-    fileSystems = pkgs.lib.mkVMOverride
+    virtualisation.fileSystems =
       { "/gluster" =
           { device = "server1:/gv0";
             fsType = "glusterfs";
@@ -22,7 +22,7 @@ let
 
     virtualisation.emptyDiskImages = [ 1024 ];
 
-    fileSystems = pkgs.lib.mkVMOverride
+    virtualisation.fileSystems =
       { "/data" =
           { device = "/dev/disk/by-label/data";
             fsType = "ext4";
diff --git a/nixos/tests/hardened.nix b/nixos/tests/hardened.nix
index d3f1f3172965a..485efc0fb78c4 100644
--- a/nixos/tests/hardened.nix
+++ b/nixos/tests/hardened.nix
@@ -18,7 +18,7 @@ import ./make-test-python.nix ({ pkgs, latestKernel ? false, ... } : {
       boot.initrd.postDeviceCommands = ''
         ${pkgs.dosfstools}/bin/mkfs.vfat -n EFISYS /dev/vdb
       '';
-      fileSystems = lib.mkVMOverride {
+      virtualisation.fileSystems = {
         "/efi" = {
           device = "/dev/disk/by-label/EFISYS";
           fsType = "vfat";
@@ -65,17 +65,6 @@ import ./make-test-python.nix ({ pkgs, latestKernel ? false, ... } : {
           machine.succeed("grep -Fq wireguard /proc/modules")
 
 
-      # Test hidepid
-      with subtest("hidepid=2 option is applied and works"):
-          # Linux >= 5.8 shows "invisible"
-          machine.succeed(
-              "grep -Fq hidepid=2 /proc/mounts || grep -Fq hidepid=invisible /proc/mounts"
-          )
-          # cannot use pgrep -u here, it segfaults when access to process info is denied
-          machine.succeed("[ `su - sybil -c 'ps --no-headers --user root | wc -l'` = 0 ]")
-          machine.succeed("[ `su - alice -c 'ps --no-headers --user root | wc -l'` != 0 ]")
-
-
       # Test kernel module hardening
       with subtest("No more kernel modules can be loaded"):
           # note: this better a be module we normally wouldn't load ...
diff --git a/nixos/tests/home-assistant.nix b/nixos/tests/home-assistant.nix
index 131f50747fef0..726c7eb6acb68 100644
--- a/nixos/tests/home-assistant.nix
+++ b/nixos/tests/home-assistant.nix
@@ -24,6 +24,8 @@ in {
     services.home-assistant = {
       inherit configDir;
       enable = true;
+      # includes the package with all tests enabled
+      package = pkgs.home-assistant;
       config = {
         homeassistant = {
           name = "Home";
diff --git a/nixos/tests/installer.nix b/nixos/tests/installer.nix
index 789add331b793..904ec17229e1c 100644
--- a/nixos/tests/installer.nix
+++ b/nixos/tests/installer.nix
@@ -325,10 +325,13 @@ let
             curl
           ]
           ++ optional (bootLoader == "grub" && grubVersion == 1) pkgs.grub
-          ++ optionals (bootLoader == "grub" && grubVersion == 2) [
-            (pkgs.grub2.override { zfsSupport = true; })
-            (pkgs.grub2_efi.override { zfsSupport = true; })
-          ];
+          ++ optionals (bootLoader == "grub" && grubVersion == 2) (let
+            zfsSupport = lib.any (x: x == "zfs")
+              (extraInstallerConfig.boot.supportedFilesystems or []);
+          in [
+            (pkgs.grub2.override { inherit zfsSupport; })
+            (pkgs.grub2_efi.override { inherit zfsSupport; })
+          ]);
 
           nix.binaryCaches = mkForce [ ];
           nix.extraOptions = ''
@@ -398,9 +401,9 @@ let
     createPartitions = ''
       machine.succeed(
           "flock /dev/vda parted --script /dev/vda -- mklabel gpt"
-          + " mkpart ESP fat32 1M 50MiB"  # /boot
+          + " mkpart ESP fat32 1M 100MiB"  # /boot
           + " set 1 boot on"
-          + " mkpart primary linux-swap 50MiB 1024MiB"
+          + " mkpart primary linux-swap 100MiB 1024MiB"
           + " mkpart primary ext2 1024MiB -1MiB",  # /
           "udevadm settle",
           "mkswap /dev/vda2 -L swap",
diff --git a/nixos/tests/jq.nix b/nixos/tests/jq.nix
deleted file mode 100644
index 075e6c43c09d4..0000000000000
--- a/nixos/tests/jq.nix
+++ /dev/null
@@ -1,10 +0,0 @@
-import ./make-test-python.nix ({ pkgs, ... }: {
-  name = "jq";
-  meta = with pkgs.lib.maintainers; { maintainers = [ nequissimus ]; };
-
-  nodes.jq = { pkgs, ... }: { environment.systemPackages = [ pkgs.jq ]; };
-
-  testScript = ''
-    assert "world" in jq.succeed('echo \'{"values":["hello","world"]}\'| jq \'.values[1]\''')
-  '';
-})
diff --git a/nixos/tests/kafka.nix b/nixos/tests/kafka.nix
index d5c54f7d99107..034601c815b92 100644
--- a/nixos/tests/kafka.nix
+++ b/nixos/tests/kafka.nix
@@ -30,11 +30,6 @@ let
           '';
           package = kafkaPackage;
           zookeeper = "zookeeper1:2181";
-          # These are the default options, but UseCompressedOops doesn't work with 32bit JVM
-          jvmOptions = [
-            "-server" "-Xmx1G" "-Xms1G" "-XX:+UseParNewGC" "-XX:+UseConcMarkSweepGC" "-XX:+CMSClassUnloadingEnabled"
-            "-XX:+CMSScavengeBeforeRemark" "-XX:+DisableExplicitGC" "-Djava.awt.headless=true" "-Djava.net.preferIPv4Stack=true"
-          ] ++ optionals (! pkgs.stdenv.isi686 ) [ "-XX:+UseCompressedOops" ];
         };
 
         networking.firewall.allowedTCPPorts = [ 9092 ];
@@ -82,4 +77,5 @@ let
 in with pkgs; {
   kafka_2_4  = makeKafkaTest "kafka_2_4"  apacheKafka_2_4;
   kafka_2_5  = makeKafkaTest "kafka_2_5"  apacheKafka_2_5;
+  kafka_2_6  = makeKafkaTest "kafka_2_6"  apacheKafka_2_6;
 }
diff --git a/nixos/tests/keepassxc.nix b/nixos/tests/keepassxc.nix
new file mode 100644
index 0000000000000..98902187f6ac3
--- /dev/null
+++ b/nixos/tests/keepassxc.nix
@@ -0,0 +1,34 @@
+import ./make-test-python.nix ({ pkgs, ...} :
+
+{
+  name = "keepassxc";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ turion ];
+  };
+
+  machine = { ... }:
+
+  {
+    imports = [
+      ./common/user-account.nix
+      ./common/x11.nix
+    ];
+
+    services.xserver.enable = true;
+    test-support.displayManager.auto.user = "alice";
+    environment.systemPackages = [ pkgs.keepassxc ];
+  };
+
+  enableOCR = true;
+
+  testScript = { nodes, ... }: ''
+    start_all()
+    machine.wait_for_x()
+
+    # start KeePassXC window
+    machine.execute("su - alice -c keepassxc &")
+
+    machine.wait_for_text("KeePassXC ${pkgs.keepassxc.version}")
+    machine.screenshot("KeePassXC")
+  '';
+})
diff --git a/nixos/tests/ksm.nix b/nixos/tests/ksm.nix
new file mode 100644
index 0000000000000..8f84b32020ab9
--- /dev/null
+++ b/nixos/tests/ksm.nix
@@ -0,0 +1,22 @@
+import ./make-test-python.nix ({ lib, ...} :
+
+{
+  name = "ksm";
+  meta = with lib.maintainers; {
+    maintainers = [ rnhmjoj ];
+  };
+
+  machine = { ... }: {
+    imports = [ ../modules/profiles/minimal.nix ];
+
+    hardware.ksm.enable = true;
+    hardware.ksm.sleep = 300;
+  };
+
+  testScript =
+    ''
+      machine.start()
+      machine.wait_until_succeeds("test $(</sys/kernel/mm/ksm/run) -eq 1")
+      machine.wait_until_succeeds("test $(</sys/kernel/mm/ksm/sleep_millisecs) -eq 300")
+    '';
+})
diff --git a/nixos/tests/kubernetes/dns.nix b/nixos/tests/kubernetes/dns.nix
index 890499a0fb8a9..b6cd811c5aefd 100644
--- a/nixos/tests/kubernetes/dns.nix
+++ b/nixos/tests/kubernetes/dns.nix
@@ -34,7 +34,7 @@ let
     name = "redis";
     tag = "latest";
     contents = [ pkgs.redis pkgs.bind.host ];
-    config.Entrypoint = "/bin/redis-server";
+    config.Entrypoint = ["/bin/redis-server"];
   };
 
   probePod = pkgs.writeText "probe-pod.json" (builtins.toJSON {
@@ -55,12 +55,11 @@ let
     name = "probe";
     tag = "latest";
     contents = [ pkgs.bind.host pkgs.busybox ];
-    config.Entrypoint = "/bin/tail";
+    config.Entrypoint = ["/bin/tail"];
   };
 
-  extraConfiguration = { config, pkgs, ... }: {
+  extraConfiguration = { config, pkgs, lib, ... }: {
     environment.systemPackages = [ pkgs.bind.host ];
-    # virtualisation.docker.extraOptions = "--dns=${config.services.kubernetes.addons.dns.clusterIp}";
     services.dnsmasq.enable = true;
     services.dnsmasq.servers = [
       "/cluster.local/${config.services.kubernetes.addons.dns.clusterIp}#53"
@@ -77,7 +76,7 @@ let
       # prepare machine1 for test
       machine1.wait_until_succeeds("kubectl get node machine1.${domain} | grep -w Ready")
       machine1.wait_until_succeeds(
-          "docker load < ${redisImage}"
+          "${pkgs.gzip}/bin/zcat ${redisImage} | ${pkgs.containerd}/bin/ctr -n k8s.io image import -"
       )
       machine1.wait_until_succeeds(
           "kubectl create -f ${redisPod}"
@@ -86,7 +85,7 @@ let
           "kubectl create -f ${redisService}"
       )
       machine1.wait_until_succeeds(
-          "docker load < ${probeImage}"
+          "${pkgs.gzip}/bin/zcat ${probeImage} | ${pkgs.containerd}/bin/ctr -n k8s.io image import -"
       )
       machine1.wait_until_succeeds(
           "kubectl create -f ${probePod}"
@@ -118,7 +117,7 @@ let
       # prepare machines for test
       machine1.wait_until_succeeds("kubectl get node machine2.${domain} | grep -w Ready")
       machine2.wait_until_succeeds(
-          "docker load < ${redisImage}"
+          "${pkgs.gzip}/bin/zcat ${redisImage} | ${pkgs.containerd}/bin/ctr -n k8s.io image import -"
       )
       machine1.wait_until_succeeds(
           "kubectl create -f ${redisPod}"
@@ -127,7 +126,7 @@ let
           "kubectl create -f ${redisService}"
       )
       machine2.wait_until_succeeds(
-          "docker load < ${probeImage}"
+          "${pkgs.gzip}/bin/zcat ${probeImage} | ${pkgs.containerd}/bin/ctr -n k8s.io image import -"
       )
       machine1.wait_until_succeeds(
           "kubectl create -f ${probePod}"
diff --git a/nixos/tests/kubernetes/rbac.nix b/nixos/tests/kubernetes/rbac.nix
index c922da515d915..3fc8ed0fbe389 100644
--- a/nixos/tests/kubernetes/rbac.nix
+++ b/nixos/tests/kubernetes/rbac.nix
@@ -85,7 +85,7 @@ let
     name = "kubectl";
     tag = "latest";
     contents = [ kubectl pkgs.busybox kubectlPod2 ];
-    config.Entrypoint = "/bin/sh";
+    config.Entrypoint = ["/bin/sh"];
   };
 
   base = {
@@ -97,7 +97,7 @@ let
       machine1.wait_until_succeeds("kubectl get node machine1.my.zyx | grep -w Ready")
 
       machine1.wait_until_succeeds(
-          "docker load < ${kubectlImage}"
+          "${pkgs.gzip}/bin/zcat ${kubectlImage} | ${pkgs.containerd}/bin/ctr -n k8s.io image import -"
       )
 
       machine1.wait_until_succeeds(
@@ -134,7 +134,7 @@ let
       machine1.wait_until_succeeds("kubectl get node machine2.my.zyx | grep -w Ready")
 
       machine2.wait_until_succeeds(
-          "docker load < ${kubectlImage}"
+          "${pkgs.gzip}/bin/zcat ${kubectlImage} | ${pkgs.containerd}/bin/ctr -n k8s.io image import -"
       )
 
       machine1.wait_until_succeeds(
diff --git a/nixos/tests/locate.nix b/nixos/tests/locate.nix
index 67ae610fe0127..e8ba41812a8f5 100644
--- a/nixos/tests/locate.nix
+++ b/nixos/tests/locate.nix
@@ -7,7 +7,7 @@ import ./make-test-python.nix ({ lib, pkgs, ... }:
     nodes = rec {
       a = {
         environment.systemPackages = with pkgs; [ sshfs ];
-        fileSystems = lib.mkVMOverride {
+        virtualisation.fileSystems = {
           "/ssh" = {
             device = "alice@b:/";
             fsType = "fuse.sshfs";
diff --git a/nixos/tests/misc.nix b/nixos/tests/misc.nix
index fda2e60a41b6f..fb19b7060562f 100644
--- a/nixos/tests/misc.nix
+++ b/nixos/tests/misc.nix
@@ -16,7 +16,7 @@ import ./make-test-python.nix ({ pkgs, ...} : rec {
       environment.variables.EDITOR = mkOverride 0 "emacs";
       documentation.nixos.enable = mkOverride 0 true;
       systemd.tmpfiles.rules = [ "d /tmp 1777 root root 10d" ];
-      fileSystems = mkVMOverride { "/tmp2" =
+      virtualisation.fileSystems = { "/tmp2" =
         { fsType = "tmpfs";
           options = [ "mode=1777" "noauto" ];
         };
diff --git a/nixos/tests/musescore.nix b/nixos/tests/musescore.nix
new file mode 100644
index 0000000000000..96481a9a8bf48
--- /dev/null
+++ b/nixos/tests/musescore.nix
@@ -0,0 +1,86 @@
+import ./make-test-python.nix ({ pkgs, ...} :
+
+let
+  # Make sure we don't have to go through the startup tutorial
+  customMuseScoreConfig = pkgs.writeText "MuseScore3.ini" ''
+    [application]
+    startup\firstStart=false
+
+    [ui]
+    application\startup\showTours=false
+    application\startup\showStartCenter=false
+    '';
+in
+{
+  name = "musescore";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ turion ];
+  };
+
+  machine = { ... }:
+
+  {
+    imports = [
+      ./common/x11.nix
+    ];
+
+    services.xserver.enable = true;
+    environment.systemPackages = with pkgs; [
+      musescore
+      pdfgrep
+    ];
+  };
+
+  enableOCR = true;
+
+  testScript = { ... }: ''
+    start_all()
+    machine.wait_for_x()
+
+    # Inject custom settings
+    machine.succeed("mkdir -p /root/.config/MuseScore/")
+    machine.succeed(
+        "cp ${customMuseScoreConfig} /root/.config/MuseScore/MuseScore3.ini"
+    )
+
+    # Start MuseScore window
+    machine.execute("DISPLAY=:0.0 mscore &")
+
+    # Wait until MuseScore has launched
+    machine.wait_for_window("MuseScore")
+
+    # Wait until the window has completely initialised
+    machine.wait_for_text("MuseScore")
+
+    # Start entering notes
+    machine.send_key("n")
+    # Type the beginning of https://de.wikipedia.org/wiki/Alle_meine_Entchen
+    machine.send_chars("cdef6gg5aaaa7g")
+    # Make sure the VM catches up with all the keys
+    machine.sleep(1)
+
+    machine.screenshot("MuseScore0")
+
+    # Go to the export dialogue and create a PDF
+    machine.send_key("alt-f")
+    machine.sleep(1)
+    machine.send_key("e")
+
+    # Wait until the export dialogue appears.
+    machine.wait_for_window("Export")
+    machine.screenshot("MuseScore1")
+    machine.send_key("ret")
+    machine.sleep(1)
+    machine.send_key("ret")
+
+    machine.screenshot("MuseScore2")
+
+    # Wait until PDF is exported
+    machine.wait_for_file("/root/Documents/MuseScore3/Scores/Untitled.pdf")
+
+    # Check that it contains the title of the score
+    machine.succeed("pdfgrep Title /root/Documents/MuseScore3/Scores/Untitled.pdf")
+
+    machine.screenshot("MuseScore3")
+  '';
+})
diff --git a/nixos/tests/networking.nix b/nixos/tests/networking.nix
index 4fc5d48e0e173..1ea61f99a9513 100644
--- a/nixos/tests/networking.nix
+++ b/nixos/tests/networking.nix
@@ -35,7 +35,7 @@ let
         extraConfig = flip concatMapStrings vlanIfs (n: ''
           subnet 192.168.${toString n}.0 netmask 255.255.255.0 {
             option routers 192.168.${toString n}.1;
-            range 192.168.${toString n}.2 192.168.${toString n}.254;
+            range 192.168.${toString n}.3 192.168.${toString n}.254;
           }
         '')
         ;
@@ -672,6 +672,30 @@ let
             ), "The IPv6 routing table has not been properly cleaned:\n{}".format(ipv6Residue)
       '';
     };
+    rename = {
+      name = "RenameInterface";
+      machine = { pkgs, ... }: {
+        virtualisation.vlans = [ 1 ];
+        networking = {
+          useNetworkd = networkd;
+          useDHCP = false;
+        };
+      } //
+      (if networkd
+       then { systemd.network.links."10-custom_name" = {
+                matchConfig.MACAddress = "52:54:00:12:01:01";
+                linkConfig.Name = "custom_name";
+              };
+            }
+       else { services.udev.initrdRules = ''
+               SUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*", ATTR{address}=="52:54:00:12:01:01", KERNEL=="eth*", NAME="custom_name"
+              '';
+            });
+      testScript = ''
+        machine.succeed("udevadm settle")
+        print(machine.succeed("ip link show dev custom_name"))
+      '';
+    };
     # even with disabled networkd, systemd.network.links should work
     # (as it's handled by udev, not networkd)
     link = {
diff --git a/nixos/tests/nextcloud/basic.nix b/nixos/tests/nextcloud/basic.nix
index 9005044704279..5074b6cdafefe 100644
--- a/nixos/tests/nextcloud/basic.nix
+++ b/nixos/tests/nextcloud/basic.nix
@@ -7,7 +7,7 @@ in {
     maintainers = [ globin eqyiel ];
   };
 
-  nodes = {
+  nodes = rec {
     # The only thing the client needs to do is download a file.
     client = { ... }: {
       services.davfs2.enable = true;
@@ -15,7 +15,7 @@ in {
         echo "http://nextcloud/remote.php/webdav/ ${adminuser} ${adminpass}" > /tmp/davfs2-secrets
         chmod 600 /tmp/davfs2-secrets
       '';
-      fileSystems = pkgs.lib.mkVMOverride {
+      virtualisation.fileSystems = {
         "/mnt/dav" = {
           device = "http://nextcloud/remote.php/webdav/";
           fsType = "davfs";
@@ -47,9 +47,14 @@ in {
 
       environment.systemPackages = [ cfg.services.nextcloud.occ ];
     };
+
+    nextcloudWithoutMagick = args@{ config, pkgs, lib, ... }:
+      lib.mkMerge
+      [ (nextcloud args)
+        { services.nextcloud.disableImagemagick = true; } ];
   };
 
-  testScript = let
+  testScript = { nodes, ... }: let
     withRcloneEnv = pkgs.writeScript "with-rclone-env" ''
       #!${pkgs.runtimeShell}
       export RCLONE_CONFIG_NEXTCLOUD_TYPE=webdav
@@ -68,8 +73,19 @@ in {
       #!${pkgs.runtimeShell}
       diff <(echo 'hi') <(${pkgs.rclone}/bin/rclone cat nextcloud:test-shared-file)
     '';
+
+    findInClosure = what: drv: pkgs.runCommand "find-in-closure" { exportReferencesGraph = [ "graph" drv ]; inherit what; } ''
+      test -e graph
+      grep "$what" graph >$out || true
+    '';
+    nextcloudUsesImagick = findInClosure "imagick" nodes.nextcloud.config.system.build.vm;
+    nextcloudWithoutDoesntUseIt = findInClosure "imagick" nodes.nextcloudWithoutMagick.config.system.build.vm;
   in ''
-    start_all()
+    assert open("${nextcloudUsesImagick}").read() != ""
+    assert open("${nextcloudWithoutDoesntUseIt}").read() == ""
+
+    nextcloud.start()
+    client.start()
     nextcloud.wait_for_unit("multi-user.target")
     # This is just to ensure the nextcloud-occ program is working
     nextcloud.succeed("nextcloud-occ status")
diff --git a/nixos/tests/nfs/kerberos.nix b/nixos/tests/nfs/kerberos.nix
index 078f0b7814cea..75d1210496b00 100644
--- a/nixos/tests/nfs/kerberos.nix
+++ b/nixos/tests/nfs/kerberos.nix
@@ -40,7 +40,7 @@ in
         networking.domain = "nfs.test";
         networking.hostName = "client";
 
-        fileSystems = lib.mkVMOverride
+        virtualisation.fileSystems =
           { "/data" = {
               device  = "server.nfs.test:/";
               fsType  = "nfs";
diff --git a/nixos/tests/nfs/simple.nix b/nixos/tests/nfs/simple.nix
index 630c68a5b05d0..6a01089c0828c 100644
--- a/nixos/tests/nfs/simple.nix
+++ b/nixos/tests/nfs/simple.nix
@@ -4,7 +4,7 @@ let
 
   client =
     { pkgs, ... }:
-    { fileSystems = pkgs.lib.mkVMOverride
+    { virtualisation.fileSystems =
         { "/data" =
            { # nfs4 exports the export with fsid=0 as a virtual root directory
              device = if (version == 4) then "server:/" else "server:/data";
diff --git a/nixos/tests/nixos-generate-config.nix b/nixos/tests/nixos-generate-config.nix
index 5daa55a8abbea..7bf8d4da7b6d4 100644
--- a/nixos/tests/nixos-generate-config.nix
+++ b/nixos/tests/nixos-generate-config.nix
@@ -11,12 +11,11 @@ import ./make-test-python.nix ({ lib, ... } : {
       }
     '';
 
-    system.nixos-generate-config.desktopConfiguration = ''
+    system.nixos-generate-config.desktopConfiguration = [''
       # DESKTOP
-      # services.xserver.enable = true;
-      # services.xserver.displayManager.gdm.enable = true;
-      # services.xserver.desktopManager.gnome3.enable = true;
-    '';
+      services.xserver.displayManager.gdm.enable = true;
+      services.xserver.desktopManager.gnome3.enable = true;
+    ''];
   };
   testScript = ''
     start_all()
diff --git a/nixos/tests/openldap.nix b/nixos/tests/openldap.nix
index 392fae243467b..f1a39ad7dde2f 100644
--- a/nixos/tests/openldap.nix
+++ b/nixos/tests/openldap.nix
@@ -1,4 +1,9 @@
-{ pkgs, system ? builtins.currentSystem, ... }: let
+{ pkgs ? (import ../.. { inherit system; config = { }; })
+, system ? builtins.currentSystem
+, ...
+}:
+
+let
   dbContents = ''
     dn: dc=example
     objectClass: domain
@@ -16,7 +21,7 @@
   '';
 in {
   # New-style configuration
-  current = import ./make-test-python.nix {
+  current = import ./make-test-python.nix ({ pkgs, ... }: {
     inherit testScript;
     name = "openldap";
 
@@ -53,10 +58,10 @@ in {
         declarativeContents."dc=example" = dbContents;
       };
     };
-  };
+  }) { inherit pkgs system; };
 
   # Old-style configuration
-  oldOptions = import ./make-test-python.nix {
+  oldOptions = import ./make-test-python.nix ({ pkgs, ... }: {
     inherit testScript;
     name = "openldap";
 
@@ -72,10 +77,10 @@ in {
         declarativeContents."dc=example" = dbContents;
       };
     };
-  };
+  }) { inherit system pkgs; };
 
   # Manually managed configDir, for example if dynamic config is essential
-  manualConfigDir = import ./make-test-python.nix {
+  manualConfigDir = import ./make-test-python.nix ({ pkgs, ... }: {
     name = "openldap";
 
     machine = { pkgs, ... }: {
@@ -121,5 +126,5 @@ in {
           "systemctl restart openldap",
       )
     '' + testScript;
-  };
+  }) { inherit system pkgs; };
 }
diff --git a/nixos/tests/orangefs.nix b/nixos/tests/orangefs.nix
index 24b7737058c83..fe9f9cc37ea03 100644
--- a/nixos/tests/orangefs.nix
+++ b/nixos/tests/orangefs.nix
@@ -9,7 +9,7 @@ let
 
     virtualisation.emptyDiskImages = [ 4096 ];
 
-    fileSystems = pkgs.lib.mkVMOverride
+    virtualisation.fileSystems =
       { "/data" =
           { device = "/dev/disk/by-label/data";
             fsType = "ext4";
diff --git a/nixos/tests/php/default.nix b/nixos/tests/php/default.nix
index ee7a3b56a3ef0..cf78c9db53b7f 100644
--- a/nixos/tests/php/default.nix
+++ b/nixos/tests/php/default.nix
@@ -1,8 +1,15 @@
-{ system ? builtins.currentSystem,
-  config ? {},
-  pkgs ? import ../../.. { inherit system config; }
-}: {
-  fpm = import ./fpm.nix { inherit system pkgs; };
-  httpd = import ./httpd.nix { inherit system pkgs; };
-  pcre = import ./pcre.nix { inherit system pkgs; };
+{ system ? builtins.currentSystem
+, config ? {}
+, pkgs ? import ../../.. { inherit system config; }
+, php ? pkgs.php
+}:
+
+let
+  php' = php.buildEnv {
+    extensions = { enabled, all }: with all; enabled ++ [ apcu ];
+  };
+in {
+  fpm = import ./fpm.nix { inherit system pkgs; php = php'; };
+  httpd = import ./httpd.nix { inherit system pkgs; php = php'; };
+  pcre = import ./pcre.nix { inherit system pkgs; php = php'; };
 }
diff --git a/nixos/tests/php/fpm.nix b/nixos/tests/php/fpm.nix
index 9ad515ebdde06..b11f85d39cb6f 100644
--- a/nixos/tests/php/fpm.nix
+++ b/nixos/tests/php/fpm.nix
@@ -1,8 +1,10 @@
-import ../make-test-python.nix ({pkgs, lib, ...}: {
-  name = "php-fpm-nginx-test";
+import ../make-test-python.nix ({pkgs, lib, php, ...}: {
+  name = "php-${php.version}-fpm-nginx-test";
   meta.maintainers = lib.teams.php.members;
 
   machine = { config, lib, pkgs, ... }: {
+    environment.systemPackages = [ php ];
+
     services.nginx = {
       enable = true;
 
@@ -10,7 +12,7 @@ import ../make-test-python.nix ({pkgs, lib, ...}: {
         testdir = pkgs.writeTextDir "web/index.php" "<?php phpinfo();";
       in {
         root = "${testdir}/web";
-        locations."~ \.php$".extraConfig = ''
+        locations."~ \\.php$".extraConfig = ''
           fastcgi_pass unix:${config.services.phpfpm.pools.foobar.socket};
           fastcgi_index index.php;
           include ${pkgs.nginx}/conf/fastcgi_params;
@@ -25,6 +27,7 @@ import ../make-test-python.nix ({pkgs, lib, ...}: {
 
     services.phpfpm.pools."foobar" = {
       user = "nginx";
+      phpPackage = php;
       settings = {
         "listen.group" = "nginx";
         "listen.mode" = "0600";
@@ -44,10 +47,11 @@ import ../make-test-python.nix ({pkgs, lib, ...}: {
 
     # Check so we get an evaluated PHP back
     response = machine.succeed("curl -fvvv -s http://127.0.0.1:80/")
-    assert "PHP Version ${pkgs.php.version}" in response, "PHP version not detected"
+    assert "PHP Version ${php.version}" in response, "PHP version not detected"
 
     # Check so we have database and some other extensions loaded
-    for ext in ["json", "opcache", "pdo_mysql", "pdo_pgsql", "pdo_sqlite"]:
+    for ext in ["json", "opcache", "pdo_mysql", "pdo_pgsql", "pdo_sqlite", "apcu"]:
         assert ext in response, f"Missing {ext} extension"
+        machine.succeed(f'test -n "$(php -m | grep -i {ext})"')
   '';
 })
diff --git a/nixos/tests/php/httpd.nix b/nixos/tests/php/httpd.nix
index 27ea7a24e3a99..a5ca9b3c5d1fe 100644
--- a/nixos/tests/php/httpd.nix
+++ b/nixos/tests/php/httpd.nix
@@ -1,5 +1,5 @@
-import ../make-test-python.nix ({pkgs, lib, ...}: {
-  name = "php-httpd-test";
+import ../make-test-python.nix ({pkgs, lib, php, ...}: {
+  name = "php-${php.version}-httpd-test";
   meta.maintainers = lib.teams.php.members;
 
   machine = { config, lib, pkgs, ... }: {
@@ -14,6 +14,7 @@ import ../make-test-python.nix ({pkgs, lib, ...}: {
           index = "index.php index.html";
         };
       };
+      phpPackage = php;
       enablePHP = true;
     };
   };
@@ -22,7 +23,7 @@ import ../make-test-python.nix ({pkgs, lib, ...}: {
 
     # Check so we get an evaluated PHP back
     response = machine.succeed("curl -fvvv -s http://127.0.0.1:80/")
-    assert "PHP Version ${pkgs.php.version}" in response, "PHP version not detected"
+    assert "PHP Version ${php.version}" in response, "PHP version not detected"
 
     # Check so we have database and some other extensions loaded
     for ext in ["json", "opcache", "pdo_mysql", "pdo_pgsql", "pdo_sqlite"]:
diff --git a/nixos/tests/php/pcre.nix b/nixos/tests/php/pcre.nix
index 3ea19304bffd5..97572f63af33a 100644
--- a/nixos/tests/php/pcre.nix
+++ b/nixos/tests/php/pcre.nix
@@ -1,7 +1,7 @@
 let
   testString = "can-use-subgroups";
-in import ../make-test-python.nix ({lib, ...}: {
-  name = "php-httpd-pcre-jit-test";
+in import ../make-test-python.nix ({lib, php, ...}: {
+  name = "php-${php.version}-httpd-pcre-jit-test";
   meta.maintainers = lib.teams.php.members;
 
   machine = { lib, pkgs, ... }: {
@@ -9,6 +9,7 @@ in import ../make-test-python.nix ({lib, ...}: {
     services.httpd = {
       enable = true;
       adminAddr = "please@dont.contact";
+      phpPackage = php;
       enablePHP = true;
       phpOptions = "pcre.jit = true";
       extraConfig = let
diff --git a/nixos/tests/plikd.nix b/nixos/tests/plikd.nix
new file mode 100644
index 0000000000000..8fec93c01f6bf
--- /dev/null
+++ b/nixos/tests/plikd.nix
@@ -0,0 +1,27 @@
+import ./make-test-python.nix ({ lib, ... }: {
+  name = "plikd";
+  meta = with lib.maintainers; {
+    maintainers = [ freezeboy ];
+  };
+
+  machine = { pkgs, ... }: let
+  in {
+    services.plikd.enable = true;
+    environment.systemPackages = [ pkgs.plik ];
+  };
+
+  testScript = ''
+    # Service basic test
+    machine.wait_for_unit("plikd")
+
+    # Network test
+    machine.wait_for_open_port("8080")
+    machine.succeed("curl --fail -v http://localhost:8080")
+
+    # Application test
+    machine.execute("echo test > /tmp/data.txt")
+    machine.succeed("plik --server http://localhost:8080 /tmp/data.txt | grep curl")
+
+    machine.succeed("diff data.txt /tmp/data.txt")
+  '';
+})
diff --git a/nixos/tests/privoxy.nix b/nixos/tests/privoxy.nix
new file mode 100644
index 0000000000000..d16cc498691fd
--- /dev/null
+++ b/nixos/tests/privoxy.nix
@@ -0,0 +1,113 @@
+import ./make-test-python.nix ({ lib, pkgs, ... }:
+
+let
+  # Note: For some reason Privoxy can't issue valid
+  # certificates if the CA is generated using gnutls :(
+  certs = pkgs.runCommand "example-certs"
+    { buildInputs = [ pkgs.openssl ]; }
+    ''
+      mkdir $out
+
+      # generate CA keypair
+      openssl req -new -nodes -x509 \
+        -extensions v3_ca -keyout $out/ca.key \
+        -out $out/ca.crt -days 365 \
+        -subj "/O=Privoxy CA/CN=Privoxy CA"
+
+      # generate server key/signing request
+      openssl genrsa -out $out/server.key 3072
+      openssl req -new -key $out/server.key \
+        -out server.csr -sha256 \
+        -subj "/O=An unhappy server./CN=example.com"
+
+      # sign the request/generate the certificate
+      openssl x509 -req -in server.csr -CA $out/ca.crt \
+      -CAkey $out/ca.key -CAcreateserial -out $out/server.crt \
+      -days 500 -sha256
+    '';
+in
+
+{
+  name = "privoxy";
+  meta = with lib.maintainers; {
+    maintainers = [ rnhmjoj ];
+  };
+
+  machine = { ... }: {
+    services.nginx.enable = true;
+    services.nginx.virtualHosts."example.com" = {
+      addSSL = true;
+      sslCertificate = "${certs}/server.crt";
+      sslCertificateKey = "${certs}/server.key";
+      locations."/".root = pkgs.writeTextFile
+        { name = "bad-day";
+          destination = "/how-are-you/index.html";
+          text = "I've had a bad day!\n";
+        };
+      locations."/ads".extraConfig = ''
+        return 200 "Hot Nixpkgs PRs in your area. Click here!\n";
+      '';
+    };
+
+    services.privoxy = {
+      enable = true;
+      inspectHttps = true;
+      settings = {
+        ca-cert-file = "${certs}/ca.crt";
+        ca-key-file  = "${certs}/ca.key";
+        debug = 65536;
+      };
+      userActions = ''
+        {+filter{positive}}
+        example.com
+
+        {+block{Fake ads}}
+        example.com/ads
+      '';
+      userFilters = ''
+        FILTER: positive This is a filter example.
+        s/bad/great/ig
+      '';
+    };
+
+    security.pki.certificateFiles = [ "${certs}/ca.crt" ];
+
+    networking.hosts."::1" = [ "example.com" ];
+    networking.proxy.httpProxy = "http://localhost:8118";
+    networking.proxy.httpsProxy = "http://localhost:8118";
+  };
+
+  testScript =
+    ''
+      with subtest("Privoxy is running"):
+          machine.wait_for_unit("privoxy")
+          machine.wait_for_open_port("8118")
+          machine.succeed("curl -f http://config.privoxy.org")
+
+      with subtest("Privoxy can filter http requests"):
+          machine.wait_for_open_port("80")
+          assert "great day" in machine.succeed(
+              "curl -sfL http://example.com/how-are-you? | tee /dev/stderr"
+          )
+
+      with subtest("Privoxy can filter https requests"):
+          machine.wait_for_open_port("443")
+          assert "great day" in machine.succeed(
+              "curl -sfL https://example.com/how-are-you? | tee /dev/stderr"
+          )
+
+      with subtest("Blocks are working"):
+          machine.wait_for_open_port("443")
+          machine.fail("curl -f https://example.com/ads 1>&2")
+          machine.succeed("curl -f https://example.com/PRIVOXY-FORCE/ads 1>&2")
+
+      with subtest("Temporary certificates are cleaned"):
+          # Count current certificates
+          machine.succeed("test $(ls /run/privoxy/certs | wc -l) -gt 0")
+          # Forward in time 12 days, trigger the timer..
+          machine.succeed("date -s \"$(date --date '12 days')\"")
+          machine.systemctl("start systemd-tmpfiles-clean")
+          # ...and count again
+          machine.succeed("test $(ls /run/privoxy/certs | wc -l) -eq 0")
+    '';
+})
diff --git a/nixos/tests/prometheus-exporters.nix b/nixos/tests/prometheus-exporters.nix
index 53aea4f258af3..78a3afad0ba33 100644
--- a/nixos/tests/prometheus-exporters.nix
+++ b/nixos/tests/prometheus-exporters.nix
@@ -75,6 +75,21 @@ let
       '';
     };
 
+    artifactory = {
+      exporterConfig = {
+        enable = true;
+        artiUsername = "artifactory-username";
+        artiPassword = "artifactory-password";
+      };
+      exporterTest = ''
+        wait_for_unit("prometheus-artifactory-exporter.service")
+        wait_for_open_port(9531)
+        succeed(
+            "curl -sSf http://localhost:9531/metrics | grep -q 'artifactory_up'"
+        )
+      '';
+    };
+
     bind = {
       exporterConfig = {
         enable = true;
@@ -248,6 +263,24 @@ let
       '';
     };
 
+    knot = {
+      exporterConfig = {
+        enable = true;
+      };
+      metricProvider = {
+        services.knot = {
+          enable = true;
+          extraArgs = [ "-v" ];
+        };
+      };
+      exporterTest = ''
+        wait_for_unit("knot.service")
+        wait_for_unit("prometheus-knot-exporter.service")
+        wait_for_open_port(9433)
+        succeed("curl -sSf 'localhost:9433' | grep -q 'knot_server_zone_count 0.0'")
+      '';
+    };
+
     keylight = {
       # A hardware device is required to properly test this exporter, so just
       # perform a couple of basic sanity checks that the exporter is running
diff --git a/nixos/tests/ripgrep.nix b/nixos/tests/ripgrep.nix
deleted file mode 100644
index 3ff3bf4be1518..0000000000000
--- a/nixos/tests/ripgrep.nix
+++ /dev/null
@@ -1,13 +0,0 @@
-import ./make-test-python.nix ({ pkgs, ... }: {
-  name = "ripgrep";
-  meta = with pkgs.lib.maintainers; { maintainers = [ nequissimus ]; };
-
-  nodes.ripgrep = { pkgs, ... }: { environment.systemPackages = [ pkgs.ripgrep ]; };
-
-  testScript = ''
-    ripgrep.succeed('echo "abc\nbcd\ncde" > /tmp/foo')
-    assert "bcd" in ripgrep.succeed("rg -N 'bcd' /tmp/foo")
-    assert "bcd\ncde" in ripgrep.succeed("rg -N 'cd' /tmp/foo")
-    assert "ripgrep ${pkgs.ripgrep.version}" in ripgrep.succeed("rg --version | head -1")
-  '';
-})
diff --git a/nixos/tests/samba.nix b/nixos/tests/samba.nix
index 142269752b34f..d1d50caabfa53 100644
--- a/nixos/tests/samba.nix
+++ b/nixos/tests/samba.nix
@@ -8,7 +8,7 @@ import ./make-test-python.nix ({ pkgs, ... }:
   nodes =
     { client =
         { pkgs, ... }:
-        { fileSystems = pkgs.lib.mkVMOverride
+        { virtualisation.fileSystems =
             { "/public" = {
                 fsType = "cifs";
                 device = "//server/public";
diff --git a/nixos/tests/sbt-extras.nix b/nixos/tests/sbt-extras.nix
deleted file mode 100644
index f1672bf20665c..0000000000000
--- a/nixos/tests/sbt-extras.nix
+++ /dev/null
@@ -1,16 +0,0 @@
-import ./make-test-python.nix ({ pkgs, ...} : {
-  name = "sbt-extras";
-  meta = with pkgs.lib.maintainers; {
-    maintainers = [ nequissimus ];
-  };
-
-  machine = { pkgs, ... }:
-    {
-      environment.systemPackages = [ pkgs.sbt-extras ];
-    };
-
-  testScript =
-    ''
-      machine.succeed("(sbt -h)")
-    '';
-})
diff --git a/nixos/tests/sbt.nix b/nixos/tests/sbt.nix
deleted file mode 100644
index 22541232ba65d..0000000000000
--- a/nixos/tests/sbt.nix
+++ /dev/null
@@ -1,18 +0,0 @@
-import ./make-test-python.nix ({ pkgs, ...} : {
-  name = "sbt";
-  meta = with pkgs.lib.maintainers; {
-    maintainers = [ nequissimus ];
-  };
-
-  machine = { pkgs, ... }:
-    {
-      environment.systemPackages = [ pkgs.sbt ];
-    };
-
-  testScript =
-    ''
-      machine.succeed(
-          "(sbt --offline --version 2>&1 || true) | grep 'getting org.scala-sbt sbt ${pkgs.sbt.version}  (this may take some time)'"
-      )
-    '';
-})
diff --git a/nixos/tests/snapper.nix b/nixos/tests/snapper.nix
index 018102d7f6409..098d8d9d72f55 100644
--- a/nixos/tests/snapper.nix
+++ b/nixos/tests/snapper.nix
@@ -9,7 +9,7 @@ import ./make-test-python.nix ({ ... }:
 
     virtualisation.emptyDiskImages = [ 4096 ];
 
-    fileSystems = lib.mkVMOverride {
+    virtualisation.fileSystems = {
       "/home" = {
         device = "/dev/disk/by-label/aux";
         fsType = "btrfs";
diff --git a/nixos/tests/systemd-networkd-ipv6-prefix-delegation.nix b/nixos/tests/systemd-networkd-ipv6-prefix-delegation.nix
index bce78f09fdcce..5831c8692f69e 100644
--- a/nixos/tests/systemd-networkd-ipv6-prefix-delegation.nix
+++ b/nixos/tests/systemd-networkd-ipv6-prefix-delegation.nix
@@ -165,7 +165,7 @@ import ./make-test-python.nix ({pkgs, ...}: {
               # accept the delegated prefix.
               PrefixDelegationHint  = "::/48";
             };
-            ipv6PrefixDelegationConfig = {
+            ipv6SendRAConfig = {
               # Let networkd know that we would very much like to use DHCPv6
               # to obtain the "managed" information. Not sure why they can't
               # just take that from the upstream RAs.
@@ -179,24 +179,20 @@ import ./make-test-python.nix ({pkgs, ...}: {
             name = "eth2";
             networkConfig = {
               Description = "Client interface";
-              # the client shouldn't be allowed to send us RAs, that would be weird.
+              # The client shouldn't be allowed to send us RAs, that would be weird.
               IPv6AcceptRA = false;
 
-              # Just delegate prefixes from the DHCPv6 PD pool.
-              # If you also want to distribute a local ULA prefix you want to
-              # set this to `yes` as that includes both static prefixes as well
-              # as PD prefixes.
-              IPv6PrefixDelegation = "dhcpv6";
+              # Delegate prefixes from the DHCPv6 PD pool.
+              DHCPv6PrefixDelegation = true;
+              IPv6SendRA = true;
             };
-            # finally "act as router" (according to systemd.network(5))
-            ipv6PrefixDelegationConfig = {
-              RouterLifetimeSec = 300; # required as otherwise no RA's are being emitted
 
-              # In a production environment you should consider setting these as well:
+            # In a production environment you should consider setting these as well:
+            # ipv6SendRAConfig = {
               #EmitDNS = true;
               #EmitDomains = true;
               #DNS= = "fe80::1"; # or whatever "well known" IP your router will have on the inside.
-            };
+            # };
 
             # This adds a "random" ULA prefix to the interface that is being
             # advertised to the clients.
diff --git a/nixos/tests/systemd.nix b/nixos/tests/systemd.nix
index f7c13a587c58e..e0685f53a9454 100644
--- a/nixos/tests/systemd.nix
+++ b/nixos/tests/systemd.nix
@@ -9,7 +9,7 @@ import ./make-test-python.nix ({ pkgs, ... }: {
 
     environment.systemPackages = [ pkgs.cryptsetup ];
 
-    fileSystems = lib.mkVMOverride {
+    virtualisation.fileSystems = {
       "/test-x-initrd-mount" = {
         device = "/dev/vdb";
         fsType = "ext2";
diff --git a/nixos/tests/traefik.nix b/nixos/tests/traefik.nix
index 4eeae29acadf3..f27f6e1e6d695 100644
--- a/nixos/tests/traefik.nix
+++ b/nixos/tests/traefik.nix
@@ -11,8 +11,8 @@ import ./make-test-python.nix ({ pkgs, ... }: {
       environment.systemPackages = [ pkgs.curl ];
     };
     traefik = { config, pkgs, ... }: {
-      docker-containers.nginx = {
-        extraDockerOptions = [
+      virtualisation.oci-containers.containers.nginx = {
+        extraOptions = [
           "-l" "traefik.enable=true"
           "-l" "traefik.http.routers.nginx.entrypoints=web"
           "-l" "traefik.http.routers.nginx.rule=Host(`nginx.traefik.test`)"
diff --git a/nixos/tests/unbound.nix b/nixos/tests/unbound.nix
index c88231636226b..d4b8bb15ced6d 100644
--- a/nixos/tests/unbound.nix
+++ b/nixos/tests/unbound.nix
@@ -27,6 +27,9 @@ import ./make-test-python.nix ({ pkgs, lib, ... }:
         # disable the root anchor update as we do not have internet access during
         # the test execution
         services.unbound.enableRootTrustAnchor = false;
+
+        # we want to test the full-variant of the package to also get DoH support
+        services.unbound.package = pkgs.unbound-full;
       };
     };
 
@@ -81,13 +84,16 @@ import ./make-test-python.nix ({ pkgs, lib, ... }:
         networking.firewall.allowedTCPPorts = [
           53 # regular DNS
           853 # DNS over TLS
+          443 # DNS over HTTPS
         ];
         networking.firewall.allowedUDPPorts = [ 53 ];
 
         services.unbound = {
           enable = true;
           allowedAccess = [ "192.168.0.0/24" "fd21::/64" "::1" "127.0.0.0/8" ];
-          interfaces = [ "::1" "127.0.0.1" "192.168.0.2" "fd21::2" "192.168.0.2@853" "fd21::2@853" "::1@853" "127.0.0.1@853" ];
+          interfaces = [ "::1" "127.0.0.1" "192.168.0.2" "fd21::2"
+                         "192.168.0.2@853" "fd21::2@853" "::1@853" "127.0.0.1@853"
+                         "192.168.0.2@443" "fd21::2@443" "::1@443" "127.0.0.1@443" ];
           forwardAddresses = [
             (lib.head nodes.authoritative.config.networking.interfaces.eth1.ipv6.addresses).address
             (lib.head nodes.authoritative.config.networking.interfaces.eth1.ipv4.addresses).address
@@ -217,6 +223,14 @@ import ./make-test-python.nix ({ pkgs, lib, ... }:
                           expected,
                           ["+tcp", "+tls"] + args,
                       )
+                      query(
+                          machine,
+                          remote,
+                          query_type,
+                          zone,
+                          expected,
+                          ["+https"] + args,
+                      )
 
 
       client.start()
diff --git a/nixos/tests/zfs.nix b/nixos/tests/zfs.nix
index 03aa5e5399c65..ba5eb7cd528eb 100644
--- a/nixos/tests/zfs.nix
+++ b/nixos/tests/zfs.nix
@@ -29,7 +29,7 @@ let
 
         # Setup regular fileSystems machinery to ensure forceImportAll can be
         # tested via the regular service units.
-        fileSystems = lib.mkVMOverride {
+        virtualisation.fileSystems = {
           "/forcepool" = {
             device = "forcepool";
             fsType = "zfs";