about summary refs log tree commit diff
path: root/nixos
diff options
context:
space:
mode:
Diffstat (limited to 'nixos')
-rw-r--r--nixos/doc/manual/from_md/release-notes/rl-2111.section.xml75
-rw-r--r--nixos/doc/manual/release-notes/rl-2111.section.md23
-rw-r--r--nixos/lib/make-zfs-image.nix333
-rw-r--r--nixos/maintainers/scripts/ec2/amazon-image-zfs.nix12
-rw-r--r--nixos/maintainers/scripts/ec2/amazon-image.nix119
-rw-r--r--nixos/modules/config/fonts/fonts.nix70
-rw-r--r--nixos/modules/config/users-groups.nix13
-rw-r--r--nixos/modules/hardware/video/nvidia.nix13
-rw-r--r--nixos/modules/module-list.nix4
-rw-r--r--nixos/modules/programs/calls.nix25
-rw-r--r--nixos/modules/programs/captive-browser.nix12
-rw-r--r--nixos/modules/security/apparmor/includes.nix2
-rw-r--r--nixos/modules/services/amqp/rabbitmq.nix40
-rw-r--r--nixos/modules/services/audio/roon-bridge.nix14
-rw-r--r--nixos/modules/services/audio/roon-server.nix14
-rw-r--r--nixos/modules/services/cluster/kubernetes/addon-manager.nix3
-rw-r--r--nixos/modules/services/cluster/kubernetes/apiserver.nix4
-rw-r--r--nixos/modules/services/cluster/kubernetes/controller-manager.nix3
-rw-r--r--nixos/modules/services/cluster/kubernetes/kubelet.nix3
-rw-r--r--nixos/modules/services/cluster/kubernetes/proxy.nix3
-rw-r--r--nixos/modules/services/cluster/kubernetes/scheduler.nix3
-rw-r--r--nixos/modules/services/continuous-integration/github-runner.nix12
-rw-r--r--nixos/modules/services/desktops/pipewire/bluez-hardware.conf.json11
-rw-r--r--nixos/modules/services/desktops/pipewire/pipewire-pulse.conf.json1
-rw-r--r--nixos/modules/services/mail/dovecot.nix1
-rw-r--r--nixos/modules/services/mail/postfixadmin.nix199
-rw-r--r--nixos/modules/services/misc/airsonic.nix23
-rw-r--r--nixos/modules/services/misc/gitlab.nix6
-rw-r--r--nixos/modules/services/misc/home-assistant.nix1
-rw-r--r--nixos/modules/services/misc/mautrix-telegram.nix2
-rw-r--r--nixos/modules/services/misc/nzbget.nix54
-rw-r--r--nixos/modules/services/misc/octoprint.nix3
-rw-r--r--nixos/modules/services/misc/paperless-ng.nix6
-rw-r--r--nixos/modules/services/monitoring/grafana.nix4
-rw-r--r--nixos/modules/services/network-filesystems/ipfs.nix114
-rw-r--r--nixos/modules/services/networking/epmd.nix19
-rw-r--r--nixos/modules/services/networking/soju.nix113
-rw-r--r--nixos/modules/services/networking/syncthing.nix26
-rw-r--r--nixos/modules/services/search/elasticsearch.nix20
-rw-r--r--nixos/modules/services/web-apps/nextcloud.nix2
-rw-r--r--nixos/modules/services/web-apps/nextcloud.xml124
-rw-r--r--nixos/modules/services/web-apps/tt-rss.nix70
-rw-r--r--nixos/modules/services/x11/desktop-managers/gnome.nix7
-rw-r--r--nixos/modules/services/x11/display-managers/sx.nix37
-rw-r--r--nixos/modules/services/x11/window-managers/awesome.nix2
-rw-r--r--nixos/modules/services/x11/window-managers/clfswm.nix16
-rw-r--r--nixos/modules/system/boot/stage-1-init.sh2
-rw-r--r--nixos/modules/tasks/filesystems.nix2
-rw-r--r--nixos/modules/tasks/filesystems/exfat.nix8
-rw-r--r--nixos/modules/tasks/filesystems/zfs.nix100
-rw-r--r--nixos/modules/virtualisation/amazon-image.nix10
-rw-r--r--nixos/modules/virtualisation/amazon-options.nix54
-rw-r--r--nixos/release.nix14
-rw-r--r--nixos/tests/all-tests.nix6
-rw-r--r--nixos/tests/caddy.nix95
-rw-r--r--nixos/tests/gitlab.nix3
-rw-r--r--nixos/tests/graylog.nix2
-rw-r--r--nixos/tests/kernel-generic.nix44
-rw-r--r--nixos/tests/kubernetes/default.nix18
-rw-r--r--nixos/tests/matrix-synapse.nix105
-rw-r--r--nixos/tests/nextcloud/basic.nix1
-rw-r--r--nixos/tests/nexus.nix2
-rw-r--r--nixos/tests/nzbget.nix18
-rw-r--r--nixos/tests/paperless-ng.nix14
-rw-r--r--nixos/tests/postfixadmin.nix31
-rw-r--r--nixos/tests/rabbitmq.nix6
-rw-r--r--nixos/tests/xautolock.nix2
-rw-r--r--nixos/tests/xss-lock.nix2
-rw-r--r--nixos/tests/yabar.nix2
69 files changed, 1856 insertions, 346 deletions
diff --git a/nixos/doc/manual/from_md/release-notes/rl-2111.section.xml b/nixos/doc/manual/from_md/release-notes/rl-2111.section.xml
index a138d4c8780c..3c7f9a274b3d 100644
--- a/nixos/doc/manual/from_md/release-notes/rl-2111.section.xml
+++ b/nixos/doc/manual/from_md/release-notes/rl-2111.section.xml
@@ -22,7 +22,7 @@
       </listitem>
       <listitem>
         <para>
-          kOps now defaults to 1.21.0, which uses containerd as the
+          kOps now defaults to 1.21.1, which uses containerd as the
           default runtime.
         </para>
       </listitem>
@@ -182,7 +182,7 @@
         </para>
       </listitem>
     </itemizedlist>
-    <itemizedlist spacing="compact">
+    <itemizedlist>
       <listitem>
         <para>
           <link xlink:href="https://docs.fluidd.xyz/">fluidd</link>, a
@@ -191,6 +191,29 @@
           <link linkend="opt-services.fluidd.enable">fluidd</link>.
         </para>
       </listitem>
+      <listitem>
+        <para>
+          <link xlink:href="https://github.com/earnestly/sx">sx</link>,
+          a simple alternative to both xinit and startx for starting a
+          Xorg server. Available as
+          <link linkend="opt-services.xserver.displayManager.sx.enable">services.xserver.displayManager.sx</link>
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          <link xlink:href="https://postfixadmin.sourceforge.io/">postfixadmin</link>,
+          a web based virtual user administration interface for Postfix
+          mail servers. Available as
+          <link linkend="opt-services.postfixadmin.enable">postfixadmin</link>.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          <link xlink:href="https://sr.ht/~emersion/soju">soju</link>, a
+          user-friendly IRC bouncer. Available as
+          <link xlink:href="options.html#opt-services.soju.enable">services.soju</link>.
+        </para>
+      </listitem>
     </itemizedlist>
   </section>
   <section xml:id="sec-release-21.11-incompatibilities">
@@ -288,6 +311,12 @@ Superuser created successfully.
       </listitem>
       <listitem>
         <para>
+          Firefox v91 does not support addons with invalid signature
+          anymore. Firefox ESR needs to be used for nix addon support.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
           The <literal>erigon</literal> ethereum node has moved to a new
           database format in <literal>2021-05-04</literal>, and requires
           a full resync
@@ -295,6 +324,15 @@ Superuser created successfully.
       </listitem>
       <listitem>
         <para>
+          The <literal>erigon</literal> ethereum node has moved it’s
+          database location in <literal>2021-08-03</literal>, users
+          upgrading must manually move their chaindata (see
+          <link xlink:href="https://github.com/ledgerwatch/erigon/releases/tag/v2021.08.03">release
+          notes</link>).
+        </para>
+      </listitem>
+      <listitem>
+        <para>
           <literal>services.geoip-updater</literal> was broken and has
           been replaced by
           <link xlink:href="options.html#opt-services.geoipupdate.enable">services.geoipupdate</link>.
@@ -847,6 +885,23 @@ Superuser created successfully.
           release instead of 1.0.x
         </para>
       </listitem>
+      <listitem>
+        <para>
+          If <literal>exfat</literal> is included in
+          <literal>boot.supportedFilesystems</literal> and when using
+          kernel 5.7 or later, the <literal>exfatprogs</literal>
+          user-space utilities are used instead of
+          <literal>exfat</literal>.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          The <literal>todoman</literal> package was upgraded from 3.9.0
+          to 4.0.0. This introduces breaking changes in the
+          <link xlink:href="https://todoman.readthedocs.io/en/stable/configure.html#configuration-file">configuration
+          file</link> format.
+        </para>
+      </listitem>
     </itemizedlist>
   </section>
   <section xml:id="sec-release-21.11-notable-changes">
@@ -1033,6 +1088,22 @@ Superuser created successfully.
           sign OCSP responses and server certificates.
         </para>
       </listitem>
+      <listitem>
+        <para>
+          <literal>lib.formats.yaml</literal>’s
+          <literal>generate</literal> will not generate JSON anymore,
+          but instead use more of the YAML-specific syntax.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          GNOME desktop environment now enables
+          <literal>QGnomePlatform</literal> as the Qt platform theme,
+          which should avoid crashes when opening file chooser dialogs
+          in Qt apps by using XDG desktop portal. Additionally, it will
+          make the apps fit better visually.
+        </para>
+      </listitem>
     </itemizedlist>
   </section>
 </section>
diff --git a/nixos/doc/manual/release-notes/rl-2111.section.md b/nixos/doc/manual/release-notes/rl-2111.section.md
index 35d65dc43cf3..c5a4b5bd98c0 100644
--- a/nixos/doc/manual/release-notes/rl-2111.section.md
+++ b/nixos/doc/manual/release-notes/rl-2111.section.md
@@ -7,7 +7,8 @@ In addition to numerous new and upgraded packages, this release has the followin
 ## Highlights {#sec-release-21.11-highlights}
 
 - PHP now defaults to PHP 8.0, updated from 7.4.
-- kOps now defaults to 1.21.0, which uses containerd as the default runtime.
+
+- kOps now defaults to 1.21.1, which uses containerd as the default runtime.
 
 - `python3` now defaults to Python 3.9, updated from Python 3.8.
 
@@ -58,8 +59,15 @@ subsonic-compatible api. Available as [navidrome](#opt-services.navidrome.enable
 
 - [fluidd](https://docs.fluidd.xyz/), a Klipper web interface for managing 3d printers using moonraker. Available as [fluidd](#opt-services.fluidd.enable).
 
+- [sx](https://github.com/earnestly/sx), a simple alternative to both xinit and startx for starting a Xorg server. Available as [services.xserver.displayManager.sx](#opt-services.xserver.displayManager.sx.enable)
+
+- [postfixadmin](https://postfixadmin.sourceforge.io/), a web based virtual user administration interface for Postfix mail servers. Available as [postfixadmin](#opt-services.postfixadmin.enable).
+
+- [soju](https://sr.ht/~emersion/soju), a user-friendly IRC bouncer. Available as [services.soju](options.html#opt-services.soju.enable).
+
 ## Backward Incompatibilities {#sec-release-21.11-incompatibilities}
 
+
 - The `paperless` module and package have been removed. All users should migrate to the
   successor `paperless-ng` instead. The Paperless project [has been
   archived](https://github.com/the-paperless-project/paperless/commit/9b0063c9731f7c5f65b1852cb8caff97f5e40ba4)
@@ -109,8 +117,12 @@ subsonic-compatible api. Available as [navidrome](#opt-services.navidrome.enable
 
 - The `staticjinja` package has been upgraded from 1.0.4 to 4.1.0
 
+- Firefox v91 does not support addons with invalid signature anymore. Firefox ESR needs to be used for nix addon support.
+
 - The `erigon` ethereum node has moved to a new database format in `2021-05-04`, and requires a full resync
 
+- The `erigon` ethereum node has moved it's database location in `2021-08-03`, users upgrading must manually move their chaindata (see [release notes](https://github.com/ledgerwatch/erigon/releases/tag/v2021.08.03)).
+
 - `services.geoip-updater` was broken and has been replaced by [services.geoipupdate](options.html#opt-services.geoipupdate.enable).
 
 - PHP 7.3 is no longer supported due to upstream not supporting this version for the entire lifecycle of the 21.11 release.
@@ -246,6 +258,11 @@ To be able to access the web UI this port needs to be opened in the firewall.
 
 - The `nomad` package now defaults to a 1.1.x release instead of 1.0.x
 
+- If `exfat` is included in `boot.supportedFilesystems` and when using kernel 5.7
+  or later, the `exfatprogs` user-space utilities are used instead of `exfat`.
+
+- The `todoman` package was upgraded from 3.9.0 to 4.0.0. This introduces breaking changes in the [configuration file](https://todoman.readthedocs.io/en/stable/configure.html#configuration-file) format.
+
 ## Other Notable Changes {#sec-release-21.11-notable-changes}
 
 - The setting [`services.openssh.logLevel`](options.html#opt-services.openssh.logLevel) `"VERBOSE"` `"INFO"`. This brings NixOS in line with upstream and other Linux distributions, and reduces log spam on servers due to bruteforcing botnets.
@@ -295,3 +312,7 @@ To be able to access the web UI this port needs to be opened in the firewall.
 - Zfs: `latestCompatibleLinuxPackages` is now exported on the zfs package. One can use `boot.kernelPackages = config.boot.zfs.package.latestCompatibleLinuxPackages;` to always track the latest compatible kernel with a given version of zfs.
 
 - Nginx will use the value of `sslTrustedCertificate` if provided for a virtual host, even if `enableACME` is set. This is useful for providers not using the same certificate to sign OCSP responses and server certificates.
+
+- `lib.formats.yaml`'s `generate` will not generate JSON anymore, but instead use more of the YAML-specific syntax.
+
+- GNOME desktop environment now enables `QGnomePlatform` as the Qt platform theme, which should avoid crashes when opening file chooser dialogs in Qt apps by using XDG desktop portal. Additionally, it will make the apps fit better visually.
diff --git a/nixos/lib/make-zfs-image.nix b/nixos/lib/make-zfs-image.nix
new file mode 100644
index 000000000000..40648ca24d4d
--- /dev/null
+++ b/nixos/lib/make-zfs-image.nix
@@ -0,0 +1,333 @@
+# Note: This is a private API, internal to NixOS. Its interface is subject
+# to change without notice.
+#
+# The result of this builder is two disk images:
+#
+#  * `boot` - a small disk formatted with FAT to be used for /boot. FAT is
+#    chosen to support EFI.
+#  * `root` - a larger disk with a zpool taking the entire disk.
+#
+# This two-disk approach is taken to satisfy ZFS's requirements for
+# autoexpand.
+#
+# # Why doesn't autoexpand work with ZFS in a partition?
+#
+# When ZFS owns the whole disk doesn’t really use a partition: it has
+# a marker partition at the start and a marker partition at the end of
+# the disk.
+#
+# If ZFS is constrained to a partition, ZFS leaves expanding the partition
+# up to the user. Obviously, the user may not choose to do so.
+#
+# Once the user expands the partition, calling zpool online -e expands the
+# vdev to use the whole partition. It doesn’t happen automatically
+# presumably because zed doesn’t get an event saying it’s partition grew,
+# whereas it can and does get an event saying the whole disk it is on is
+# now larger.
+{ lib
+, pkgs
+, # The NixOS configuration to be installed onto the disk image.
+  config
+
+, # size of the FAT boot disk, in megabytes.
+  bootSize ? 1024
+
+, # The size of the root disk, in megabytes.
+  rootSize ? 2048
+
+, # The name of the ZFS pool
+  rootPoolName ? "tank"
+
+, # zpool properties
+  rootPoolProperties ? {
+    autoexpand = "on";
+  }
+, # pool-wide filesystem properties
+  rootPoolFilesystemProperties ? {
+    acltype = "posixacl";
+    atime = "off";
+    compression = "on";
+    mountpoint = "legacy";
+    xattr = "sa";
+  }
+
+, # datasets, with per-attribute options:
+  # mount: (optional) mount point in the VM
+  # properties: (optional) ZFS properties on the dataset, like filesystemProperties
+  # Notes:
+  # 1. datasets will be created from shorter to longer names as a simple topo-sort
+  # 2. you should define a root's dataset's mount for `/`
+  datasets ? { }
+
+, # The files and directories to be placed in the target file system.
+  # This is a list of attribute sets {source, target} where `source'
+  # is the file system object (regular file or directory) to be
+  # grafted in the file system at path `target'.
+  contents ? []
+
+, # The initial NixOS configuration file to be copied to
+  # /etc/nixos/configuration.nix. This configuration will be embedded
+  # inside a configuration which includes the described ZFS fileSystems.
+  configFile ? null
+
+, # Shell code executed after the VM has finished.
+  postVM ? ""
+
+, name ? "nixos-disk-image"
+
+, # Disk image format, one of qcow2, qcow2-compressed, vdi, vpc, raw.
+  format ? "raw"
+
+, # Include a copy of Nixpkgs in the disk image
+  includeChannel ? true
+}:
+let
+  formatOpt = if format == "qcow2-compressed" then "qcow2" else format;
+
+  compress = lib.optionalString (format == "qcow2-compressed") "-c";
+
+  filenameSuffix = "." + {
+    qcow2 = "qcow2";
+    vdi = "vdi";
+    vpc = "vhd";
+    raw = "img";
+  }.${formatOpt} or formatOpt;
+  bootFilename = "nixos.boot${filenameSuffix}";
+  rootFilename = "nixos.root${filenameSuffix}";
+
+  # FIXME: merge with channel.nix / make-channel.nix.
+  channelSources =
+    let
+      nixpkgs = lib.cleanSource pkgs.path;
+    in
+      pkgs.runCommand "nixos-${config.system.nixos.version}" {} ''
+        mkdir -p $out
+        cp -prd ${nixpkgs.outPath} $out/nixos
+        chmod -R u+w $out/nixos
+        if [ ! -e $out/nixos/nixpkgs ]; then
+          ln -s . $out/nixos/nixpkgs
+        fi
+        rm -rf $out/nixos/.git
+        echo -n ${config.system.nixos.versionSuffix} > $out/nixos/.version-suffix
+      '';
+
+  closureInfo = pkgs.closureInfo {
+    rootPaths = [ config.system.build.toplevel ]
+    ++ (lib.optional includeChannel channelSources);
+  };
+
+  modulesTree = pkgs.aggregateModules
+    (with config.boot.kernelPackages; [ kernel zfs ]);
+
+  tools = lib.makeBinPath (
+    with pkgs; [
+      config.system.build.nixos-enter
+      config.system.build.nixos-install
+      dosfstools
+      e2fsprogs
+      gptfdisk
+      nix
+      parted
+      utillinux
+      zfs
+    ]
+  );
+
+  hasDefinedMount  = disk: ((disk.mount or null) != null);
+
+  stringifyProperties = prefix: properties: lib.concatStringsSep " \\\n" (
+    lib.mapAttrsToList
+      (
+        property: value: "${prefix} ${lib.escapeShellArg property}=${lib.escapeShellArg value}"
+      )
+      properties
+  );
+
+  featuresToProperties = features:
+    lib.listToAttrs
+      (builtins.map (feature: {
+        name = "feature@${feature}";
+        value = "enabled";
+      }) features);
+
+  createDatasets =
+    let
+      datasetlist = lib.mapAttrsToList lib.nameValuePair datasets;
+      sorted = lib.sort (left: right: (lib.stringLength left.name) < (lib.stringLength right.name)) datasetlist;
+      cmd = { name, value }:
+        let
+          properties = stringifyProperties "-o" (value.properties or {});
+        in
+          "zfs create -p ${properties} ${name}";
+    in
+      lib.concatMapStringsSep "\n" cmd sorted;
+
+  mountDatasets =
+    let
+      datasetlist = lib.mapAttrsToList lib.nameValuePair datasets;
+      mounts = lib.filter ({ value, ... }: hasDefinedMount value) datasetlist;
+      sorted = lib.sort (left: right: (lib.stringLength left.value.mount) < (lib.stringLength right.value.mount)) mounts;
+      cmd = { name, value }:
+        ''
+          mkdir -p /mnt${lib.escapeShellArg value.mount}
+          mount -t zfs ${name} /mnt${lib.escapeShellArg value.mount}
+        '';
+    in
+      lib.concatMapStringsSep "\n" cmd sorted;
+
+  unmountDatasets =
+    let
+      datasetlist = lib.mapAttrsToList lib.nameValuePair datasets;
+      mounts = lib.filter ({ value, ... }: hasDefinedMount value) datasetlist;
+      sorted = lib.sort (left: right: (lib.stringLength left.value.mount) > (lib.stringLength right.value.mount)) mounts;
+      cmd = { name, value }:
+        ''
+          umount /mnt${lib.escapeShellArg value.mount}
+        '';
+    in
+      lib.concatMapStringsSep "\n" cmd sorted;
+
+
+  fileSystemsCfgFile =
+    let
+      mountable = lib.filterAttrs (_: value: hasDefinedMount value) datasets;
+    in
+      pkgs.runCommand "filesystem-config.nix" {
+        buildInputs = with pkgs; [ jq nixpkgs-fmt ];
+        filesystems = builtins.toJSON {
+          fileSystems = lib.mapAttrs'
+            (
+              dataset: attrs:
+                {
+                  name = attrs.mount;
+                  value = {
+                    fsType = "zfs";
+                    device = "${dataset}";
+                  };
+                }
+            )
+            mountable;
+        };
+        passAsFile = [ "filesystems" ];
+      } ''
+      (
+        echo "builtins.fromJSON '''"
+        jq . < "$filesystemsPath"
+        echo "'''"
+      ) > $out
+
+      nixpkgs-fmt $out
+    '';
+
+  mergedConfig =
+    if configFile == null
+    then fileSystemsCfgFile
+    else
+      pkgs.runCommand "configuration.nix" {
+        buildInputs = with pkgs; [ nixpkgs-fmt ];
+      }
+        ''
+          (
+            echo '{ imports = ['
+            printf "(%s)\n" "$(cat ${fileSystemsCfgFile})";
+            printf "(%s)\n" "$(cat ${configFile})";
+            echo ']; }'
+          ) > $out
+
+          nixpkgs-fmt $out
+        '';
+
+  image = (
+    pkgs.vmTools.override {
+      rootModules =
+        [ "zfs" "9p" "9pnet_virtio" "virtio_pci" "virtio_blk" ] ++
+          (pkgs.lib.optional (pkgs.stdenv.isi686 || pkgs.stdenv.isx86_64) "rtc_cmos");
+      kernel = modulesTree;
+    }
+  ).runInLinuxVM (
+    pkgs.runCommand name
+      {
+        QEMU_OPTS = "-drive file=$bootDiskImage,if=virtio,cache=unsafe,werror=report"
+         + " -drive file=$rootDiskImage,if=virtio,cache=unsafe,werror=report";
+        preVM = ''
+          PATH=$PATH:${pkgs.qemu_kvm}/bin
+          mkdir $out
+          bootDiskImage=boot.raw
+          qemu-img create -f raw $bootDiskImage ${toString bootSize}M
+
+          rootDiskImage=root.raw
+          qemu-img create -f raw $rootDiskImage ${toString rootSize}M
+        '';
+
+        postVM = ''
+          ${if formatOpt == "raw" then ''
+          mv $bootDiskImage $out/${bootFilename}
+          mv $rootDiskImage $out/${rootFilename}
+        '' else ''
+          ${pkgs.qemu}/bin/qemu-img convert -f raw -O ${formatOpt} ${compress} $bootDiskImage $out/${bootFilename}
+          ${pkgs.qemu}/bin/qemu-img convert -f raw -O ${formatOpt} ${compress} $rootDiskImage $out/${rootFilename}
+        ''}
+          bootDiskImage=$out/${bootFilename}
+          rootDiskImage=$out/${rootFilename}
+          set -x
+          ${postVM}
+        '';
+      } ''
+      export PATH=${tools}:$PATH
+      set -x
+
+      cp -sv /dev/vda /dev/sda
+      cp -sv /dev/vda /dev/xvda
+
+      parted --script /dev/vda -- \
+        mklabel gpt \
+        mkpart no-fs 1MiB 2MiB \
+        set 1 bios_grub on \
+        align-check optimal 1 \
+        mkpart ESP fat32 2MiB -1MiB \
+        align-check optimal 2 \
+        print
+
+      sfdisk --dump /dev/vda
+
+
+      zpool create \
+        ${stringifyProperties "  -o" rootPoolProperties} \
+        ${stringifyProperties "  -O" rootPoolFilesystemProperties} \
+        ${rootPoolName} /dev/vdb
+      parted --script /dev/vdb -- print
+
+      ${createDatasets}
+      ${mountDatasets}
+
+      mkdir -p /mnt/boot
+      mkfs.vfat -n ESP /dev/vda2
+      mount /dev/vda2 /mnt/boot
+
+      mount
+
+      # Install a configuration.nix
+      mkdir -p /mnt/etc/nixos
+      # `cat` so it is mutable on the fs
+      cat ${mergedConfig} > /mnt/etc/nixos/configuration.nix
+
+      export NIX_STATE_DIR=$TMPDIR/state
+      nix-store --load-db < ${closureInfo}/registration
+
+      nixos-install \
+        --root /mnt \
+        --no-root-passwd \
+        --system ${config.system.build.toplevel} \
+        --substituters "" \
+        ${lib.optionalString includeChannel ''--channel ${channelSources}''}
+
+      df -h
+
+      umount /mnt/boot
+      ${unmountDatasets}
+
+      zpool export ${rootPoolName}
+    ''
+  );
+in
+image
diff --git a/nixos/maintainers/scripts/ec2/amazon-image-zfs.nix b/nixos/maintainers/scripts/ec2/amazon-image-zfs.nix
new file mode 100644
index 000000000000..32dd96a7cb7e
--- /dev/null
+++ b/nixos/maintainers/scripts/ec2/amazon-image-zfs.nix
@@ -0,0 +1,12 @@
+{
+  imports = [ ./amazon-image.nix ];
+  ec2.zfs = {
+    enable = true;
+    datasets = {
+      "tank/system/root".mount = "/";
+      "tank/system/var".mount = "/var";
+      "tank/local/nix".mount = "/nix";
+      "tank/user/home".mount = "/home";
+    };
+  };
+}
diff --git a/nixos/maintainers/scripts/ec2/amazon-image.nix b/nixos/maintainers/scripts/ec2/amazon-image.nix
index 677aff4421e0..6942b58f236e 100644
--- a/nixos/maintainers/scripts/ec2/amazon-image.nix
+++ b/nixos/maintainers/scripts/ec2/amazon-image.nix
@@ -4,6 +4,7 @@ with lib;
 
 let
   cfg = config.amazonImage;
+
 in {
 
   imports = [ ../../../modules/virtualisation/amazon-image.nix ];
@@ -53,15 +54,7 @@ in {
     };
   };
 
-  config.system.build.amazonImage = import ../../../lib/make-disk-image.nix {
-    inherit lib config;
-    inherit (cfg) contents format name;
-    pkgs = import ../../../.. { inherit (pkgs) system; }; # ensure we use the regular qemu-kvm package
-    partitionTableType = if config.ec2.efi then "efi"
-                         else if config.ec2.hvm then "legacy+gpt"
-                         else "none";
-    diskSize = cfg.sizeMB;
-    fsType = "ext4";
+  config.system.build.amazonImage = let
     configFile = pkgs.writeText "configuration.nix"
       ''
         { modulesPath, ... }: {
@@ -72,24 +65,96 @@ in {
           ${optionalString config.ec2.efi ''
             ec2.efi = true;
           ''}
+          ${optionalString config.ec2.zfs.enable ''
+            ec2.zfs.enable = true;
+            networking.hostId = "${config.networking.hostId}";
+          ''}
         }
       '';
-    postVM = ''
-      extension=''${diskImage##*.}
-      friendlyName=$out/${cfg.name}.$extension
-      mv "$diskImage" "$friendlyName"
-      diskImage=$friendlyName
-
-      mkdir -p $out/nix-support
-      echo "file ${cfg.format} $diskImage" >> $out/nix-support/hydra-build-products
-
-      ${pkgs.jq}/bin/jq -n \
-        --arg label ${lib.escapeShellArg config.system.nixos.label} \
-        --arg system ${lib.escapeShellArg pkgs.stdenv.hostPlatform.system} \
-        --arg logical_bytes "$(${pkgs.qemu}/bin/qemu-img info --output json "$diskImage" | ${pkgs.jq}/bin/jq '."virtual-size"')" \
-        --arg file "$diskImage" \
-        '$ARGS.named' \
-        > $out/nix-support/image-info.json
-    '';
-  };
+
+    zfsBuilder = import ../../../lib/make-zfs-image.nix {
+      inherit lib config configFile;
+      inherit (cfg) contents format name;
+      pkgs = import ../../../.. { inherit (pkgs) system; }; # ensure we use the regular qemu-kvm package
+
+      includeChannel = true;
+
+      bootSize = 1000; # 1G is the minimum EBS volume
+
+      rootSize = cfg.sizeMB;
+      rootPoolProperties = {
+        ashift = 12;
+        autoexpand = "on";
+      };
+
+      datasets = config.ec2.zfs.datasets;
+
+      postVM = ''
+        extension=''${rootDiskImage##*.}
+        friendlyName=$out/${cfg.name}
+        rootDisk="$friendlyName.root.$extension"
+        bootDisk="$friendlyName.boot.$extension"
+        mv "$rootDiskImage" "$rootDisk"
+        mv "$bootDiskImage" "$bootDisk"
+
+        mkdir -p $out/nix-support
+        echo "file ${cfg.format} $bootDisk" >> $out/nix-support/hydra-build-products
+        echo "file ${cfg.format} $rootDisk" >> $out/nix-support/hydra-build-products
+
+       ${pkgs.jq}/bin/jq -n \
+         --arg system_label ${lib.escapeShellArg config.system.nixos.label} \
+         --arg system ${lib.escapeShellArg pkgs.stdenv.hostPlatform.system} \
+         --arg root_logical_bytes "$(${pkgs.qemu}/bin/qemu-img info --output json "$bootDisk" | ${pkgs.jq}/bin/jq '."virtual-size"')" \
+         --arg boot_logical_bytes "$(${pkgs.qemu}/bin/qemu-img info --output json "$rootDisk" | ${pkgs.jq}/bin/jq '."virtual-size"')" \
+         --arg root "$rootDisk" \
+         --arg boot "$bootDisk" \
+        '{}
+          | .label = $system_label
+          | .system = $system
+          | .disks.boot.logical_bytes = $boot_logical_bytes
+          | .disks.boot.file = $boot
+          | .disks.root.logical_bytes = $root_logical_bytes
+          | .disks.root.file = $root
+          ' > $out/nix-support/image-info.json
+      '';
+    };
+
+    extBuilder = import ../../../lib/make-disk-image.nix {
+      inherit lib config configFile;
+
+      inherit (cfg) contents format name;
+      pkgs = import ../../../.. { inherit (pkgs) system; }; # ensure we use the regular qemu-kvm package
+
+      fsType = "ext4";
+      partitionTableType = if config.ec2.efi then "efi"
+                           else if config.ec2.hvm then "legacy+gpt"
+                           else "none";
+
+      diskSize = cfg.sizeMB;
+
+      postVM = ''
+        extension=''${diskImage##*.}
+        friendlyName=$out/${cfg.name}.$extension
+        mv "$diskImage" "$friendlyName"
+        diskImage=$friendlyName
+
+        mkdir -p $out/nix-support
+        echo "file ${cfg.format} $diskImage" >> $out/nix-support/hydra-build-products
+
+       ${pkgs.jq}/bin/jq -n \
+         --arg system_label ${lib.escapeShellArg config.system.nixos.label} \
+         --arg system ${lib.escapeShellArg pkgs.stdenv.hostPlatform.system} \
+         --arg logical_bytes "$(${pkgs.qemu}/bin/qemu-img info --output json "$diskImage" | ${pkgs.jq}/bin/jq '."virtual-size"')" \
+         --arg file "$diskImage" \
+          '{}
+          | .label = $system_label
+          | .system = $system
+          | .logical_bytes = $logical_bytes
+          | .file = $file
+          | .disks.root.logical_bytes = $logical_bytes
+          | .disks.root.file = $file
+          ' > $out/nix-support/image-info.json
+      '';
+    };
+  in if config.ec2.zfs.enable then zfsBuilder else extBuilder;
 }
diff --git a/nixos/modules/config/fonts/fonts.nix b/nixos/modules/config/fonts/fonts.nix
index 3911196c1013..f87e61e3ef9f 100644
--- a/nixos/modules/config/fonts/fonts.nix
+++ b/nixos/modules/config/fonts/fonts.nix
@@ -2,6 +2,52 @@
 
 with lib;
 
+let
+  # A scalable variant of the X11 "core" cursor
+  #
+  # If not running a fancy desktop environment, the cursor is likely set to
+  # the default `cursor.pcf` bitmap font. This is 17px wide, so it's very
+  # small and almost invisible on 4K displays.
+  fontcursormisc_hidpi = pkgs.xorg.fontxfree86type1.overrideAttrs (old:
+    let
+      # The scaling constant is 230/96: the scalable `left_ptr` glyph at
+      # about 23 points is rendered as 17px, on a 96dpi display.
+      # Note: the XLFD font size is in decipoints.
+      size = 2.39583 * config.services.xserver.dpi;
+      sizeString = builtins.head (builtins.split "\\." (toString size));
+    in
+    {
+      postInstall = ''
+        alias='cursor -xfree86-cursor-medium-r-normal--0-${sizeString}-0-0-p-0-adobe-fontspecific'
+        echo "$alias" > $out/lib/X11/fonts/Type1/fonts.alias
+      '';
+    });
+
+  hasHidpi =
+    config.hardware.video.hidpi.enable &&
+    config.services.xserver.dpi != null;
+
+  defaultFonts =
+    [ pkgs.dejavu_fonts
+      pkgs.freefont_ttf
+      pkgs.gyre-fonts # TrueType substitutes for standard PostScript fonts
+      pkgs.liberation_ttf
+      pkgs.unifont
+      pkgs.noto-fonts-emoji
+    ];
+
+  defaultXFonts =
+    [ (if hasHidpi then fontcursormisc_hidpi else pkgs.xorg.fontcursormisc)
+      pkgs.xorg.fontmiscmisc
+    ] ++ optionals (config.nixpkgs.config.allowUnfree or false)
+    [ # these are unfree, and will make usage with xserver fail
+      pkgs.xorg.fontbhlucidatypewriter100dpi
+      pkgs.xorg.fontbhlucidatypewriter75dpi
+      pkgs.xorg.fontbh100dpi
+    ];
+
+in
+
 {
   imports = [
     (mkRemovedOptionModule [ "fonts" "enableCoreFonts" ] "Use fonts.fonts = [ pkgs.corefonts ]; instead.")
@@ -32,25 +78,9 @@ with lib;
 
   };
 
-  config = {
-
-    fonts.fonts = mkIf config.fonts.enableDefaultFonts
-      ([
-        pkgs.dejavu_fonts
-        pkgs.freefont_ttf
-        pkgs.gyre-fonts # TrueType substitutes for standard PostScript fonts
-        pkgs.liberation_ttf
-        pkgs.xorg.fontmiscmisc
-        pkgs.xorg.fontcursormisc
-        pkgs.unifont
-        pkgs.noto-fonts-emoji
-      ] ++ lib.optionals (config.nixpkgs.config.allowUnfree or false) [
-        # these are unfree, and will make usage with xserver fail
-        pkgs.xorg.fontbhlucidatypewriter100dpi
-        pkgs.xorg.fontbhlucidatypewriter75dpi
-        pkgs.xorg.fontbh100dpi
-      ]);
-
-  };
+  config = mkMerge [
+    { fonts.fonts = mkIf config.fonts.enableDefaultFonts defaultFonts; }
+    { fonts.fonts = mkIf config.services.xserver.enable defaultXFonts; }
+  ];
 
 }
diff --git a/nixos/modules/config/users-groups.nix b/nixos/modules/config/users-groups.nix
index d5e7745c53f9..f86be3be2c65 100644
--- a/nixos/modules/config/users-groups.nix
+++ b/nixos/modules/config/users-groups.nix
@@ -324,7 +324,7 @@ let
 
   };
 
-  groupOpts = { name, ... }: {
+  groupOpts = { name, config, ... }: {
 
     options = {
 
@@ -358,6 +358,10 @@ let
 
     config = {
       name = mkDefault name;
+
+      members = mapAttrsToList (n: u: u.name) (
+        filterAttrs (n: u: elem config.name u.extraGroups) cfg.users
+      );
     };
 
   };
@@ -419,12 +423,7 @@ let
           initialPassword initialHashedPassword;
         shell = utils.toShellPath u.shell;
       }) cfg.users;
-    groups = mapAttrsToList (n: g:
-      { inherit (g) name gid;
-        members = g.members ++ (mapAttrsToList (n: u: u.name) (
-          filterAttrs (n: u: elem g.name u.extraGroups) cfg.users
-        ));
-      }) cfg.groups;
+    groups = attrValues cfg.groups;
   });
 
   systemShells =
diff --git a/nixos/modules/hardware/video/nvidia.nix b/nixos/modules/hardware/video/nvidia.nix
index 1e125eced2cb..cf87ca5377dd 100644
--- a/nixos/modules/hardware/video/nvidia.nix
+++ b/nixos/modules/hardware/video/nvidia.nix
@@ -23,6 +23,7 @@ let
   offloadCfg = pCfg.offload;
   primeEnabled = syncCfg.enable || offloadCfg.enable;
   nvidiaPersistencedEnabled =  cfg.nvidiaPersistenced;
+  nvidiaSettings = cfg.nvidiaSettings;
 in
 
 {
@@ -143,6 +144,15 @@ in
       '';
     };
 
+    hardware.nvidia.nvidiaSettings = mkOption {
+      default = true;
+      type = types.bool;
+      description = ''
+        Whether to add nvidia-settings, NVIDIA's GUI configuration tool, to
+        systemPackages.
+      '';
+    };
+
     hardware.nvidia.nvidiaPersistenced = mkOption {
       default = false;
       type = types.bool;
@@ -279,7 +289,8 @@ in
     hardware.opengl.extraPackages = optional offloadCfg.enable nvidia_x11.out;
     hardware.opengl.extraPackages32 = optional offloadCfg.enable nvidia_x11.lib32;
 
-    environment.systemPackages = [ nvidia_x11.bin nvidia_x11.settings ]
+    environment.systemPackages = [ nvidia_x11.bin ]
+      ++ optionals nvidiaSettings [ nvidia_x11.settings ]
       ++ optionals nvidiaPersistencedEnabled [ nvidia_x11.persistenced ];
 
     systemd.packages = optional cfg.powerManagement.enable nvidia_x11.out;
diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix
index 2473bf2f55fb..ce948bac4282 100644
--- a/nixos/modules/module-list.nix
+++ b/nixos/modules/module-list.nix
@@ -121,6 +121,7 @@
   ./programs/bash-my-aws.nix
   ./programs/bcc.nix
   ./programs/browserpass.nix
+  ./programs/calls.nix
   ./programs/captive-browser.nix
   ./programs/ccache.nix
   ./programs/cdemu.nix
@@ -460,6 +461,7 @@
   ./services/mail/opensmtpd.nix
   ./services/mail/pfix-srsd.nix
   ./services/mail/postfix.nix
+  ./services/mail/postfixadmin.nix
   ./services/mail/postsrsd.nix
   ./services/mail/postgrey.nix
   ./services/mail/spamassassin.nix
@@ -831,6 +833,7 @@
   ./services/networking/smokeping.nix
   ./services/networking/softether.nix
   ./services/networking/solanum.nix
+  ./services/networking/soju.nix
   ./services/networking/spacecookie.nix
   ./services/networking/spiped.nix
   ./services/networking/squid.nix
@@ -1036,6 +1039,7 @@
   ./services/x11/display-managers/sddm.nix
   ./services/x11/display-managers/slim.nix
   ./services/x11/display-managers/startx.nix
+  ./services/x11/display-managers/sx.nix
   ./services/x11/display-managers/xpra.nix
   ./services/x11/fractalart.nix
   ./services/x11/hardware/libinput.nix
diff --git a/nixos/modules/programs/calls.nix b/nixos/modules/programs/calls.nix
new file mode 100644
index 000000000000..59961625e5d9
--- /dev/null
+++ b/nixos/modules/programs/calls.nix
@@ -0,0 +1,25 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.programs.calls;
+in {
+  options = {
+    programs.calls = {
+      enable = mkEnableOption ''
+        Whether to enable GNOME calls: a phone dialer and call handler.
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+    environment.systemPackages = [
+      pkgs.calls
+    ];
+
+    services.dbus.packages = [
+      pkgs.callaudiod
+    ];
+  };
+}
diff --git a/nixos/modules/programs/captive-browser.nix b/nixos/modules/programs/captive-browser.nix
index 007b0369ec10..d7684d08c6c7 100644
--- a/nixos/modules/programs/captive-browser.nix
+++ b/nixos/modules/programs/captive-browser.nix
@@ -85,18 +85,18 @@ in
 
     programs.captive-browser.dhcp-dns =
       let
-        iface = prefix:
-          optionalString cfg.bindInterface (concatStringsSep " " (map escapeShellArg [ prefix cfg.interface ]));
+        iface = prefixes:
+          optionalString cfg.bindInterface (escapeShellArgs (prefixes ++ [ cfg.interface ]));
       in
       mkOptionDefault (
         if config.networking.networkmanager.enable then
-          "${pkgs.networkmanager}/bin/nmcli dev show ${iface ""} | ${pkgs.gnugrep}/bin/fgrep IP4.DNS"
+          "${pkgs.networkmanager}/bin/nmcli dev show ${iface []} | ${pkgs.gnugrep}/bin/fgrep IP4.DNS"
         else if config.networking.dhcpcd.enable then
-          "${pkgs.dhcpcd}/bin/dhcpcd ${iface "-U"} | ${pkgs.gnugrep}/bin/fgrep domain_name_servers"
+          "${pkgs.dhcpcd}/bin/dhcpcd ${iface ["-U"]} | ${pkgs.gnugrep}/bin/fgrep domain_name_servers"
         else if config.networking.useNetworkd then
-          "${cfg.package}/bin/systemd-networkd-dns ${iface ""}"
+          "${cfg.package}/bin/systemd-networkd-dns ${iface []}"
         else
-          "${config.security.wrapperDir}/udhcpc --quit --now -f ${iface "-i"} -O dns --script ${
+          "${config.security.wrapperDir}/udhcpc --quit --now -f ${iface ["-i"]} -O dns --script ${
           pkgs.writeShellScript "udhcp-script" ''
             if [ "$1" = bound ]; then
               echo "$dns"
diff --git a/nixos/modules/security/apparmor/includes.nix b/nixos/modules/security/apparmor/includes.nix
index e3dd410b3bb5..f290e95a296d 100644
--- a/nixos/modules/security/apparmor/includes.nix
+++ b/nixos/modules/security/apparmor/includes.nix
@@ -95,7 +95,7 @@ config.security.apparmor.includes = {
      include "${pkgs.apparmor-profiles}/etc/apparmor.d/abstractions/consoles"
   '';
   "abstractions/cups-client" = ''
-    include "${pkgs.apparmor-profiles}/etc/apparmor.d/abstractions/cpus-client"
+    include "${pkgs.apparmor-profiles}/etc/apparmor.d/abstractions/cups-client"
     ${etcRule "cups/cups-client.conf"}
   '';
   "abstractions/dbus-session-strict" = ''
diff --git a/nixos/modules/services/amqp/rabbitmq.nix b/nixos/modules/services/amqp/rabbitmq.nix
index fc8a1bc3c23c..8fdfda9a66d8 100644
--- a/nixos/modules/services/amqp/rabbitmq.nix
+++ b/nixos/modules/services/amqp/rabbitmq.nix
@@ -7,12 +7,13 @@ let
 
   inherit (builtins) concatStringsSep;
 
-  config_file_content = lib.generators.toKeyValue {} cfg.configItems;
+  config_file_content = lib.generators.toKeyValue { } cfg.configItems;
   config_file = pkgs.writeText "rabbitmq.conf" config_file_content;
 
   advanced_config_file = pkgs.writeText "advanced.config" cfg.config;
 
-in {
+in
+{
   ###### interface
   options = {
     services.rabbitmq = {
@@ -79,7 +80,7 @@ in {
       };
 
       configItems = mkOption {
-        default = {};
+        default = { };
         type = types.attrsOf types.str;
         example = literalExample ''
           {
@@ -123,16 +124,38 @@ in {
       };
 
       plugins = mkOption {
-        default = [];
+        default = [ ];
         type = types.listOf types.str;
         description = "The names of plugins to enable";
       };
 
       pluginDirs = mkOption {
-        default = [];
+        default = [ ];
         type = types.listOf types.path;
         description = "The list of directories containing external plugins";
       };
+
+      managementPlugin = mkOption {
+        description = "The options to run the management plugin";
+        type = types.submodule {
+          options = {
+            enable = mkOption {
+              default = false;
+              type = types.bool;
+              description = ''
+                Whether to enable the management plugin
+              '';
+            };
+            port = mkOption {
+              default = 15672;
+              type = types.port;
+              description = ''
+                On which port to run the management plugin
+              '';
+            };
+          };
+        };
+      };
     };
   };
 
@@ -157,8 +180,13 @@ in {
 
     services.rabbitmq.configItems = {
       "listeners.tcp.1" = mkDefault "${cfg.listenAddress}:${toString cfg.port}";
+    } // optionalAttrs cfg.managementPlugin.enable {
+      "management.tcp.port" = toString cfg.managementPlugin.port;
+      "management.tcp.ip" = cfg.listenAddress;
     };
 
+    services.rabbitmq.plugins = optional cfg.managementPlugin.enable "rabbitmq_management";
+
     systemd.services.rabbitmq = {
       description = "RabbitMQ Server";
 
@@ -180,7 +208,7 @@ in {
         RABBITMQ_ENABLED_PLUGINS_FILE = pkgs.writeText "enabled_plugins" ''
           [ ${concatStringsSep "," cfg.plugins} ].
         '';
-      } //  optionalAttrs (cfg.config != "") { RABBITMQ_ADVANCED_CONFIG_FILE = advanced_config_file; };
+      } // optionalAttrs (cfg.config != "") { RABBITMQ_ADVANCED_CONFIG_FILE = advanced_config_file; };
 
       serviceConfig = {
         ExecStart = "${cfg.package}/sbin/rabbitmq-server";
diff --git a/nixos/modules/services/audio/roon-bridge.nix b/nixos/modules/services/audio/roon-bridge.nix
index 85273a2039c3..e08f8a4f9e7a 100644
--- a/nixos/modules/services/audio/roon-bridge.nix
+++ b/nixos/modules/services/audio/roon-bridge.nix
@@ -14,9 +14,6 @@ in {
         default = false;
         description = ''
           Open ports in the firewall for the bridge.
-
-          UDP: 9003
-          TCP: 9100 - 9200
         '';
       };
       user = mkOption {
@@ -54,10 +51,15 @@ in {
     };
 
     networking.firewall = mkIf cfg.openFirewall {
-      allowedTCPPortRanges = [
-        { from = 9100; to = 9200; }
-      ];
+      allowedTCPPortRanges = [{ from = 9100; to = 9200; }];
       allowedUDPPorts = [ 9003 ];
+      extraCommands = ''
+        iptables -A INPUT -s 224.0.0.0/4 -j ACCEPT
+        iptables -A INPUT -d 224.0.0.0/4 -j ACCEPT
+        iptables -A INPUT -s 240.0.0.0/5 -j ACCEPT
+        iptables -A INPUT -m pkttype --pkt-type multicast -j ACCEPT
+        iptables -A INPUT -m pkttype --pkt-type broadcast -j ACCEPT
+      '';
     };
 
 
diff --git a/nixos/modules/services/audio/roon-server.nix b/nixos/modules/services/audio/roon-server.nix
index eceb65044c5b..42da5a100170 100644
--- a/nixos/modules/services/audio/roon-server.nix
+++ b/nixos/modules/services/audio/roon-server.nix
@@ -14,9 +14,6 @@ in {
         default = false;
         description = ''
           Open ports in the firewall for the server.
-
-          UDP: 9003
-          TCP: 9100 - 9200
         '';
       };
       user = mkOption {
@@ -54,10 +51,15 @@ in {
     };
 
     networking.firewall = mkIf cfg.openFirewall {
-      allowedTCPPortRanges = [
-        { from = 9100; to = 9200; }
-      ];
+      allowedTCPPortRanges = [{ from = 9100; to = 9200; }];
       allowedUDPPorts = [ 9003 ];
+      extraCommands = ''
+        iptables -A INPUT -s 224.0.0.0/4 -j ACCEPT
+        iptables -A INPUT -d 224.0.0.0/4 -j ACCEPT
+        iptables -A INPUT -s 240.0.0.0/5 -j ACCEPT
+        iptables -A INPUT -m pkttype --pkt-type multicast -j ACCEPT
+        iptables -A INPUT -m pkttype --pkt-type broadcast -j ACCEPT
+      '';
     };
 
 
diff --git a/nixos/modules/services/cluster/kubernetes/addon-manager.nix b/nixos/modules/services/cluster/kubernetes/addon-manager.nix
index 1378b5ccfb7a..821f1aa54604 100644
--- a/nixos/modules/services/cluster/kubernetes/addon-manager.nix
+++ b/nixos/modules/services/cluster/kubernetes/addon-manager.nix
@@ -84,6 +84,9 @@ in
         Restart = "on-failure";
         RestartSec = 10;
       };
+      unitConfig = {
+        StartLimitIntervalSec = 0;
+      };
     };
 
     services.kubernetes.addonManager.bootstrapAddons = mkIf isRBACEnabled
diff --git a/nixos/modules/services/cluster/kubernetes/apiserver.nix b/nixos/modules/services/cluster/kubernetes/apiserver.nix
index f842f784b349..2c89310beb5a 100644
--- a/nixos/modules/services/cluster/kubernetes/apiserver.nix
+++ b/nixos/modules/services/cluster/kubernetes/apiserver.nix
@@ -398,6 +398,10 @@ in
             Restart = "on-failure";
             RestartSec = 5;
           };
+
+          unitConfig = {
+            StartLimitIntervalSec = 0;
+          };
         };
 
         services.etcd = {
diff --git a/nixos/modules/services/cluster/kubernetes/controller-manager.nix b/nixos/modules/services/cluster/kubernetes/controller-manager.nix
index 0c81fa9ae492..7128b5f70b1a 100644
--- a/nixos/modules/services/cluster/kubernetes/controller-manager.nix
+++ b/nixos/modules/services/cluster/kubernetes/controller-manager.nix
@@ -146,6 +146,9 @@ in
         User = "kubernetes";
         Group = "kubernetes";
       };
+      unitConfig = {
+        StartLimitIntervalSec = 0;
+      };
       path = top.path;
     };
 
diff --git a/nixos/modules/services/cluster/kubernetes/kubelet.nix b/nixos/modules/services/cluster/kubernetes/kubelet.nix
index fcfcc8435477..08f5cdfdf334 100644
--- a/nixos/modules/services/cluster/kubernetes/kubelet.nix
+++ b/nixos/modules/services/cluster/kubernetes/kubelet.nix
@@ -337,6 +337,9 @@ in
           '';
           WorkingDirectory = top.dataDir;
         };
+        unitConfig = {
+          StartLimitIntervalSec = 0;
+        };
       };
 
       # Allways include cni plugins
diff --git a/nixos/modules/services/cluster/kubernetes/proxy.nix b/nixos/modules/services/cluster/kubernetes/proxy.nix
index 42729f54643b..a92043d52597 100644
--- a/nixos/modules/services/cluster/kubernetes/proxy.nix
+++ b/nixos/modules/services/cluster/kubernetes/proxy.nix
@@ -77,6 +77,9 @@ in
         Restart = "on-failure";
         RestartSec = 5;
       };
+      unitConfig = {
+        StartLimitIntervalSec = 0;
+      };
     };
 
     services.kubernetes.proxy.hostname = with config.networking; mkDefault hostName;
diff --git a/nixos/modules/services/cluster/kubernetes/scheduler.nix b/nixos/modules/services/cluster/kubernetes/scheduler.nix
index 454c689759df..1b0c22a11426 100644
--- a/nixos/modules/services/cluster/kubernetes/scheduler.nix
+++ b/nixos/modules/services/cluster/kubernetes/scheduler.nix
@@ -79,6 +79,9 @@ in
         Restart = "on-failure";
         RestartSec = 5;
       };
+      unitConfig = {
+        StartLimitIntervalSec = 0;
+      };
     };
 
     services.kubernetes.pki.certs = {
diff --git a/nixos/modules/services/continuous-integration/github-runner.nix b/nixos/modules/services/continuous-integration/github-runner.nix
index 9627b723f8f1..f951c1553235 100644
--- a/nixos/modules/services/continuous-integration/github-runner.nix
+++ b/nixos/modules/services/continuous-integration/github-runner.nix
@@ -98,6 +98,14 @@ in
       '';
       default = [ ];
     };
+
+    package = mkOption {
+      type = types.package;
+      description = ''
+        Which github-runner derivation to use.
+      '';
+      default = pkgs.github-runner;
+    };
   };
 
   config = mkIf cfg.enable {
@@ -131,7 +139,7 @@ in
       ] ++ cfg.extraPackages;
 
       serviceConfig = rec {
-        ExecStart = "${pkgs.github-runner}/bin/runsvc.sh";
+        ExecStart = "${cfg.package}/bin/runsvc.sh";
 
         # Does the following, sequentially:
         # - Copy the current and the previous `tokenFile` to the $RUNTIME_DIRECTORY
@@ -208,7 +216,7 @@ in
               if [[ -z "$empty" ]]; then
                 echo "Configuring GitHub Actions Runner"
                 token=$(< "$RUNTIME_DIRECTORY"/${newConfigTokenFilename})
-                RUNNER_ROOT="$STATE_DIRECTORY" ${pkgs.github-runner}/bin/config.sh \
+                RUNNER_ROOT="$STATE_DIRECTORY" ${cfg.package}/bin/config.sh \
                   --unattended \
                   --work "$RUNTIME_DIRECTORY" \
                   --url ${escapeShellArg cfg.url} \
diff --git a/nixos/modules/services/desktops/pipewire/bluez-hardware.conf.json b/nixos/modules/services/desktops/pipewire/bluez-hardware.conf.json
index cae9e1bdba06..46697ece4483 100644
--- a/nixos/modules/services/desktops/pipewire/bluez-hardware.conf.json
+++ b/nixos/modules/services/desktops/pipewire/bluez-hardware.conf.json
@@ -198,6 +198,17 @@
     },
     {
       "sysname": "Linux",
+      "release": "~^5\\.10\\.(1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31|32|33|34|35|36|37|38|39|40|41|42|43|44|45|46|47|48|49|50)($|[^0-9])"
+    },
+    {
+      "sysname": "Linux",
+      "release": "~^5\\.10\\.",
+      "no-features": [
+        "msbc-alt1"
+      ]
+    },
+    {
+      "sysname": "Linux",
       "release": "~^5\\.12\\.(1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|16|17)($|[^0-9])"
     },
     {
diff --git a/nixos/modules/services/desktops/pipewire/pipewire-pulse.conf.json b/nixos/modules/services/desktops/pipewire/pipewire-pulse.conf.json
index 17bbbdef1179..3ed994f11145 100644
--- a/nixos/modules/services/desktops/pipewire/pipewire-pulse.conf.json
+++ b/nixos/modules/services/desktops/pipewire/pipewire-pulse.conf.json
@@ -37,5 +37,6 @@
       }
     }
   ],
+  "context.exec": [],
   "stream.properties": {}
 }
diff --git a/nixos/modules/services/mail/dovecot.nix b/nixos/modules/services/mail/dovecot.nix
index bac437c752dc..f3500f46e355 100644
--- a/nixos/modules/services/mail/dovecot.nix
+++ b/nixos/modules/services/mail/dovecot.nix
@@ -429,6 +429,7 @@ in
 
       startLimitIntervalSec = 60;  # 1 min
       serviceConfig = {
+        Type = "notify";
         ExecStart = "${dovecotPkg}/sbin/dovecot -F";
         ExecReload = "${dovecotPkg}/sbin/doveadm reload";
         Restart = "on-failure";
diff --git a/nixos/modules/services/mail/postfixadmin.nix b/nixos/modules/services/mail/postfixadmin.nix
new file mode 100644
index 000000000000..f5c8efb3076c
--- /dev/null
+++ b/nixos/modules/services/mail/postfixadmin.nix
@@ -0,0 +1,199 @@
+{ lib, config, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.postfixadmin;
+  fpm = config.services.phpfpm.pools.postfixadmin;
+  localDB = cfg.database.host == "localhost";
+  user = if localDB then cfg.database.username else "nginx";
+in
+{
+  options.services.postfixadmin = {
+    enable = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Whether to enable postfixadmin.
+
+        Also enables nginx virtual host management.
+        Further nginx configuration can be done by adapting <literal>services.nginx.virtualHosts.&lt;name&gt;</literal>.
+        See <xref linkend="opt-services.nginx.virtualHosts"/> for further information.
+      '';
+    };
+
+    hostName = mkOption {
+      type = types.str;
+      example = "postfixadmin.example.com";
+      description = "Hostname to use for the nginx vhost";
+    };
+
+    adminEmail = mkOption {
+      type = types.str;
+      example = "postmaster@example.com";
+      description = ''
+        Defines the Site Admin's email address.
+        This will be used to send emails from to create mailboxes and
+        from Send Email / Broadcast message pages.
+      '';
+    };
+
+    setupPasswordFile = mkOption {
+      type = types.path;
+      description = ''
+        Password file for the admin.
+        Generate with <literal>php -r "echo password_hash('some password here', PASSWORD_DEFAULT);"</literal>
+      '';
+    };
+
+    database = {
+      username = mkOption {
+        type = types.str;
+        default = "postfixadmin";
+        description = ''
+          Username for the postgresql connection.
+          If <literal>database.host</literal> is set to <literal>localhost</literal>, a unix user and group of the same name will be created as well.
+        '';
+      };
+      host = mkOption {
+        type = types.str;
+        default = "localhost";
+        description = ''
+          Host of the postgresql server. If this is not set to
+          <literal>localhost</literal>, you have to create the
+          postgresql user and database yourself, with appropriate
+          permissions.
+        '';
+      };
+      passwordFile = mkOption {
+        type = types.path;
+        description = "Password file for the postgresql connection. Must be readable by user <literal>nginx</literal>.";
+      };
+      dbname = mkOption {
+        type = types.str;
+        default = "postfixadmin";
+        description = "Name of the postgresql database";
+      };
+    };
+
+    extraConfig = mkOption {
+      type = types.lines;
+      default = "";
+      description = "Extra configuration for the postfixadmin instance, see postfixadmin's config.inc.php for available options.";
+    };
+  };
+
+  config = mkIf cfg.enable {
+    environment.etc."postfixadmin/config.local.php".text = ''
+      <?php
+
+      $CONF['setup_password'] = file_get_contents('${cfg.setupPasswordFile}');
+
+      $CONF['database_type'] = 'pgsql';
+      $CONF['database_host'] = ${if localDB then "null" else "'${cfg.database.host}'"};
+      ${optionalString localDB "$CONF['database_user'] = '${cfg.database.username}';"}
+      $CONF['database_password'] = ${if localDB then "'dummy'" else "file_get_contents('${cfg.database.passwordFile}')"};
+      $CONF['database_name'] = '${cfg.database.dbname}';
+      $CONF['configured'] = true;
+
+      ${cfg.extraConfig}
+    '';
+
+    systemd.tmpfiles.rules = [ "d /var/cache/postfixadmin/templates_c 700 ${user} ${user}" ];
+
+    services.nginx = {
+      enable = true;
+      virtualHosts = {
+        ${cfg.hostName} = {
+          forceSSL = mkDefault true;
+          enableACME = mkDefault true;
+          locations."/" = {
+            root = "${pkgs.postfixadmin}/public";
+            index = "index.php";
+            extraConfig = ''
+              location ~* \.php$ {
+                fastcgi_split_path_info ^(.+\.php)(/.+)$;
+                fastcgi_pass unix:${fpm.socket};
+                include ${pkgs.nginx}/conf/fastcgi_params;
+                include ${pkgs.nginx}/conf/fastcgi.conf;
+              }
+            '';
+          };
+        };
+      };
+    };
+
+    services.postgresql = mkIf localDB {
+      enable = true;
+      ensureUsers = [ {
+        name = cfg.database.username;
+      } ];
+    };
+    # The postgresql module doesn't currently support concepts like
+    # objects owners and extensions; for now we tack on what's needed
+    # here.
+    systemd.services.postfixadmin-postgres = let pgsql = config.services.postgresql; in mkIf localDB {
+      after = [ "postgresql.service" ];
+      bindsTo = [ "postgresql.service" ];
+      wantedBy = [ "multi-user.target" ];
+      path = [
+        pgsql.package
+        pkgs.util-linux
+      ];
+      script = ''
+        set -eu
+
+        PSQL() {
+            psql --port=${toString pgsql.port} "$@"
+        }
+
+        PSQL -tAc "SELECT 1 FROM pg_database WHERE datname = '${cfg.database.dbname}'" | grep -q 1 || PSQL -tAc 'CREATE DATABASE "${cfg.database.dbname}" OWNER "${cfg.database.username}"'
+        current_owner=$(PSQL -tAc "SELECT pg_catalog.pg_get_userbyid(datdba) FROM pg_catalog.pg_database WHERE datname = '${cfg.database.dbname}'")
+        if [[ "$current_owner" != "${cfg.database.username}" ]]; then
+            PSQL -tAc 'ALTER DATABASE "${cfg.database.dbname}" OWNER TO "${cfg.database.username}"'
+            if [[ -e "${config.services.postgresql.dataDir}/.reassigning_${cfg.database.dbname}" ]]; then
+                echo "Reassigning ownership of database ${cfg.database.dbname} to user ${cfg.database.username} failed on last boot. Failing..."
+                exit 1
+            fi
+            touch "${config.services.postgresql.dataDir}/.reassigning_${cfg.database.dbname}"
+            PSQL "${cfg.database.dbname}" -tAc "REASSIGN OWNED BY \"$current_owner\" TO \"${cfg.database.username}\""
+            rm "${config.services.postgresql.dataDir}/.reassigning_${cfg.database.dbname}"
+        fi
+      '';
+
+      serviceConfig = {
+        User = pgsql.superUser;
+        Type = "oneshot";
+        RemainAfterExit = true;
+      };
+    };
+
+    users.users.${user} = mkIf localDB {
+      group = user;
+      isSystemUser = true;
+      createHome = false;
+    };
+    users.groups.${user} = mkIf localDB {};
+
+    services.phpfpm.pools.postfixadmin = {
+      user = user;
+      phpPackage = pkgs.php74;
+      phpOptions = ''
+        error_log = 'stderr'
+        log_errors = on
+      '';
+      settings = mapAttrs (name: mkDefault) {
+        "listen.owner" = "nginx";
+        "listen.group" = "nginx";
+        "listen.mode" = "0660";
+        "pm" = "dynamic";
+        "pm.max_children" = 75;
+        "pm.start_servers" = 2;
+        "pm.min_spare_servers" = 1;
+        "pm.max_spare_servers" = 20;
+        "pm.max_requests" = 500;
+        "catch_workers_output" = true;
+      };
+    };
+  };
+}
diff --git a/nixos/modules/services/misc/airsonic.nix b/nixos/modules/services/misc/airsonic.nix
index a572f1f6d6f5..490f6c5a5c06 100644
--- a/nixos/modules/services/misc/airsonic.nix
+++ b/nixos/modules/services/misc/airsonic.nix
@@ -82,6 +82,25 @@ in {
         '';
       };
 
+      jre = mkOption {
+        type = types.package;
+        default = pkgs.jre8;
+        defaultText = literalExample "pkgs.jre8";
+        description = ''
+          JRE package to use.
+
+          Airsonic only supports Java 8, airsonic-advanced requires at least
+          Java 11.
+        '';
+      };
+
+      war = mkOption {
+        type = types.path;
+        default = "${pkgs.airsonic}/webapps/airsonic.war";
+        defaultText = "\${pkgs.airsonic}/webapps/airsonic.war";
+        description = "Airsonic war file to use.";
+      };
+
       jvmOptions = mkOption {
         description = ''
           Extra command line options for the JVM running AirSonic.
@@ -118,7 +137,7 @@ in {
       '';
       serviceConfig = {
         ExecStart = ''
-          ${pkgs.jre8}/bin/java -Xmx${toString cfg.maxMemory}m \
+          ${cfg.jre}/bin/java -Xmx${toString cfg.maxMemory}m \
           -Dairsonic.home=${cfg.home} \
           -Dserver.address=${cfg.listenAddress} \
           -Dserver.port=${toString cfg.port} \
@@ -128,7 +147,7 @@ in {
             "-Dserver.use-forward-headers=true"} \
           ${toString cfg.jvmOptions} \
           -verbose:gc \
-          -jar ${pkgs.airsonic}/webapps/airsonic.war
+          -jar ${cfg.war}
         '';
         Restart = "always";
         User = "airsonic";
diff --git a/nixos/modules/services/misc/gitlab.nix b/nixos/modules/services/misc/gitlab.nix
index 1514cc0665df..805deeee0c04 100644
--- a/nixos/modules/services/misc/gitlab.nix
+++ b/nixos/modules/services/misc/gitlab.nix
@@ -117,6 +117,7 @@ let
       shared.path = "${cfg.statePath}/shared";
       gitaly.client_path = "${cfg.packages.gitaly}/bin";
       backup = {
+        gitaly_backup_path = "${cfg.packages.gitaly}/bin/gitaly-backup";
         path = cfg.backup.path;
         keep_time = cfg.backup.keepTime;
       } // (optionalAttrs (cfg.backup.uploadOptions != {}) {
@@ -1299,7 +1300,7 @@ in {
         Restart = "on-failure";
         WorkingDirectory = gitlabEnv.HOME;
         ExecStart =
-          "${cfg.packages.gitlab-workhorse}/bin/gitlab-workhorse "
+          "${cfg.packages.gitlab-workhorse}/bin/workhorse "
           + "-listenUmask 0 "
           + "-listenNetwork unix "
           + "-listenAddr /run/gitlab/gitlab-workhorse.socket "
@@ -1352,9 +1353,8 @@ in {
         procps
         gnupg
       ];
-
       serviceConfig = {
-        Type = "simple";
+        Type = "notify";
         User = cfg.user;
         Group = cfg.group;
         TimeoutSec = "infinity";
diff --git a/nixos/modules/services/misc/home-assistant.nix b/nixos/modules/services/misc/home-assistant.nix
index f1a1a88580eb..73ec3b9a17a2 100644
--- a/nixos/modules/services/misc/home-assistant.nix
+++ b/nixos/modules/services/misc/home-assistant.nix
@@ -285,6 +285,7 @@ in {
           "alarmdecoder"
           "arduino"
           "blackbird"
+          "deconz"
           "dsmr"
           "edl21"
           "elkm1"
diff --git a/nixos/modules/services/misc/mautrix-telegram.nix b/nixos/modules/services/misc/mautrix-telegram.nix
index 0ae5797fea04..717cf7936ead 100644
--- a/nixos/modules/services/misc/mautrix-telegram.nix
+++ b/nixos/modules/services/misc/mautrix-telegram.nix
@@ -128,7 +128,7 @@ in {
         # https://github.com/tulir/mautrix-telegram/issues/584
         [ -f ${settingsFile} ] && rm -f ${settingsFile}
         old_umask=$(umask)
-        umask 0277
+        umask 0177
         ${pkgs.envsubst}/bin/envsubst \
           -o ${settingsFile} \
           -i ${settingsFileUnsubstituted}
diff --git a/nixos/modules/services/misc/nzbget.nix b/nixos/modules/services/misc/nzbget.nix
index 715ec891cd68..27c5f2e395f6 100644
--- a/nixos/modules/services/misc/nzbget.nix
+++ b/nixos/modules/services/misc/nzbget.nix
@@ -7,24 +7,12 @@ let
   pkg = pkgs.nzbget;
   stateDir = "/var/lib/nzbget";
   configFile = "${stateDir}/nzbget.conf";
-  configOpts = concatStringsSep " " (mapAttrsToList (name: value: "-o ${name}=${value}") nixosOpts);
-
-  nixosOpts = {
-    # allows nzbget to run as a "simple" service
-    OutputMode = "loggable";
-    # use journald for logging
-    WriteLog = "none";
-    ErrorTarget = "screen";
-    WarningTarget = "screen";
-    InfoTarget = "screen";
-    DetailTarget = "screen";
-    # required paths
-    ConfigTemplate = "${pkg}/share/nzbget/nzbget.conf";
-    WebDir = "${pkg}/share/nzbget/webui";
-    # nixos handles package updates
-    UpdateCheck = "none";
-  };
-
+  configOpts = concatStringsSep " " (mapAttrsToList (name: value: "-o ${name}=${escapeShellArg (toStr value)}") cfg.settings);
+  toStr = v:
+    if v == true then "yes"
+    else if v == false then "no"
+    else if isInt v then toString v
+    else v;
 in
 {
   imports = [
@@ -50,12 +38,41 @@ in
         default = "nzbget";
         description = "Group under which NZBGet runs";
       };
+
+      settings = mkOption {
+        type = with types; attrsOf (oneOf [ bool int str ]);
+        default = {};
+        description = ''
+          NZBGet configuration, passed via command line using switch -o. Refer to
+          <link xlink:href="https://github.com/nzbget/nzbget/blob/master/nzbget.conf"/>
+          for details on supported values.
+        '';
+        example = {
+          MainDir = "/data";
+        };
+      };
     };
   };
 
   # implementation
 
   config = mkIf cfg.enable {
+    services.nzbget.settings = {
+      # allows nzbget to run as a "simple" service
+      OutputMode = "loggable";
+      # use journald for logging
+      WriteLog = "none";
+      ErrorTarget = "screen";
+      WarningTarget = "screen";
+      InfoTarget = "screen";
+      DetailTarget = "screen";
+      # required paths
+      ConfigTemplate = "${pkg}/share/nzbget/nzbget.conf";
+      WebDir = "${pkg}/share/nzbget/webui";
+      # nixos handles package updates
+      UpdateCheck = "none";
+    };
+
     systemd.services.nzbget = {
       description = "NZBGet Daemon";
       after = [ "network.target" ];
@@ -64,6 +81,7 @@ in
         unrar
         p7zip
       ];
+
       preStart = ''
         if [ ! -f ${configFile} ]; then
           ${pkgs.coreutils}/bin/install -m 0700 ${pkg}/share/nzbget/nzbget.conf ${configFile}
diff --git a/nixos/modules/services/misc/octoprint.nix b/nixos/modules/services/misc/octoprint.nix
index c926d889b37a..7129ac69527f 100644
--- a/nixos/modules/services/misc/octoprint.nix
+++ b/nixos/modules/services/misc/octoprint.nix
@@ -122,6 +122,9 @@ in
         ExecStart = "${pluginsEnv}/bin/octoprint serve -b ${cfg.stateDir}";
         User = cfg.user;
         Group = cfg.group;
+        SupplementaryGroups = [
+          "dialout"
+        ];
       };
     };
 
diff --git a/nixos/modules/services/misc/paperless-ng.nix b/nixos/modules/services/misc/paperless-ng.nix
index 9eaf8fa8859c..4b7087e17f96 100644
--- a/nixos/modules/services/misc/paperless-ng.nix
+++ b/nixos/modules/services/misc/paperless-ng.nix
@@ -29,6 +29,7 @@ let
       "-/etc/nsswitch.conf"
       "-/etc/hosts"
       "-/etc/localtime"
+      "-/run/postgresql"
     ];
     BindPaths = [
       cfg.consumptionDir
@@ -60,7 +61,7 @@ let
     ProtectKernelModules = true;
     ProtectKernelTunables = true;
     ProtectProc = "invisible";
-    RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ];
+    RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" ];
     RestrictNamespaces = true;
     RestrictRealtime = true;
     RestrictSUIDSGID = true;
@@ -283,6 +284,9 @@ in
         PATH = mkForce cfg.package.path;
         PYTHONPATH = "${cfg.package.pythonPath}:${cfg.package}/lib/paperless-ng/src";
       };
+      # Allow the web interface to access the private /tmp directory of the server.
+      # This is required to support uploading files via the web interface.
+      unitConfig.JoinsNamespaceOf = "paperless-ng-server.service";
       # Bind to `paperless-ng-server` so that the web server never runs
       # during migrations
       bindsTo = [ "paperless-ng-server.service" ];
diff --git a/nixos/modules/services/monitoring/grafana.nix b/nixos/modules/services/monitoring/grafana.nix
index e0b2624b6cac..fb67bbfb8420 100644
--- a/nixos/modules/services/monitoring/grafana.nix
+++ b/nixos/modules/services/monitoring/grafana.nix
@@ -6,6 +6,8 @@ let
   cfg = config.services.grafana;
   opt = options.services.grafana;
   declarativePlugins = pkgs.linkFarm "grafana-plugins" (builtins.map (pkg: { name = pkg.pname; path = pkg; }) cfg.declarativePlugins);
+  useMysql = cfg.database.type == "mysql";
+  usePostgresql = cfg.database.type == "postgres";
 
   envOptions = {
     PATHS_DATA = cfg.dataDir;
@@ -635,7 +637,7 @@ in {
     systemd.services.grafana = {
       description = "Grafana Service Daemon";
       wantedBy = ["multi-user.target"];
-      after = ["networking.target"];
+      after = ["networking.target"] ++ lib.optional usePostgresql "postgresql.service" ++ lib.optional useMysql "mysql.service";
       environment = {
         QT_QPA_PLATFORM = "offscreen";
       } // mapAttrs' (n: v: nameValuePair "GF_${n}" (toString v)) envOptions;
diff --git a/nixos/modules/services/network-filesystems/ipfs.nix b/nixos/modules/services/network-filesystems/ipfs.nix
index 2748571be1f7..57f5f6b006c8 100644
--- a/nixos/modules/services/network-filesystems/ipfs.nix
+++ b/nixos/modules/services/network-filesystems/ipfs.nix
@@ -5,36 +5,41 @@ let
   opt = options.services.ipfs;
 
   ipfsFlags = toString ([
-    (optionalString  cfg.autoMount                   "--mount")
-    (optionalString  cfg.enableGC                    "--enable-gc")
-    (optionalString (cfg.serviceFdlimit != null)     "--manage-fdlimit=false")
-    (optionalString (cfg.defaultMode == "offline")   "--offline")
+    (optionalString cfg.autoMount "--mount")
+    (optionalString cfg.enableGC "--enable-gc")
+    (optionalString (cfg.serviceFdlimit != null) "--manage-fdlimit=false")
+    (optionalString (cfg.defaultMode == "offline") "--offline")
     (optionalString (cfg.defaultMode == "norouting") "--routing=none")
   ] ++ cfg.extraFlags);
 
   splitMulitaddr = addrRaw: lib.tail (lib.splitString "/" addrRaw);
 
-  multiaddrToListenStream = addrRaw: let
+  multiaddrToListenStream = addrRaw:
+    let
       addr = splitMulitaddr addrRaw;
       s = builtins.elemAt addr;
-    in if s 0 == "ip4" && s 2 == "tcp"
-      then "${s 1}:${s 3}"
+    in
+    if s 0 == "ip4" && s 2 == "tcp"
+    then "${s 1}:${s 3}"
     else if s 0 == "ip6" && s 2 == "tcp"
-      then "[${s 1}]:${s 3}"
+    then "[${s 1}]:${s 3}"
     else if s 0 == "unix"
-      then "/${lib.concatStringsSep "/" (lib.tail addr)}"
+    then "/${lib.concatStringsSep "/" (lib.tail addr)}"
     else null; # not valid for listen stream, skip
 
-  multiaddrToListenDatagram = addrRaw: let
+  multiaddrToListenDatagram = addrRaw:
+    let
       addr = splitMulitaddr addrRaw;
       s = builtins.elemAt addr;
-    in if s 0 == "ip4" && s 2 == "udp"
-      then "${s 1}:${s 3}"
+    in
+    if s 0 == "ip4" && s 2 == "udp"
+    then "${s 1}:${s 3}"
     else if s 0 == "ip6" && s 2 == "udp"
-      then "[${s 1}]:${s 3}"
+    then "[${s 1}]:${s 3}"
     else null; # not valid for listen datagram, skip
 
-in {
+in
+{
 
   ###### interface
 
@@ -65,9 +70,10 @@ in {
 
       dataDir = mkOption {
         type = types.str;
-        default = if versionAtLeast config.system.stateVersion "17.09"
-                  then "/var/lib/ipfs"
-                  else "/var/lib/ipfs/.ipfs";
+        default =
+          if versionAtLeast config.system.stateVersion "17.09"
+          then "/var/lib/ipfs"
+          else "/var/lib/ipfs/.ipfs";
         description = "The data dir for IPFS";
       };
 
@@ -83,6 +89,12 @@ in {
         description = "Whether IPFS should try to mount /ipfs and /ipns at startup.";
       };
 
+      autoMigrate = mkOption {
+        type = types.bool;
+        default = true;
+        description = "Whether IPFS should try to run the fs-repo-migration at startup.";
+      };
+
       ipfsMountDir = mkOption {
         type = types.str;
         default = "/ipfs";
@@ -137,7 +149,7 @@ in {
           These are applied last, so may override configuration set by other options in this module.
           Keep in mind that this configuration is stateful; i.e., unsetting anything in here does not reset the value to the default!
         '';
-        default = {};
+        default = { };
         example = {
           Datastore.StorageMax = "100GB";
           Discovery.MDNS.Enabled = false;
@@ -153,7 +165,7 @@ in {
       extraFlags = mkOption {
         type = types.listOf types.str;
         description = "Extra flags passed to the IPFS daemon";
-        default = [];
+        default = [ ];
       };
 
       localDiscovery = mkOption {
@@ -168,7 +180,7 @@ in {
         type = types.nullOr types.int;
         default = null;
         description = "The fdlimit for the IPFS systemd unit or <literal>null</literal> to have the daemon attempt to manage it";
-        example = 64*1024;
+        example = 64 * 1024;
       };
 
       startWhenNeeded = mkOption {
@@ -186,6 +198,9 @@ in {
     environment.systemPackages = [ cfg.package ];
     environment.variables.IPFS_PATH = cfg.dataDir;
 
+    # https://github.com/lucas-clemente/quic-go/wiki/UDP-Receive-Buffer-Size
+    boot.kernel.sysctl."net.core.rmem_max" = mkDefault 2500000;
+
     programs.fuse = mkIf cfg.autoMount {
       userAllowOther = true;
     };
@@ -226,33 +241,36 @@ in {
             ${optionalString (! cfg.localDiscovery) "--profile=server"}
         else
           ${if cfg.localDiscovery
-            then "ipfs config profile apply local-discovery"
-            else "ipfs config profile apply server"
+            then "ipfs --offline config profile apply local-discovery"
+            else "ipfs --offline config profile apply server"
           }
         fi
       '' + optionalString cfg.autoMount ''
         ipfs --offline config Mounts.FuseAllowOther --json true
         ipfs --offline config Mounts.IPFS ${cfg.ipfsMountDir}
         ipfs --offline config Mounts.IPNS ${cfg.ipnsMountDir}
+      '' + optionalString cfg.autoMigrate ''
+        ${pkgs.ipfs-migrator}/bin/fs-repo-migrations -y
       '' + concatStringsSep "\n" (collect
-            isString
-            (mapAttrsRecursive
-              (path: value:
-              # Using heredoc below so that the value is never improperly quoted
-              ''
-                read value <<EOF
-                ${builtins.toJSON value}
-                EOF
-                ipfs --offline config --json "${concatStringsSep "." path}" "$value"
-              '')
-              ({ Addresses.API = cfg.apiAddress;
-                 Addresses.Gateway = cfg.gatewayAddress;
-                 Addresses.Swarm = cfg.swarmAddress;
-              } //
-              cfg.extraConfig))
-          );
+        isString
+        (mapAttrsRecursive
+          (path: value:
+            # Using heredoc below so that the value is never improperly quoted
+            ''
+              read value <<EOF
+              ${builtins.toJSON value}
+              EOF
+              ipfs --offline config --json "${concatStringsSep "." path}" "$value"
+            '')
+          ({
+            Addresses.API = cfg.apiAddress;
+            Addresses.Gateway = cfg.gatewayAddress;
+            Addresses.Swarm = cfg.swarmAddress;
+          } //
+          cfg.extraConfig))
+      );
       serviceConfig = {
-        ExecStart = ["" "${cfg.package}/bin/ipfs daemon ${ipfsFlags}"];
+        ExecStart = [ "" "${cfg.package}/bin/ipfs daemon ${ipfsFlags}" ];
         User = cfg.user;
         Group = cfg.group;
       } // optionalAttrs (cfg.serviceFdlimit != null) { LimitNOFILE = cfg.serviceFdlimit; };
@@ -263,12 +281,16 @@ in {
     systemd.sockets.ipfs-gateway = {
       wantedBy = [ "sockets.target" ];
       socketConfig = {
-        ListenStream = let
+        ListenStream =
+          let
             fromCfg = multiaddrToListenStream cfg.gatewayAddress;
-          in [ "" ] ++ lib.optional (fromCfg != null) fromCfg;
-        ListenDatagram = let
+          in
+          [ "" ] ++ lib.optional (fromCfg != null) fromCfg;
+        ListenDatagram =
+          let
             fromCfg = multiaddrToListenDatagram cfg.gatewayAddress;
-          in [ "" ] ++ lib.optional (fromCfg != null) fromCfg;
+          in
+          [ "" ] ++ lib.optional (fromCfg != null) fromCfg;
       };
     };
 
@@ -276,9 +298,11 @@ in {
       wantedBy = [ "sockets.target" ];
       # We also include "%t/ipfs.sock" because there is no way to put the "%t"
       # in the multiaddr.
-      socketConfig.ListenStream = let
+      socketConfig.ListenStream =
+        let
           fromCfg = multiaddrToListenStream cfg.apiAddress;
-        in [ "" "%t/ipfs.sock" ] ++ lib.optional (fromCfg != null) fromCfg;
+        in
+        [ "" "%t/ipfs.sock" ] ++ lib.optional (fromCfg != null) fromCfg;
     };
 
   };
diff --git a/nixos/modules/services/networking/epmd.nix b/nixos/modules/services/networking/epmd.nix
index f7cdc0fe79c0..3899d164f16a 100644
--- a/nixos/modules/services/networking/epmd.nix
+++ b/nixos/modules/services/networking/epmd.nix
@@ -4,9 +4,7 @@ with lib;
 
 let
   cfg = config.services.epmd;
-
 in
-
 {
   ###### interface
   options.services.epmd = {
@@ -27,16 +25,31 @@ in
         an Erlang runtime that is already installed for other purposes.
       '';
     };
+    listenStream = mkOption
+      {
+        type = types.str;
+        default = "[::]:4369";
+        description = ''
+          the listenStream used by the systemd socket.
+          see https://www.freedesktop.org/software/systemd/man/systemd.socket.html#ListenStream= for more informations.
+          use this to change the port epmd will run on.
+          if not defined, epmd will use "[::]:4369"
+        '';
+      };
   };
 
   ###### implementation
   config = mkIf cfg.enable {
+    assertions = [{
+      assertion = cfg.listenStream == "[::]:4369" -> config.networking.enableIPv6;
+      message = "epmd listens by default on ipv6, enable ipv6 or change config.services.epmd.listenStream";
+    }];
     systemd.sockets.epmd = rec {
       description = "Erlang Port Mapper Daemon Activation Socket";
       wantedBy = [ "sockets.target" ];
       before = wantedBy;
       socketConfig = {
-        ListenStream = "4369";
+        ListenStream = cfg.listenStream;
         Accept = "false";
       };
     };
diff --git a/nixos/modules/services/networking/soju.nix b/nixos/modules/services/networking/soju.nix
new file mode 100644
index 000000000000..68a33e9dccba
--- /dev/null
+++ b/nixos/modules/services/networking/soju.nix
@@ -0,0 +1,113 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.soju;
+  stateDir = "/var/lib/soju";
+  listenCfg = concatMapStringsSep "\n" (l: "listen ${l}") cfg.listen;
+  tlsCfg = optionalString (cfg.tlsCertificate != null)
+    "tls ${cfg.tlsCertificate} ${cfg.tlsCertificateKey}";
+  logCfg = optionalString cfg.enableMessageLogging
+    "log fs ${stateDir}/logs";
+
+  configFile = pkgs.writeText "soju.conf" ''
+    ${listenCfg}
+    hostname ${cfg.hostName}
+    ${tlsCfg}
+    db sqlite3 ${stateDir}/soju.db
+    ${logCfg}
+    http-origin ${concatStringsSep " " cfg.httpOrigins}
+    accept-proxy-ip ${concatStringsSep " " cfg.acceptProxyIP}
+
+    ${cfg.extraConfig}
+  '';
+in
+{
+  ###### interface
+
+  options.services.soju = {
+    enable = mkEnableOption "soju";
+
+    listen = mkOption {
+      type = types.listOf types.str;
+      default = [ ":6697" ];
+      description = ''
+        Where soju should listen for incoming connections. See the
+        <literal>listen</literal> directive in
+        <citerefentry><refentrytitle>soju</refentrytitle>
+        <manvolnum>1</manvolnum></citerefentry>.
+      '';
+    };
+
+    hostName = mkOption {
+      type = types.str;
+      default = config.networking.hostName;
+      description = "Server hostname.";
+    };
+
+    tlsCertificate = mkOption {
+      type = types.nullOr types.path;
+      example = "/var/host.cert";
+      description = "Path to server TLS certificate.";
+    };
+
+    tlsCertificateKey = mkOption {
+      type = types.nullOr types.path;
+      example = "/var/host.key";
+      description = "Path to server TLS certificate key.";
+    };
+
+    enableMessageLogging = mkOption {
+      type = types.bool;
+      default = true;
+      description = "Whether to enable message logging.";
+    };
+
+    httpOrigins = mkOption {
+      type = types.listOf types.str;
+      default = [];
+      description = ''
+        List of allowed HTTP origins for WebSocket listeners. The parameters are
+        interpreted as shell patterns, see
+        <citerefentry><refentrytitle>glob</refentrytitle>
+        <manvolnum>7</manvolnum></citerefentry>.
+      '';
+    };
+
+    acceptProxyIP = mkOption {
+      type = types.listOf types.str;
+      default = [];
+      description = ''
+        Allow the specified IPs to act as a proxy. Proxys have the ability to
+        overwrite the remote and local connection addresses (via the X-Forwarded-\*
+        HTTP header fields). The special name "localhost" accepts the loopback
+        addresses 127.0.0.0/8 and ::1/128. By default, all IPs are rejected.
+      '';
+    };
+
+    extraConfig = mkOption {
+      type = types.lines;
+      default = "";
+      description = "Lines added verbatim to the configuration file.";
+    };
+  };
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+    systemd.services.soju = {
+      description = "soju IRC bouncer";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network-online.target" ];
+      serviceConfig = {
+        DynamicUser = true;
+        Restart = "always";
+        ExecStart = "${pkgs.soju}/bin/soju -config ${configFile}";
+        StateDirectory = "soju";
+      };
+    };
+  };
+
+  meta.maintainers = with maintainers; [ malvo ];
+}
diff --git a/nixos/modules/services/networking/syncthing.nix b/nixos/modules/services/networking/syncthing.nix
index 2e92fe51e90a..ebe4d89a0e7f 100644
--- a/nixos/modules/services/networking/syncthing.nix
+++ b/nixos/modules/services/networking/syncthing.nix
@@ -9,7 +9,7 @@ let
 
   devices = mapAttrsToList (name: device: {
     deviceID = device.id;
-    inherit (device) name addresses introducer;
+    inherit (device) name addresses introducer autoAcceptFolders;
   }) cfg.devices;
 
   folders = mapAttrsToList ( _: folder: {
@@ -37,7 +37,7 @@ let
     do sleep 1; done
 
     curl() {
-        ${pkgs.curl}/bin/curl -sS -H "X-API-Key: $api_key" \
+        ${pkgs.curl}/bin/curl -sSLk -H "X-API-Key: $api_key" \
             --retry 1000 --retry-delay 1 --retry-all-errors \
             "$@"
     }
@@ -46,7 +46,7 @@ let
     old_cfg=$(curl ${cfg.guiAddress}/rest/config)
 
     # generate the new config by merging with the NixOS config options
-    new_cfg=$(echo "$old_cfg" | ${pkgs.jq}/bin/jq -c '. * {
+    new_cfg=$(printf '%s\n' "$old_cfg" | ${pkgs.jq}/bin/jq -c '. * {
         "devices": (${builtins.toJSON devices}${optionalString (! cfg.overrideDevices) " + .devices"}),
         "folders": (${builtins.toJSON folders}${optionalString (! cfg.overrideFolders) " + .folders"})
     } * ${builtins.toJSON cfg.extraOptions}')
@@ -149,6 +149,15 @@ in {
               '';
             };
 
+            autoAcceptFolders = mkOption {
+              type = types.bool;
+              default = false;
+              description = ''
+                Automatically create or share folders that this device advertises at the default path.
+                See <link xlink:href="https://docs.syncthing.net/users/config.html?highlight=autoaccept#config-file-format"/>.
+              '';
+            };
+
           };
         }));
       };
@@ -425,6 +434,15 @@ in {
         defaultText = literalExample "dataDir${optionalString cond " + \"/.config/syncthing\""}";
       };
 
+      extraFlags = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        example = [ "--reset-deltas" ];
+        description = ''
+          Extra flags passed to the syncthing command in the service definition.
+        '';
+      };
+
       openDefaultPorts = mkOption {
         type = types.bool;
         default = false;
@@ -517,7 +535,7 @@ in {
             ${cfg.package}/bin/syncthing \
               -no-browser \
               -gui-address=${cfg.guiAddress} \
-              -home=${cfg.configDir}
+              -home=${cfg.configDir} ${escapeShellArgs cfg.extraFlags}
           '';
           MemoryDenyWriteExecute = true;
           NoNewPrivileges = true;
diff --git a/nixos/modules/services/search/elasticsearch.nix b/nixos/modules/services/search/elasticsearch.nix
index 91d8f544e16b..440f34b3dc5c 100644
--- a/nixos/modules/services/search/elasticsearch.nix
+++ b/nixos/modules/services/search/elasticsearch.nix
@@ -5,8 +5,6 @@ with lib;
 let
   cfg = config.services.elasticsearch;
 
-  es6 = builtins.compareVersions cfg.package.version "6" >= 0;
-
   esConfig = ''
     network.host: ${cfg.listenAddress}
     cluster.name: ${cfg.cluster_name}
@@ -36,7 +34,8 @@ let
     postBuild = "${pkgs.coreutils}/bin/mkdir -p $out/plugins";
   };
 
-in {
+in
+{
 
   ###### interface
 
@@ -116,20 +115,20 @@ in {
 
     extraCmdLineOptions = mkOption {
       description = "Extra command line options for the elasticsearch launcher.";
-      default = [];
+      default = [ ];
       type = types.listOf types.str;
     };
 
     extraJavaOptions = mkOption {
       description = "Extra command line options for Java.";
-      default = [];
+      default = [ ];
       type = types.listOf types.str;
       example = [ "-Djava.net.preferIPv4Stack=true" ];
     };
 
     plugins = mkOption {
       description = "Extra elasticsearch plugins";
-      default = [];
+      default = [ ];
       type = types.listOf types.package;
       example = lib.literalExample "[ pkgs.elasticsearchPlugins.discovery-ec2 ]";
     };
@@ -146,9 +145,7 @@ in {
       path = [ pkgs.inetutils ];
       environment = {
         ES_HOME = cfg.dataDir;
-        ES_JAVA_OPTS = toString ( optional (!es6) [ "-Des.path.conf=${configDir}" ]
-                                  ++ cfg.extraJavaOptions);
-      } // optionalAttrs es6 {
+        ES_JAVA_OPTS = toString cfg.extraJavaOptions;
         ES_PATH_CONF = configDir;
       };
       serviceConfig = {
@@ -187,7 +184,10 @@ in {
         rm -f "${configDir}/logging.yml"
         cp ${loggingConfigFile} ${configDir}/${loggingConfigFilename}
         mkdir -p ${configDir}/scripts
-        ${optionalString es6 "cp ${cfg.package}/config/jvm.options ${configDir}/jvm.options"}
+        cp ${cfg.package}/config/jvm.options ${configDir}/jvm.options
+        # redirect jvm logs to the data directory
+        mkdir -m 0700 -p ${cfg.dataDir}/logs
+        ${pkgs.sd}/bin/sd 'logs/gc.log' '${cfg.dataDir}/logs/gc.log' ${configDir}/jvm.options \
 
         if [ "$(id -u)" = 0 ]; then chown -R elasticsearch:elasticsearch ${cfg.dataDir}; fi
       '';
diff --git a/nixos/modules/services/web-apps/nextcloud.nix b/nixos/modules/services/web-apps/nextcloud.nix
index 5e15aaba0966..ba5f6582cbec 100644
--- a/nixos/modules/services/web-apps/nextcloud.nix
+++ b/nixos/modules/services/web-apps/nextcloud.nix
@@ -502,8 +502,6 @@ in {
               ${if c.dbport != null then "--database-port" else null} = ''"${toString c.dbport}"'';
               ${if c.dbuser != null then "--database-user" else null} = ''"${c.dbuser}"'';
               "--database-pass" = dbpass;
-              ${if c.dbtableprefix != null
-                then "--database-table-prefix" else null} = ''"${toString c.dbtableprefix}"'';
               "--admin-user" = ''"${c.adminuser}"'';
               "--admin-pass" = adminpass;
               "--data-dir" = ''"${cfg.home}/data"'';
diff --git a/nixos/modules/services/web-apps/nextcloud.xml b/nixos/modules/services/web-apps/nextcloud.xml
index 3af37b15dd56..ed84487d233a 100644
--- a/nixos/modules/services/web-apps/nextcloud.xml
+++ b/nixos/modules/services/web-apps/nextcloud.xml
@@ -84,47 +84,93 @@
 </para>
 
  </section>
- <section xml:id="module-services-nextcloud-pitfalls-during-upgrade">
-  <title>Pitfalls</title>
-
-  <para>
-   Unfortunately Nextcloud appears to be very stateful when it comes to
-   managing its own configuration. The config file lives in the home directory
-   of the <literal>nextcloud</literal> user (by default
-   <literal>/var/lib/nextcloud/config/config.php</literal>) and is also used to
-   track several states of the application (e.g. whether installed or not).
-  </para>
 
-  <para>
-   All configuration parameters are also stored in
-   <literal>/var/lib/nextcloud/config/override.config.php</literal> which is generated by
-   the module and linked from the store to ensure that all values from <literal>config.php</literal>
-   can be modified by the module.
-   However <literal>config.php</literal> manages the application's state and shouldn't be touched
-   manually because of that.
-  </para>
-
-  <warning>
-   <para>Don't delete <literal>config.php</literal>! This file
-   tracks the application's state and a deletion can cause unwanted
-   side-effects!</para>
-  </warning>
-
-  <warning>
-   <para>Don't rerun <literal>nextcloud-occ
-   maintenance:install</literal>! This command tries to install the application
-   and can cause unwanted side-effects!</para>
-  </warning>
+ <section xml:id="module-services-nextcloud-pitfalls-during-upgrade">
+  <title>Common problems</title>
+  <itemizedlist>
+   <listitem>
+    <formalpara>
+     <title>General notes</title>
+     <para>
+      Unfortunately Nextcloud appears to be very stateful when it comes to
+      managing its own configuration. The config file lives in the home directory
+      of the <literal>nextcloud</literal> user (by default
+      <literal>/var/lib/nextcloud/config/config.php</literal>) and is also used to
+      track several states of the application (e.g., whether installed or not).
+     </para>
+    </formalpara>
+    <para>
+     All configuration parameters are also stored in
+     <filename>/var/lib/nextcloud/config/override.config.php</filename> which is generated by
+     the module and linked from the store to ensure that all values from
+     <filename>config.php</filename> can be modified by the module.
+     However <filename>config.php</filename> manages the application's state and shouldn't be
+     touched manually because of that.
+    </para>
+    <warning>
+     <para>Don't delete <filename>config.php</filename>! This file
+     tracks the application's state and a deletion can cause unwanted
+     side-effects!</para>
+    </warning>
 
-  <para>
-   Nextcloud doesn't allow to move more than one major-version forward. If you're e.g. on
-   <literal>v16</literal>, you cannot upgrade to <literal>v18</literal>, you need to upgrade to
-   <literal>v17</literal> first. This is ensured automatically as long as the
-   <link linkend="opt-system.stateVersion">stateVersion</link> is declared properly. In that case
-   the oldest version available (one major behind the one from the previous NixOS
-   release) will be selected by default and the module will generate a warning that reminds
-   the user to upgrade to latest Nextcloud <emphasis>after</emphasis> that deploy.
-  </para>
+    <warning>
+     <para>Don't rerun <literal>nextcloud-occ
+     maintenance:install</literal>! This command tries to install the application
+     and can cause unwanted side-effects!</para>
+    </warning>
+   </listitem>
+   <listitem>
+    <formalpara>
+     <title>Multiple version upgrades</title>
+     <para>
+      Nextcloud doesn't allow to move more than one major-version forward. E.g., if you're on
+      <literal>v16</literal>, you cannot upgrade to <literal>v18</literal>, you need to upgrade to
+      <literal>v17</literal> first. This is ensured automatically as long as the
+      <link linkend="opt-system.stateVersion">stateVersion</link> is declared properly. In that case
+      the oldest version available (one major behind the one from the previous NixOS
+      release) will be selected by default and the module will generate a warning that reminds
+      the user to upgrade to latest Nextcloud <emphasis>after</emphasis> that deploy.
+     </para>
+    </formalpara>
+   </listitem>
+   <listitem>
+    <formalpara>
+     <title><literal>Error: Command "upgrade" is not defined.</literal></title>
+     <para>
+      This error usually occurs if the initial installation
+      (<command>nextcloud-occ maintenance:install</command>) has failed. After that, the application
+      is not installed, but the upgrade is attempted to be executed. Further context can
+      be found in <link xlink:href="https://github.com/NixOS/nixpkgs/issues/111175">NixOS/nixpkgs#111175</link>.
+     </para>
+    </formalpara>
+    <para>
+     First of all, it makes sense to find out what went wrong by looking at the logs
+     of the installation via <command>journalctl -u nextcloud-setup</command> and try to fix
+     the underlying issue.
+    </para>
+    <itemizedlist>
+     <listitem>
+      <para>
+       If this occurs on an <emphasis>existing</emphasis> setup, this is most likely because
+       the maintenance mode is active. It can be deactivated by running
+       <command>nextcloud-occ maintenance:mode --off</command>. It's advisable though to
+       check the logs first on why the maintenance mode was activated.
+      </para>
+     </listitem>
+     <listitem>
+      <warning><para>Only perform the following measures on
+      <emphasis>freshly installed instances!</emphasis></para></warning>
+      <para>
+       A re-run of the installer can be forced by <emphasis>deleting</emphasis>
+       <filename>/var/lib/nextcloud/config/config.php</filename>. This is the only time
+       advisable because the fresh install doesn't have any state that can be lost.
+       In case that doesn't help, an entire re-creation can be forced via
+       <command>rm -rf ~nextcloud/</command>.
+      </para>
+     </listitem>
+    </itemizedlist>
+   </listitem>
+  </itemizedlist>
  </section>
 
  <section xml:id="module-services-nextcloud-httpd">
diff --git a/nixos/modules/services/web-apps/tt-rss.nix b/nixos/modules/services/web-apps/tt-rss.nix
index ed13845915c4..bc18c824f394 100644
--- a/nixos/modules/services/web-apps/tt-rss.nix
+++ b/nixos/modules/services/web-apps/tt-rss.nix
@@ -6,10 +6,6 @@ let
 
   configVersion = 26;
 
-  cacheDir = "cache";
-  lockDir = "lock";
-  feedIconsDir = "feed-icons";
-
   dbPort = if cfg.database.port == null
     then (if cfg.database.type == "pgsql" then 5432 else 3306)
     else cfg.database.port;
@@ -32,10 +28,10 @@ let
     <?php
       putenv('TTRSS_PHP_EXECUTABLE=${pkgs.php}/bin/php');
 
-      putenv('TTRSS_LOCK_DIRECTORY=${lockDir}');
-      putenv('TTRSS_CACHE_DIR=${cacheDir}');
-      putenv('TTRSS_ICONS_DIR=${feedIconsDir}');
-      putenv('TTRSS_ICONS_URL=${feedIconsDir}');
+      putenv('TTRSS_LOCK_DIRECTORY=${cfg.root}/lock');
+      putenv('TTRSS_CACHE_DIR=${cfg.root}/cache');
+      putenv('TTRSS_ICONS_DIR=${cfg.root}/feed-icons');
+      putenv('TTRSS_ICONS_URL=feed-icons');
       putenv('TTRSS_SELF_URL_PATH=${cfg.selfUrlPath}');
 
       putenv('TTRSS_MYSQL_CHARSET=UTF8');
@@ -101,6 +97,22 @@ let
       ${cfg.extraConfig}
   '';
 
+  # tt-rss and plugins and themes and config.php
+  servedRoot = pkgs.runCommand "tt-rss-served-root" {} ''
+    cp --no-preserve=mode -r ${pkgs.tt-rss} $out
+    cp ${tt-rss-config} $out/config.php
+    ${optionalString (cfg.pluginPackages != []) ''
+    for plugin in ${concatStringsSep " " cfg.pluginPackages}; do
+    cp -r "$plugin"/* "$out/plugins.local/"
+    done
+    ''}
+    ${optionalString (cfg.themePackages != []) ''
+    for theme in ${concatStringsSep " " cfg.themePackages}; do
+    cp -r "$theme"/* "$out/themes.local/"
+    done
+    ''}
+  '';
+
  in {
 
   ###### interface
@@ -544,12 +556,16 @@ let
       enable = true;
       virtualHosts = {
         ${cfg.virtualHost} = {
-          root = "${cfg.root}";
+          root = "${cfg.root}/www";
 
           locations."/" = {
             index = "index.php";
           };
 
+          locations."^~ /feed-icons" = {
+            root = "${cfg.root}";
+          };
+
           locations."~ \\.php$" = {
             extraConfig = ''
               fastcgi_split_path_info ^(.+\.php)(/.+)$;
@@ -562,13 +578,19 @@ let
     };
 
     systemd.tmpfiles.rules = [
-      "d '${cfg.root}' 0755 ${cfg.user} tt_rss - -"
-      "Z '${cfg.root}' 0755 ${cfg.user} tt_rss - -"
+      "d '${cfg.root}' 0555 ${cfg.user} tt_rss - -"
+      "d '${cfg.root}/lock' 0755 ${cfg.user} tt_rss - -"
+      "d '${cfg.root}/cache' 0755 ${cfg.user} tt_rss - -"
+      "d '${cfg.root}/cache/upload' 0755 ${cfg.user} tt_rss - -"
+      "d '${cfg.root}/cache/images' 0755 ${cfg.user} tt_rss - -"
+      "d '${cfg.root}/cache/export' 0755 ${cfg.user} tt_rss - -"
+      "d '${cfg.root}/feed-icons' 0755 ${cfg.user} tt_rss - -"
+      "L+ '${cfg.root}/www' - - - - ${servedRoot}"
     ];
 
     systemd.services = {
       phpfpm-tt-rss = mkIf (cfg.pool == "${poolName}") {
-        restartTriggers = [ tt-rss-config pkgs.tt-rss ];
+        restartTriggers = [ servedRoot ];
       };
 
       tt-rss = {
@@ -594,27 +616,7 @@ let
 
               else "";
 
-        in ''
-          rm -rf "${cfg.root}/*"
-          cp -r "${pkgs.tt-rss}/"* "${cfg.root}"
-          ${optionalString (cfg.pluginPackages != []) ''
-            for plugin in ${concatStringsSep " " cfg.pluginPackages}; do
-              cp -r "$plugin"/* "${cfg.root}/plugins.local/"
-            done
-          ''}
-          ${optionalString (cfg.themePackages != []) ''
-            for theme in ${concatStringsSep " " cfg.themePackages}; do
-              cp -r "$theme"/* "${cfg.root}/themes.local/"
-            done
-          ''}
-          ln -sf "${tt-rss-config}" "${cfg.root}/config.php"
-          chmod -R 755 "${cfg.root}"
-          chmod -R ug+rwX "${cfg.root}/${lockDir}"
-          chmod -R ug+rwX "${cfg.root}/${cacheDir}"
-          chmod -R ug+rwX "${cfg.root}/${feedIconsDir}"
-        ''
-
-        + (optionalString (cfg.database.type == "pgsql") ''
+        in (optionalString (cfg.database.type == "pgsql") ''
           exists=$(${callSql "select count(*) > 0 from pg_tables where tableowner = user"} \
           | tail -n+3 | head -n-2 | sed -e 's/[ \n\t]*//')
 
@@ -639,7 +641,7 @@ let
         serviceConfig = {
           User = "${cfg.user}";
           Group = "tt_rss";
-          ExecStart = "${pkgs.php}/bin/php ${cfg.root}/update.php --daemon --quiet";
+          ExecStart = "${pkgs.php}/bin/php ${cfg.root}/www/update.php --daemon --quiet";
           Restart = "on-failure";
           RestartSec = "60";
           SyslogIdentifier = "tt-rss";
diff --git a/nixos/modules/services/x11/desktop-managers/gnome.nix b/nixos/modules/services/x11/desktop-managers/gnome.nix
index b0859321a525..4bc42525906c 100644
--- a/nixos/modules/services/x11/desktop-managers/gnome.nix
+++ b/nixos/modules/services/x11/desktop-managers/gnome.nix
@@ -372,6 +372,13 @@ in
       xdg.portal.enable = true;
       xdg.portal.extraPortals = [ pkgs.xdg-desktop-portal-gtk ];
 
+      # Harmonize Qt5 application style and also make them use the portal for file chooser dialog.
+      qt5 = {
+        enable = mkDefault true;
+        platformTheme = mkDefault "gnome";
+        style = mkDefault "adwaita";
+      };
+
       networking.networkmanager.enable = mkDefault true;
 
       services.xserver.updateDbusEnvironment = true;
diff --git a/nixos/modules/services/x11/display-managers/sx.nix b/nixos/modules/services/x11/display-managers/sx.nix
new file mode 100644
index 000000000000..132531c0ddc0
--- /dev/null
+++ b/nixos/modules/services/x11/display-managers/sx.nix
@@ -0,0 +1,37 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let cfg = config.services.xserver.displayManager.sx;
+
+in {
+  options = {
+    services.xserver.displayManager.sx = {
+      enable = mkEnableOption "sx pseudo-display manager" // {
+        description = ''
+          Whether to enable the "sx" pseudo-display manager, which allows users
+          to start manually via the "sx" command from a vt shell. The X server
+          runs under the user's id, not as root. The user must provide a
+          ~/.config/sx/sxrc file containing session startup commands, see
+          sx(1). This is not automatically generated from the desktopManager
+          and windowManager settings. sx doesn't have a way to directly set
+          X server flags, but it can be done by overriding its xorgserver
+          dependency.
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    environment.systemPackages = [ pkgs.sx ];
+    services.xserver = {
+      exportConfiguration = true;
+      displayManager = {
+        job.execCmd = "";
+        lightdm.enable = mkForce false;
+      };
+      logFile = mkDefault null;
+    };
+    systemd.services.display-manager.enable = false;
+  };
+}
diff --git a/nixos/modules/services/x11/window-managers/awesome.nix b/nixos/modules/services/x11/window-managers/awesome.nix
index 089e9f769f0a..37a14e34f57e 100644
--- a/nixos/modules/services/x11/window-managers/awesome.nix
+++ b/nixos/modules/services/x11/window-managers/awesome.nix
@@ -27,7 +27,7 @@ in
         default = [];
         type = types.listOf types.package;
         description = "List of lua packages available for being used in the Awesome configuration.";
-        example = literalExample "[ luaPackages.oocairo ]";
+        example = literalExample "[ pkgs.luaPackages.vicious ]";
       };
 
       package = mkOption {
diff --git a/nixos/modules/services/x11/window-managers/clfswm.nix b/nixos/modules/services/x11/window-managers/clfswm.nix
index 171660c53ac3..5015852db69f 100644
--- a/nixos/modules/services/x11/window-managers/clfswm.nix
+++ b/nixos/modules/services/x11/window-managers/clfswm.nix
@@ -8,17 +8,27 @@ in
 
 {
   options = {
-    services.xserver.windowManager.clfswm.enable = mkEnableOption "clfswm";
+    services.xserver.windowManager.clfswm = {
+      enable = mkEnableOption "clfswm";
+      package = mkOption {
+        type        = types.package;
+        default     = pkgs.lispPackages.clfswm;
+        defaultText = "pkgs.lispPackages.clfswm";
+        description = ''
+          clfswm package to use.
+        '';
+      };
+    };
   };
 
   config = mkIf cfg.enable {
     services.xserver.windowManager.session = singleton {
       name = "clfswm";
       start = ''
-        ${pkgs.lispPackages.clfswm}/bin/clfswm &
+        ${cfg.package}/bin/clfswm &
         waitPID=$!
       '';
     };
-    environment.systemPackages = [ pkgs.lispPackages.clfswm ];
+    environment.systemPackages = [ cfg.package ];
   };
 }
diff --git a/nixos/modules/system/boot/stage-1-init.sh b/nixos/modules/system/boot/stage-1-init.sh
index ddaf985878e0..3dfcc010b64e 100644
--- a/nixos/modules/system/boot/stage-1-init.sh
+++ b/nixos/modules/system/boot/stage-1-init.sh
@@ -542,7 +542,7 @@ while read -u 3 mountPoint; do
     # If copytoram is enabled: skip mounting the ISO and copy its content to a tmpfs.
     if [ -n "$copytoram" ] && [ "$device" = /dev/root ] && [ "$mountPoint" = /iso ]; then
       fsType=$(blkid -o value -s TYPE "$device")
-      fsSize=$(blockdev --getsize64 "$device")
+      fsSize=$(blockdev --getsize64 "$device" || stat -Lc '%s' "$device")
 
       mkdir -p /tmp-iso
       mount -t "$fsType" /dev/root /tmp-iso
diff --git a/nixos/modules/tasks/filesystems.nix b/nixos/modules/tasks/filesystems.nix
index ea13d396c46f..4f56504f45e7 100644
--- a/nixos/modules/tasks/filesystems.nix
+++ b/nixos/modules/tasks/filesystems.nix
@@ -264,6 +264,8 @@ in
         #
         # To make changes, edit the fileSystems and swapDevices NixOS options
         # in your /etc/nixos/configuration.nix file.
+        #
+        # <file system> <mount point>   <type>  <options>       <dump>  <pass>
 
         # Filesystems.
         ${concatMapStrings (fs:
diff --git a/nixos/modules/tasks/filesystems/exfat.nix b/nixos/modules/tasks/filesystems/exfat.nix
index 1527f993fdd4..540b9b91c3ec 100644
--- a/nixos/modules/tasks/filesystems/exfat.nix
+++ b/nixos/modules/tasks/filesystems/exfat.nix
@@ -4,8 +4,10 @@ with lib;
 
 {
   config = mkIf (any (fs: fs == "exfat") config.boot.supportedFilesystems) {
-
-    system.fsPackages = [ pkgs.exfat ];
-
+    system.fsPackages = if config.boot.kernelPackages.kernelOlder "5.7" then [
+      pkgs.exfat # FUSE
+    ] else [
+      pkgs.exfatprogs # non-FUSE
+    ];
   };
 }
diff --git a/nixos/modules/tasks/filesystems/zfs.nix b/nixos/modules/tasks/filesystems/zfs.nix
index 376d6530f363..cb0e66402476 100644
--- a/nixos/modules/tasks/filesystems/zfs.nix
+++ b/nixos/modules/tasks/filesystems/zfs.nix
@@ -8,6 +8,7 @@ with lib;
 let
 
   cfgZfs = config.boot.zfs;
+  cfgExpandOnBoot = config.services.zfs.expandOnBoot;
   cfgSnapshots = config.services.zfs.autoSnapshot;
   cfgSnapFlags = cfgSnapshots.flags;
   cfgScrub = config.services.zfs.autoScrub;
@@ -200,7 +201,6 @@ in
           an interactive prompt (keylocation=prompt) and from a file (keylocation=file://).
         '';
       };
-
     };
 
     services.zfs.autoSnapshot = {
@@ -327,6 +327,23 @@ in
       };
     };
 
+    services.zfs.expandOnBoot = mkOption {
+      type = types.either (types.enum [ "disabled" "all" ]) (types.listOf types.str);
+      default = "disabled";
+      example = [ "tank" "dozer" ];
+      description = ''
+        After importing, expand each device in the specified pools.
+
+        Set the value to the plain string "all" to expand all pools on boot:
+
+            services.zfs.expandOnBoot = "all";
+
+        or set the value to a list of pools to expand the disks of specific pools:
+
+            services.zfs.expandOnBoot = [ "tank" "dozer" ];
+      '';
+    };
+
     services.zfs.zed = {
       enableMail = mkEnableOption "ZED's ability to send emails" // {
         default = cfgZfs.package.enableMail;
@@ -586,6 +603,7 @@ in
               ${cfgZfs.package}/sbin/zfs set nixos:shutdown-time="$(date)" "${pool}"
             '';
           };
+
         createZfsService = serv:
           nameValuePair serv {
             after = [ "systemd-modules-load.service" ];
@@ -609,6 +627,86 @@ in
       systemd.targets.zfs.wantedBy = [ "multi-user.target" ];
     })
 
+    (mkIf (cfgZfs.enabled && cfgExpandOnBoot != "disabled") {
+      systemd.services."zpool-expand@" = {
+        description = "Expand ZFS pools";
+        after = [ "zfs.target" ];
+
+        serviceConfig = {
+          Type = "oneshot";
+          RemainAfterExit = true;
+        };
+
+        scriptArgs = "%i";
+        path = [ pkgs.gawk cfgZfs.package ];
+
+        # ZFS has no way of enumerating just devices in a pool in a way
+        # that 'zpool online -e' supports. Thus, we've implemented a
+        # bit of a strange approach of highlighting just devices.
+        # See: https://github.com/openzfs/zfs/issues/12505
+        script = let
+          # This UUID has been chosen at random and is to provide a
+          # collision-proof, predictable token to search for
+          magicIdentifier = "NIXOS-ZFS-ZPOOL-DEVICE-IDENTIFIER-37108bec-aff6-4b58-9e5e-53c7c9766f05";
+          zpoolScripts = pkgs.writeShellScriptBin "device-highlighter" ''
+            echo "${magicIdentifier}"
+          '';
+        in ''
+          pool=$1
+
+          echo "Expanding all devices for $pool."
+
+          # Put our device-highlighter script it to the PATH
+          export ZPOOL_SCRIPTS_PATH=${zpoolScripts}/bin
+
+          # Enable running our precisely specified zpool script as root
+          export ZPOOL_SCRIPTS_AS_ROOT=1
+
+          devices() (
+            zpool status -c device-highlighter "$pool" \
+             | awk '($2 == "ONLINE" && $6 == "${magicIdentifier}") { print $1; }'
+          )
+
+          for device in $(devices); do
+            echo "Attempting to expand $device of $pool..."
+            if ! zpool online -e "$pool" "$device"; then
+              echo "Failed to expand '$device' of '$pool'."
+            fi
+          done
+        '';
+      };
+
+      systemd.services."zpool-expand-pools" =
+        let
+          # Create a string, to be interpolated in a bash script
+          # which enumerates all of the pools to expand.
+          # If the `pools` option is `true`, we want to dynamically
+          # expand every pool. Otherwise we want to enumerate
+          # just the specifically provided list of pools.
+          poolListProvider = if cfgExpandOnBoot == "all"
+            then "$(zpool list -H | awk '{print $1}')"
+            else lib.escapeShellArgs cfgExpandOnBoot;
+        in
+        {
+          description = "Expand specified ZFS pools";
+          wantedBy = [ "default.target" ];
+          after = [ "zfs.target" ];
+
+          serviceConfig = {
+            Type = "oneshot";
+            RemainAfterExit = true;
+          };
+
+          path = [ pkgs.gawk cfgZfs.package ];
+
+          script = ''
+            for pool in ${poolListProvider}; do
+              systemctl start --no-block "zpool-expand@$pool"
+            done
+          '';
+        };
+    })
+
     (mkIf (cfgZfs.enabled && cfgSnapshots.enable) {
       systemd.services = let
                            descr = name: if name == "frequent" then "15 mins"
diff --git a/nixos/modules/virtualisation/amazon-image.nix b/nixos/modules/virtualisation/amazon-image.nix
index bf5c04543a70..fe248a94488b 100644
--- a/nixos/modules/virtualisation/amazon-image.nix
+++ b/nixos/modules/virtualisation/amazon-image.nix
@@ -41,17 +41,23 @@ in
 
     boot.growPartition = cfg.hvm;
 
-    fileSystems."/" = {
+    fileSystems."/" = mkIf (!cfg.zfs.enable) {
       device = "/dev/disk/by-label/nixos";
       fsType = "ext4";
       autoResize = true;
     };
 
-    fileSystems."/boot" = mkIf cfg.efi {
+    fileSystems."/boot" = mkIf (cfg.efi || cfg.zfs.enable) {
+      # The ZFS image uses a partition labeled ESP whether or not we're
+      # booting with EFI.
       device = "/dev/disk/by-label/ESP";
       fsType = "vfat";
     };
 
+    services.zfs.expandOnBoot = mkIf cfg.zfs.enable "all";
+
+    boot.zfs.devNodes = mkIf cfg.zfs.enable "/dev/";
+
     boot.extraModulePackages = [
       config.boot.kernelPackages.ena
     ];
diff --git a/nixos/modules/virtualisation/amazon-options.nix b/nixos/modules/virtualisation/amazon-options.nix
index 2e807131e938..698edcd835a6 100644
--- a/nixos/modules/virtualisation/amazon-options.nix
+++ b/nixos/modules/virtualisation/amazon-options.nix
@@ -1,7 +1,46 @@
 { config, lib, pkgs, ... }:
-{
+let
+  inherit (lib) types;
+in {
   options = {
     ec2 = {
+      zfs = {
+        enable = lib.mkOption {
+          default = false;
+          internal = true;
+          description = ''
+            Whether the EC2 instance uses a ZFS root.
+          '';
+        };
+
+        datasets = lib.mkOption {
+          description = ''
+            Datasets to create under the `tank` and `boot` zpools.
+
+            **NOTE:** This option is used only at image creation time, and
+            does not attempt to declaratively create or manage datasets
+            on an existing system.
+          '';
+
+          default = {};
+
+          type = types.attrsOf (types.submodule {
+            options = {
+              mount = lib.mkOption {
+                description = "Where to mount this dataset.";
+                type = types.nullOr types.string;
+                default = null;
+              };
+
+              properties = lib.mkOption {
+                description = "Properties to set on this dataset.";
+                type = types.attrsOf types.string;
+                default = {};
+              };
+            };
+          });
+        };
+      };
       hvm = lib.mkOption {
         default = lib.versionAtLeast config.system.stateVersion "17.03";
         internal = true;
@@ -18,4 +57,17 @@
       };
     };
   };
+
+  config = lib.mkIf config.ec2.zfs.enable {
+    networking.hostId = lib.mkDefault "00000000";
+
+    fileSystems = let
+      mountable = lib.filterAttrs (_: value: ((value.mount or null) != null)) config.ec2.zfs.datasets;
+    in lib.mapAttrs'
+      (dataset: opts: lib.nameValuePair opts.mount {
+        device = dataset;
+        fsType = "zfs";
+      })
+      mountable;
+  };
 }
diff --git a/nixos/release.nix b/nixos/release.nix
index 2367e79e4ad0..264d82bacc8a 100644
--- a/nixos/release.nix
+++ b/nixos/release.nix
@@ -217,6 +217,20 @@ in rec {
     }).config.system.build.amazonImage)
 
   );
+  amazonImageZfs = forMatchingSystems [ "x86_64-linux" "aarch64-linux" ] (system:
+
+    with import ./.. { inherit system; };
+
+    hydraJob ((import lib/eval-config.nix {
+      inherit system;
+      modules =
+        [ configuration
+          versionModule
+          ./maintainers/scripts/ec2/amazon-image-zfs.nix
+        ];
+    }).config.system.build.amazonImage)
+
+  );
 
 
   # Test job for https://github.com/NixOS/nixpkgs/issues/121354 to test
diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix
index 314c031bb3d4..6baa986b2bda 100644
--- a/nixos/tests/all-tests.nix
+++ b/nixos/tests/all-tests.nix
@@ -221,10 +221,7 @@ in
   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 {};
-  kubernetes.rbac = handleTestOn ["x86_64-linux"] ./kubernetes/rbac.nix {};
+  kubernetes = handleTestOn ["x86_64-linux"] ./kubernetes {};
   latestKernel.hardened = handleTest ./hardened.nix { latestKernel = true; };
   latestKernel.login = handleTest ./login.nix { latestKernel = true; };
   leaps = handleTest ./leaps.nix {};
@@ -359,6 +356,7 @@ in
   pomerium = handleTestOn ["x86_64-linux"] ./pomerium.nix {};
   postfix = handleTest ./postfix.nix {};
   postfix-raise-smtpd-tls-security-level = handleTest ./postfix-raise-smtpd-tls-security-level.nix {};
+  postfixadmin = handleTest ./postfixadmin.nix {};
   postgis = handleTest ./postgis.nix {};
   postgresql = handleTest ./postgresql.nix {};
   postgresql-wal-receiver = handleTest ./postgresql-wal-receiver.nix {};
diff --git a/nixos/tests/caddy.nix b/nixos/tests/caddy.nix
index 29b227c0409b..0902904b2086 100644
--- a/nixos/tests/caddy.nix
+++ b/nixos/tests/caddy.nix
@@ -50,57 +50,58 @@ import ./make-test-python.nix ({ pkgs, ... }: {
         };
       };
     };
+  };
 
-    testScript = { nodes, ... }:
-      let
-        etagSystem = "${nodes.webserver.config.system.build.toplevel}/specialisation/etag";
-        justReloadSystem = "${nodes.webserver.config.system.build.toplevel}/specialisation/config-reload";
-        multipleConfigs = "${nodes.webserver.config.system.build.toplevel}/specialisation/multiple-configs";
-      in
-      ''
-        url = "http://localhost/example.html"
-        webserver.wait_for_unit("caddy")
-        webserver.wait_for_open_port("80")
+  testScript = { nodes, ... }:
+    let
+      etagSystem = "${nodes.webserver.config.system.build.toplevel}/specialisation/etag";
+      justReloadSystem = "${nodes.webserver.config.system.build.toplevel}/specialisation/config-reload";
+      multipleConfigs = "${nodes.webserver.config.system.build.toplevel}/specialisation/multiple-configs";
+    in
+    ''
+      url = "http://localhost/example.html"
+      webserver.wait_for_unit("caddy")
+      webserver.wait_for_open_port("80")
 
 
-        def check_etag(url):
-            etag = webserver.succeed(
-                "curl --fail -v '{}' 2>&1 | sed -n -e \"s/^< [Ee][Tt][Aa][Gg]: *//p\"".format(
-                    url
-                )
-            )
-            etag = etag.replace("\r\n", " ")
-            http_code = webserver.succeed(
-                "curl --fail --silent --show-error -o /dev/null -w \"%{{http_code}}\" --head -H 'If-None-Match: {}' {}".format(
-                    etag, url
-                )
-            )
-            assert int(http_code) == 304, "HTTP code is {}, expected 304".format(http_code)
-            return etag
+      def check_etag(url):
+          etag = webserver.succeed(
+              "curl --fail -v '{}' 2>&1 | sed -n -e \"s/^< [Ee][Tt][Aa][Gg]: *//p\"".format(
+                  url
+              )
+          )
+          etag = etag.replace("\r\n", " ")
+          http_code = webserver.succeed(
+              "curl --fail --silent --show-error -o /dev/null -w \"%{{http_code}}\" --head -H 'If-None-Match: {}' {}".format(
+                  etag, url
+              )
+          )
+          assert int(http_code) == 304, "HTTP code is {}, expected 304".format(http_code)
+          return etag
 
 
-        with subtest("check ETag if serving Nix store paths"):
-            old_etag = check_etag(url)
-            webserver.succeed(
-                "${etagSystem}/bin/switch-to-configuration test >&2"
-            )
-            webserver.sleep(1)
-            new_etag = check_etag(url)
-            assert old_etag != new_etag, "Old ETag {} is the same as {}".format(
-                old_etag, new_etag
-            )
+      with subtest("check ETag if serving Nix store paths"):
+          old_etag = check_etag(url)
+          webserver.succeed(
+              "${etagSystem}/bin/switch-to-configuration test >&2"
+          )
+          webserver.sleep(1)
+          new_etag = check_etag(url)
+          assert old_etag != new_etag, "Old ETag {} is the same as {}".format(
+              old_etag, new_etag
+          )
 
-        with subtest("config is reloaded on nixos-rebuild switch"):
-            webserver.succeed(
-                "${justReloadSystem}/bin/switch-to-configuration test >&2"
-            )
-            webserver.wait_for_open_port("8080")
+      with subtest("config is reloaded on nixos-rebuild switch"):
+          webserver.succeed(
+              "${justReloadSystem}/bin/switch-to-configuration test >&2"
+          )
+          webserver.wait_for_open_port("8080")
 
-        with subtest("multiple configs are correctly merged"):
-            webserver.succeed(
-                "${multipleConfigs}/bin/switch-to-configuration test >&2"
-            )
-            webserver.wait_for_open_port("8080")
-            webserver.wait_for_open_port("8081")
-      '';
-  })
+      with subtest("multiple configs are correctly merged"):
+          webserver.succeed(
+              "${multipleConfigs}/bin/switch-to-configuration test >&2"
+          )
+          webserver.wait_for_open_port("8080")
+          webserver.wait_for_open_port("8081")
+    '';
+})
diff --git a/nixos/tests/gitlab.nix b/nixos/tests/gitlab.nix
index fc153bb06ec1..3e9feeb0769d 100644
--- a/nixos/tests/gitlab.nix
+++ b/nixos/tests/gitlab.nix
@@ -145,7 +145,8 @@ import ./make-test-python.nix ({ pkgs, lib, ...} : with lib; {
       )
       gitlab.succeed("systemd-tmpfiles --create")
       gitlab.succeed("rm -rf ${nodes.gitlab.config.services.postgresql.dataDir}")
-      gitlab.systemctl("start gitlab-config.service gitlab-postgresql.service")
+      gitlab.systemctl("start gitlab-config.service gitaly.service gitlab-postgresql.service")
+      gitlab.wait_for_file("${nodes.gitlab.config.services.gitlab.statePath}/tmp/sockets/gitaly.socket")
       gitlab.succeed(
           "sudo -u gitlab -H gitlab-rake gitlab:backup:restore RAILS_ENV=production BACKUP=dump force=yes"
       )
diff --git a/nixos/tests/graylog.nix b/nixos/tests/graylog.nix
index 2d22012fa7c0..572904f60d57 100644
--- a/nixos/tests/graylog.nix
+++ b/nixos/tests/graylog.nix
@@ -1,6 +1,6 @@
 import ./make-test-python.nix ({ pkgs, lib, ... }: {
   name = "graylog";
-  meta.maintainers = with lib.maintainers; [ ma27 ];
+  meta.maintainers = with lib.maintainers; [ ];
 
   machine = { pkgs, ... }: {
     virtualisation.memorySize = 4096;
diff --git a/nixos/tests/kernel-generic.nix b/nixos/tests/kernel-generic.nix
index 5993d0e6ab78..391a93e36981 100644
--- a/nixos/tests/kernel-generic.nix
+++ b/nixos/tests/kernel-generic.nix
@@ -6,10 +6,10 @@
 with pkgs.lib;
 
 let
-  makeKernelTest = version: linuxPackages: (import ./make-test-python.nix ({ pkgs, ... }: {
-    name = "kernel-${version}";
+  testsForLinuxPackages = linuxPackages: (import ./make-test-python.nix ({ pkgs, ... }: {
+    name = "kernel-${linuxPackages.kernel.version}";
     meta = with pkgs.lib.maintainers; {
-      maintainers = [ nequissimus ];
+      maintainers = [ nequissimus atemu ];
     };
 
     machine = { ... }:
@@ -23,20 +23,26 @@ let
         assert "${linuxPackages.kernel.modDirVersion}" in machine.succeed("uname -a")
       '';
   }) args);
-in
-with pkgs; {
-  linux_4_4 = makeKernelTest "4.4" linuxPackages_4_4;
-  linux_4_9 = makeKernelTest "4.9" linuxPackages_4_9;
-  linux_4_14 = makeKernelTest "4.14" linuxPackages_4_14;
-  linux_4_19 = makeKernelTest "4.19" linuxPackages_4_19;
-  linux_5_4 = makeKernelTest "5.4" linuxPackages_5_4;
-  linux_5_10 = makeKernelTest "5.10" linuxPackages_5_10;
-  linux_5_13 = makeKernelTest "5.13" linuxPackages_5_13;
-
-  linux_hardened_4_14 = makeKernelTest "4.14" linuxPackages_4_14_hardened;
-  linux_hardened_4_19 = makeKernelTest "4.19" linuxPackages_4_19_hardened;
-  linux_hardened_5_4 = makeKernelTest "5.4" linuxPackages_5_4_hardened;
-  linux_hardened_5_10 = makeKernelTest "5.10" linuxPackages_5_10_hardened;
-
-  linux_testing = makeKernelTest "testing" linuxPackages_testing;
+  kernels = {
+    inherit (pkgs)
+      linuxPackages_4_4
+      linuxPackages_4_9
+      linuxPackages_4_14
+      linuxPackages_4_19
+      linuxPackages_5_4
+      linuxPackages_5_10
+      linuxPackages_5_13
+
+      linuxPackages_4_14_hardened
+      linuxPackages_4_19_hardened
+      linuxPackages_5_4_hardened
+      linuxPackages_5_10_hardened
+
+      linuxPackages_testing;
+  };
+
+in mapAttrs (_: lP: testsForLinuxPackages lP) kernels // {
+  inherit testsForLinuxPackages;
+
+  testsForKernel = kernel: testsForLinuxPackages (pkgs.linuxPackagesFor kernel);
 }
diff --git a/nixos/tests/kubernetes/default.nix b/nixos/tests/kubernetes/default.nix
index a801759bf582..90b73c68a76d 100644
--- a/nixos/tests/kubernetes/default.nix
+++ b/nixos/tests/kubernetes/default.nix
@@ -1,7 +1,15 @@
-{ system ? builtins.currentSystem }:
+{ system ? builtins.currentSystem
+, pkgs ? import <nixpkgs> { inherit system; }
+}:
+let
+  dns = import ./dns.nix { inherit system pkgs; };
+  rbac = import ./rbac.nix { inherit system pkgs; };
+  # TODO kubernetes.e2e should eventually replace kubernetes.rbac when it works
+  # e2e = import ./e2e.nix { inherit system pkgs; };
+in
 {
-  dns = import ./dns.nix { inherit system; };
-  # e2e = import ./e2e.nix { inherit system; };  # TODO: make it pass
-  # the following test(s) can be removed when e2e is working:
-  rbac = import ./rbac.nix { inherit system; };
+  dns-single-node = dns.singlenode.test;
+  dns-multi-node = dns.multinode.test;
+  rbac-single-node = rbac.singlenode.test;
+  rbac-multi-node = rbac.multinode.test;
 }
diff --git a/nixos/tests/matrix-synapse.nix b/nixos/tests/matrix-synapse.nix
index 9a1ff8a0d3ed..21e8c24e4713 100644
--- a/nixos/tests/matrix-synapse.nix
+++ b/nixos/tests/matrix-synapse.nix
@@ -26,6 +26,13 @@ import ./make-test-python.nix ({ pkgs, ... } : let
       -days 365
   '';
 
+
+  mailerCerts = import ./common/acme/server/snakeoil-certs.nix;
+  mailerDomain = mailerCerts.domain;
+  registrationSharedSecret = "unsecure123";
+  testUser = "alice";
+  testPassword = "alicealice";
+  testEmail = "alice@example.com";
 in {
 
   name = "matrix-synapse";
@@ -35,7 +42,10 @@ in {
 
   nodes = {
     # Since 0.33.0, matrix-synapse doesn't allow underscores in server names
-    serverpostgres = { pkgs, ... }: {
+    serverpostgres = { pkgs, nodes, ... }: let
+      mailserverIP = nodes.mailserver.config.networking.primaryIPAddress;
+    in
+    {
       services.matrix-synapse = {
         enable = true;
         database_type = "psycopg2";
@@ -44,6 +54,16 @@ in {
         database_args = {
           password = "synapse";
         };
+        registration_shared_secret = registrationSharedSecret;
+        public_baseurl = "https://example.com";
+        extraConfig = ''
+          email:
+            smtp_host: "${mailerDomain}"
+            smtp_port: 25
+            require_transport_security: true
+            notif_from: "matrix <matrix@${mailerDomain}>"
+            app_name: "Matrix"
+        '';
       };
       services.postgresql = {
         enable = true;
@@ -61,6 +81,85 @@ in {
             LC_CTYPE = "C";
         '';
       };
+
+      networking.extraHosts = ''
+        ${mailserverIP} ${mailerDomain}
+      '';
+
+      security.pki.certificateFiles = [
+        mailerCerts.ca.cert ca_pem
+      ];
+
+      environment.systemPackages = let
+        sendTestMailStarttls = pkgs.writeScriptBin "send-testmail-starttls" ''
+          #!${pkgs.python3.interpreter}
+          import smtplib
+          import ssl
+
+          ctx = ssl.create_default_context()
+
+          with smtplib.SMTP('${mailerDomain}') as smtp:
+            smtp.ehlo()
+            smtp.starttls(context=ctx)
+            smtp.ehlo()
+            smtp.sendmail('matrix@${mailerDomain}', '${testEmail}', 'Subject: Test STARTTLS\n\nTest data.')
+            smtp.quit()
+         '';
+
+        obtainTokenAndRegisterEmail = let
+          # adding the email through the API is quite complicated as it involves more than one step and some
+          # client-side calculation
+          insertEmailForAlice = pkgs.writeText "alice-email.sql" ''
+            INSERT INTO user_threepids (user_id, medium, address, validated_at, added_at) VALUES ('${testUser}@serverpostgres', 'email', '${testEmail}', '1629149927271', '1629149927270');
+          '';
+        in
+        pkgs.writeScriptBin "obtain-token-and-register-email" ''
+          #!${pkgs.runtimeShell}
+          set -o errexit
+          set -o pipefail
+          set -o nounset
+          su postgres -c "psql -d matrix-synapse -f ${insertEmailForAlice}"
+          curl --fail -XPOST 'https://localhost:8448/_matrix/client/r0/account/password/email/requestToken' -d '{"email":"${testEmail}","client_secret":"foobar","send_attempt":1}' -v
+        '';
+        in [ sendTestMailStarttls pkgs.matrix-synapse obtainTokenAndRegisterEmail ];
+    };
+
+    # test mail delivery
+    mailserver = args: let
+    in
+    {
+      security.pki.certificateFiles = [
+        mailerCerts.ca.cert
+      ];
+
+      networking.firewall.enable = false;
+
+      services.postfix = {
+        enable = true;
+        hostname = "${mailerDomain}";
+        # open relay for subnet
+        networksStyle = "subnet";
+        enableSubmission = true;
+        tlsTrustedAuthorities = "${mailerCerts.ca.cert}";
+        sslCert = "${mailerCerts.${mailerDomain}.cert}";
+        sslKey = "${mailerCerts.${mailerDomain}.key}";
+
+        # blackhole transport
+        transport = "example.com discard:silently";
+
+        config = {
+          debug_peer_level = "10";
+          smtpd_relay_restrictions = [
+            "permit_mynetworks" "reject_unauth_destination"
+          ];
+
+          # disable obsolete protocols, something old versions of twisted are still using
+          smtpd_tls_protocols = "TLSv1.3, TLSv1.2, !TLSv1.1, !TLSv1, !SSLv2, !SSLv3";
+          smtp_tls_protocols = "TLSv1.3, TLSv1.2, !TLSv1.1, !TLSv1, !SSLv2, !SSLv3";
+          smtpd_tls_mandatory_protocols = "TLSv1.3, TLSv1.2, !TLSv1.1, !TLSv1, !SSLv2, !SSLv3";
+          smtp_tls_mandatory_protocols = "TLSv1.3, TLSv1.2, !TLSv1.1, !TLSv1, !SSLv2, !SSLv3";
+        };
+      };
     };
 
     serversqlite = args: {
@@ -75,11 +174,15 @@ in {
 
   testScript = ''
     start_all()
+    mailserver.wait_for_unit("postfix.service")
+    serverpostgres.succeed("send-testmail-starttls")
     serverpostgres.wait_for_unit("matrix-synapse.service")
     serverpostgres.wait_until_succeeds(
         "curl --fail -L --cacert ${ca_pem} https://localhost:8448/"
     )
     serverpostgres.require_unit_state("postgresql.service")
+    serverpostgres.succeed("register_new_matrix_user -u ${testUser} -p ${testPassword} -a -k ${registrationSharedSecret} ")
+    serverpostgres.succeed("obtain-token-and-register-email")
     serversqlite.wait_for_unit("matrix-synapse.service")
     serversqlite.wait_until_succeeds(
         "curl --fail -L --cacert ${ca_pem} https://localhost:8448/"
diff --git a/nixos/tests/nextcloud/basic.nix b/nixos/tests/nextcloud/basic.nix
index 76f7f68dc960..c4ce34748ace 100644
--- a/nixos/tests/nextcloud/basic.nix
+++ b/nixos/tests/nextcloud/basic.nix
@@ -37,6 +37,7 @@ in {
         config = {
           # Don't inherit adminuser since "root" is supposed to be the default
           inherit adminpass;
+          dbtableprefix = "nixos_";
         };
         autoUpdateApps = {
           enable = true;
diff --git a/nixos/tests/nexus.nix b/nixos/tests/nexus.nix
index 2a30a4eb2cc8..87bb4d2eb58a 100644
--- a/nixos/tests/nexus.nix
+++ b/nixos/tests/nexus.nix
@@ -6,7 +6,7 @@
 import ./make-test-python.nix ({ pkgs, ...} : {
   name = "nexus";
   meta = with pkgs.lib.maintainers; {
-    maintainers = [ ironpinguin ma27 ];
+    maintainers = [ ironpinguin ];
   };
 
   nodes = {
diff --git a/nixos/tests/nzbget.nix b/nixos/tests/nzbget.nix
index d6111ba079c8..fe5a4bc3df91 100644
--- a/nixos/tests/nzbget.nix
+++ b/nixos/tests/nzbget.nix
@@ -8,13 +8,21 @@ import ./make-test-python.nix ({ pkgs, ...} : {
     server = { ... }: {
       services.nzbget.enable = true;
 
+      # provide some test settings
+      services.nzbget.settings = {
+        "MainDir" = "/var/lib/nzbget";
+        "DirectRename" = true;
+        "DiskSpace" = 0;
+        "Server1.Name" = "this is a test";
+      };
+
       # hack, don't add (unfree) unrar to nzbget's path,
       # so we can run this test in CI
       systemd.services.nzbget.path = pkgs.lib.mkForce [ pkgs.p7zip ];
     };
   };
 
-  testScript = ''
+  testScript = { nodes, ... }: ''
     start_all()
 
     server.wait_for_unit("nzbget.service")
@@ -26,5 +34,13 @@ import ./make-test-python.nix ({ pkgs, ...} : {
     server.succeed(
         "${pkgs.nzbget}/bin/nzbget -n -o Control_iP=127.0.0.1 -o Control_port=6789 -o Control_password=tegbzn6789 -V"
     )
+
+    config = server.succeed("${nodes.server.config.systemd.services.nzbget.serviceConfig.ExecStart} --printconfig")
+
+    # confirm the test settings are applied
+    assert 'MainDir = "/var/lib/nzbget"' in config
+    assert 'DirectRename = "yes"' in config
+    assert 'DiskSpace = "0"' in config
+    assert 'Server1.Name = "this is a test"' in config
   '';
 })
diff --git a/nixos/tests/paperless-ng.nix b/nixos/tests/paperless-ng.nix
index d8aafc2a08fd..a4b2f348ec32 100644
--- a/nixos/tests/paperless-ng.nix
+++ b/nixos/tests/paperless-ng.nix
@@ -25,12 +25,22 @@ import ./make-test-python.nix ({ lib, ... }: {
         # Wait until server accepts connections
         machine.wait_until_succeeds("curl -fs localhost:28981")
 
-    with subtest("Document is consumed"):
+    with subtest("Create web test doc"):
+        machine.succeed(
+            "convert -size 400x40 xc:white -font 'DejaVu-Sans' -pointsize 20 -fill black "
+            "-annotate +5+20 'hello web 16-10-2005' /tmp/webdoc.png"
+        )
+        machine.wait_until_succeeds("curl -u admin:admin -F document=@/tmp/webdoc.png -fs localhost:28981/api/documents/post_document/")
+
+    with subtest("Documents are consumed"):
         machine.wait_until_succeeds(
-            "(($(curl -u admin:admin -fs localhost:28981/api/documents/ | jq .count) == 1))"
+            "(($(curl -u admin:admin -fs localhost:28981/api/documents/ | jq .count) == 2))"
         )
         assert "2005-10-16" in machine.succeed(
             "curl -u admin:admin -fs localhost:28981/api/documents/ | jq '.results | .[0] | .created'"
         )
+        assert "2005-10-16" in machine.succeed(
+            "curl -u admin:admin -fs localhost:28981/api/documents/ | jq '.results | .[1] | .created'"
+        )
   '';
 })
diff --git a/nixos/tests/postfixadmin.nix b/nixos/tests/postfixadmin.nix
new file mode 100644
index 000000000000..aba5e3eed102
--- /dev/null
+++ b/nixos/tests/postfixadmin.nix
@@ -0,0 +1,31 @@
+import ./make-test-python.nix ({ pkgs, ...} : {
+  name = "postfixadmin";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ globin ];
+  };
+
+  nodes = {
+    postfixadmin = { config, pkgs, ... }: {
+      services.postfixadmin = {
+        enable = true;
+        hostName = "postfixadmin";
+        setupPasswordFile = pkgs.writeText "insecure-test-setup-pw-file" "$2y$10$r0p63YCjd9rb9nHrV9UtVuFgGTmPDLKu.0UIJoQTkWCZZze2iuB1m";
+      };
+      services.nginx.virtualHosts.postfixadmin = {
+        forceSSL = false;
+        enableACME = false;
+      };
+    };
+  };
+
+  testScript = ''
+    postfixadmin.start
+    postfixadmin.wait_for_unit("postgresql.service")
+    postfixadmin.wait_for_unit("phpfpm-postfixadmin.service")
+    postfixadmin.wait_for_unit("nginx.service")
+    postfixadmin.succeed(
+        "curl -sSfL http://postfixadmin/setup.php -X POST -F 'setup_password=not production'"
+    )
+    postfixadmin.succeed("curl -sSfL http://postfixadmin/ | grep 'Mail admins login here'")
+  '';
+})
diff --git a/nixos/tests/rabbitmq.nix b/nixos/tests/rabbitmq.nix
index 69be29b0f9e5..03f1fa46d29e 100644
--- a/nixos/tests/rabbitmq.nix
+++ b/nixos/tests/rabbitmq.nix
@@ -7,7 +7,10 @@ import ./make-test-python.nix ({ pkgs, ... }: {
   };
 
   machine = {
-    services.rabbitmq.enable = true;
+    services.rabbitmq = {
+      enable = true;
+      managementPlugin.enable = true;
+    };
     # Ensure there is sufficient extra disk space for rabbitmq to be happy
     virtualisation.diskSize = 1024;
   };
@@ -19,5 +22,6 @@ import ./make-test-python.nix ({ pkgs, ... }: {
     machine.wait_until_succeeds(
         'su -s ${pkgs.runtimeShell} rabbitmq -c "rabbitmqctl status"'
     )
+    machine.wait_for_open_port("15672")
   '';
 })
diff --git a/nixos/tests/xautolock.nix b/nixos/tests/xautolock.nix
index 2d29f80b3fee..529567e07971 100644
--- a/nixos/tests/xautolock.nix
+++ b/nixos/tests/xautolock.nix
@@ -4,7 +4,7 @@ with lib;
 
 {
   name = "xautolock";
-  meta.maintainers = with pkgs.lib.maintainers; [ ma27 ];
+  meta.maintainers = with pkgs.lib.maintainers; [ ];
 
   nodes.machine = {
     imports = [ ./common/x11.nix ./common/user-account.nix ];
diff --git a/nixos/tests/xss-lock.nix b/nixos/tests/xss-lock.nix
index 71f56e32c58a..c927d9274e65 100644
--- a/nixos/tests/xss-lock.nix
+++ b/nixos/tests/xss-lock.nix
@@ -4,7 +4,7 @@ with lib;
 
 {
   name = "xss-lock";
-  meta.maintainers = with pkgs.lib.maintainers; [ ma27 ];
+  meta.maintainers = with pkgs.lib.maintainers; [ ];
 
   nodes = {
     simple = {
diff --git a/nixos/tests/yabar.nix b/nixos/tests/yabar.nix
index 545fe544d534..c2431e556c37 100644
--- a/nixos/tests/yabar.nix
+++ b/nixos/tests/yabar.nix
@@ -5,7 +5,7 @@ with lib;
 {
   name = "yabar";
   meta = with pkgs.lib.maintainers; {
-    maintainers = [ ma27 ];
+    maintainers = [ ];
   };
 
   machine = {