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/administration/boot-problems.xml31
-rw-r--r--nixos/doc/manual/installation/installing-from-other-distro.xml2
-rw-r--r--nixos/doc/manual/release-notes/rl-2009.xml123
-rw-r--r--nixos/lib/make-disk-image.nix3
-rw-r--r--nixos/lib/make-iso9660-image.nix2
-rw-r--r--nixos/lib/make-iso9660-image.sh2
-rw-r--r--nixos/modules/config/i18n.nix3
-rw-r--r--nixos/modules/config/users-groups.nix32
-rw-r--r--nixos/modules/installer/cd-dvd/sd-image.nix2
-rw-r--r--nixos/modules/misc/ids.nix2
-rw-r--r--nixos/modules/module-list.nix5
-rw-r--r--nixos/modules/programs/hamster.nix15
-rw-r--r--nixos/modules/programs/ssh.nix30
-rw-r--r--nixos/modules/programs/ssmtp.nix77
-rw-r--r--nixos/modules/rename.nix6
-rw-r--r--nixos/modules/security/pam.nix4
-rw-r--r--nixos/modules/services/databases/mysql.nix44
-rw-r--r--nixos/modules/services/databases/redis.nix2
-rw-r--r--nixos/modules/services/games/teeworlds.nix119
-rw-r--r--nixos/modules/services/hardware/fwupd.nix35
-rw-r--r--nixos/modules/services/hardware/u2f.nix23
-rw-r--r--nixos/modules/services/mail/dovecot.nix35
-rw-r--r--nixos/modules/services/mail/opensmtpd.nix23
-rw-r--r--nixos/modules/services/misc/matrix-synapse.nix13
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters.nix3
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/apcupsd.nix38
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/keylight.nix19
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/lnd.nix46
-rw-r--r--nixos/modules/services/network-filesystems/ipfs.nix153
-rw-r--r--nixos/modules/services/networking/corerad.nix45
-rw-r--r--nixos/modules/services/networking/dnschain.nix184
-rw-r--r--nixos/modules/services/networking/go-neb.nix53
-rw-r--r--nixos/modules/services/networking/namecoind.nix5
-rw-r--r--nixos/modules/services/security/physlock.nix1
-rw-r--r--nixos/modules/services/torrent/transmission.nix2
-rw-r--r--nixos/modules/services/x11/desktop-managers/gnome3.nix4
-rw-r--r--nixos/modules/services/x11/xserver.nix2
-rw-r--r--nixos/modules/system/boot/kernel_config.nix5
-rw-r--r--nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py20
-rw-r--r--nixos/modules/system/boot/networkd.nix10
-rw-r--r--nixos/modules/system/boot/resolved.nix1
-rw-r--r--nixos/modules/system/boot/systemd.nix9
-rw-r--r--nixos/modules/system/boot/timesyncd.nix1
-rw-r--r--nixos/modules/tasks/network-interfaces-scripted.nix24
-rw-r--r--nixos/modules/virtualisation/lxd.nix17
-rw-r--r--nixos/modules/virtualisation/qemu-vm.nix2
-rw-r--r--nixos/tests/all-tests.nix3
-rw-r--r--nixos/tests/corerad.nix35
-rw-r--r--nixos/tests/go-neb.nix44
-rw-r--r--nixos/tests/hydra/db-migration.nix6
-rw-r--r--nixos/tests/ipfs.nix7
-rw-r--r--nixos/tests/lxd-nftables.nix50
-rw-r--r--nixos/tests/lxd.nix135
-rw-r--r--nixos/tests/matrix-synapse.nix4
-rw-r--r--nixos/tests/prometheus-exporters.nix78
-rw-r--r--nixos/tests/systemd-boot.nix57
-rw-r--r--nixos/tests/teeworlds.nix55
57 files changed, 1306 insertions, 445 deletions
diff --git a/nixos/doc/manual/administration/boot-problems.xml b/nixos/doc/manual/administration/boot-problems.xml
index 5fa0b29e6d62..badc374ebcfd 100644
--- a/nixos/doc/manual/administration/boot-problems.xml
+++ b/nixos/doc/manual/administration/boot-problems.xml
@@ -19,9 +19,9 @@
     </term>
     <listitem>
      <para>
-      Start a root shell if something goes wrong in stage 1 of the boot process
-      (the initial ramdisk). This is disabled by default because there is no
-      authentication for the root shell.
+      Allows the user to start a root shell if something goes wrong in stage 1
+      of the boot process (the initial ramdisk). This is disabled by default
+      because there is no authentication for the root shell.
      </para>
     </listitem>
    </varlistentry>
@@ -51,6 +51,22 @@
    </varlistentry>
    <varlistentry>
     <term>
+     <literal>boot.debug1mounts</literal>
+    </term>
+    <listitem>
+     <para>
+      Like <literal>boot.debug1</literal> or
+      <literal>boot.debug1devices</literal>, but runs stage1 until all
+      filesystems that are mounted during initrd are mounted (see
+      <option><link linkend="opt-fileSystems._name__.neededForBoot">neededForBoot</link></option>
+      ). As a motivating example, this could be useful if you've forgotten to set
+      <option><link linkend="opt-fileSystems._name__.neededForBoot">neededForBoot</link></option>
+      on a file system.
+     </para>
+    </listitem>
+   </varlistentry>
+   <varlistentry>
+    <term>
      <literal>boot.trace</literal>
     </term>
     <listitem>
@@ -91,6 +107,15 @@
  </para>
 
  <para>
+  Notice that for <literal>boot.shell_on_fail</literal>,
+  <literal>boot.debug1</literal>, <literal>boot.debug1devices</literal>, and
+  <literal>boot.debug1mounts</literal>, if you did <emphasis>not</emphasis>
+  select "start the new shell as pid 1", and you <literal>exit</literal> from
+  the new shell, boot will proceed normally from the point where it failed, as
+  if you'd chosen "ignore the error and continue".
+ </para>
+
+ <para>
   If no login prompts or X11 login screens appear (e.g. due to hanging
   dependencies), you can press Alt+ArrowUp. If you’re lucky, this will start
   rescue mode (described above). (Also note that since most units have a
diff --git a/nixos/doc/manual/installation/installing-from-other-distro.xml b/nixos/doc/manual/installation/installing-from-other-distro.xml
index 8ed45899fd7f..45d68f8787f1 100644
--- a/nixos/doc/manual/installation/installing-from-other-distro.xml
+++ b/nixos/doc/manual/installation/installing-from-other-distro.xml
@@ -89,7 +89,7 @@ nixpkgs https://nixos.org/channels/nixpkgs-unstable</screen>
     NixOS partition. They are installed by default on NixOS, but you don't have
     NixOS yet..
    </para>
-<screen><prompt>$ </prompt>nix-env -iE "_: with import &lt;nixpkgs/nixos&gt; { configuration = {}; }; with config.system.build; [ nixos-generate-config nixos-install nixos-enter manual.manpages ]"</screen>
+<screen><prompt>$ </prompt>nix-env -f '&lt;nixpkgs/nixos&gt;' --arg configuration {} -iA config.system.build.{nixos-generate-config,nixos-install,nixos-enter,manual.manpages}</screen>
   </listitem>
   <listitem>
    <note>
diff --git a/nixos/doc/manual/release-notes/rl-2009.xml b/nixos/doc/manual/release-notes/rl-2009.xml
index 8bf9c16e6f8a..900b1103f2b1 100644
--- a/nixos/doc/manual/release-notes/rl-2009.xml
+++ b/nixos/doc/manual/release-notes/rl-2009.xml
@@ -44,6 +44,11 @@
    </listitem>
    <listitem>
     <para>
+     Python 3 now defaults to Python 3.8 instead of 3.7.
+    </para>
+   </listitem>
+   <listitem>
+    <para>
      Two new options, <link linkend="opt-services.openssh.authorizedKeysCommand">authorizedKeysCommand</link>
      and <link linkend="opt-services.openssh.authorizedKeysCommandUser">authorizedKeysCommandUser</link>, have
      been added to the <literal>openssh</literal> module. If you have <literal>AuthorizedKeysCommand</literal>
@@ -89,6 +94,22 @@ services.mysql.initialScript = pkgs.writeText "mariadb-init.sql" ''
       When MariaDB data directory is just upgraded (not initialized), the users are not created or modified.
     </para>
    </listitem>
+   <listitem>
+    <para>
+      MySQL server is now started with additional systemd sandbox/hardening options for better security. The PrivateTmp, ProtectHome, and ProtectSystem options
+      may be problematic when MySQL is attempting to read from or write to your filesystem anywhere outside of its own state directory, for example when
+      calling <literal>LOAD DATA INFILE or SELECT * INTO OUTFILE</literal>. In this scenario a variant of the following may be required:
+        - allow MySQL to read from /home and /tmp directories when using <literal>LOAD DATA INFILE</literal>
+<programlisting>
+systemd.services.mysql.serviceConfig.ProtectHome = lib.mkForce "read-only";
+</programlisting>
+        - allow MySQL to write to custom folder <literal>/var/data</literal> when using <literal>SELECT * INTO OUTFILE</literal>, assuming the mysql user has write
+          access to <literal>/var/data</literal>
+<programlisting>
+systemd.services.mysql.serviceConfig.ReadWritePaths = [ "/var/data" ];
+</programlisting>
+    </para>
+   </listitem>
   </itemizedlist>
  </section>
 
@@ -178,6 +199,12 @@ services.mysql.initialScript = pkgs.writeText "mariadb-init.sql" ''
    </listitem>
    <listitem>
     <para>
+      <literal>vmware</literal> has been removed from the <literal>services.x11.videoDrivers</literal> defaults.
+      For VMWare guests set <literal>virtualisation.vmware.guest.enable</literal> to <literal>true</literal> which will include the appropriate drivers.
+    </para>
+   </listitem>
+   <listitem>
+    <para>
      The initrd SSH support now uses OpenSSH rather than Dropbear to
      allow the use of Ed25519 keys and other OpenSSH-specific
      functionality. Host keys must now be in the OpenSSH format, and at
@@ -455,6 +482,14 @@ systemd.services.nginx.serviceConfig.ReadWritePaths = [ "/var/www" ];
      initrd file has not been deleted.
     </para>
    </listitem>
+   <listitem>
+    <para>
+     The <link xlink:href="https://github.com/okTurtles/dnschain">DNSChain</link>
+     package and NixOS module have been removed from Nixpkgs as the software is
+     unmaintained and can't be built. For more information see issue
+     <link xlink:href="https://github.com/NixOS/nixpkgs/issues/89205">#89205</link>.
+    </para>
+   </listitem>
   </itemizedlist>
  </section>
 
@@ -467,7 +502,7 @@ systemd.services.nginx.serviceConfig.ReadWritePaths = [ "/var/www" ];
 
   <itemizedlist>
    <listitem>
-     <para>SD and ISO images are now compressed by default using <literal>zstd</literal>.</para>
+    <para>SD images are now compressed by default using <literal>zstd</literal>. The compression for ISO images has also been changed to <literal>zstd</literal>, but ISO images are still not compressed by default.</para>
    </listitem>
    <listitem>
     <para>
@@ -486,8 +521,31 @@ systemd.services.nginx.serviceConfig.ReadWritePaths = [ "/var/www" ];
     </para>
    </listitem>
    <listitem>
+    <para>
+      The default output of <literal>buildGoPackage</literal> is now <literal>$out</literal> instead of <literal>$bin</literal>.
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     Packages built using <literal>buildRustPackage</literal> now use <literal>release</literal>
+     mode for the <literal>checkPhase</literal> by default.
+    </para>
+    <para>
+     Please note that Rust packages utilizing a custom build/install procedure
+     (e.g. by using a <filename>Makefile</filename>) or test suites that rely on the
+     structure of the <filename>target/</filename> directory may break due to those assumptions.
+     For further information, please read the Rust section in the Nixpkgs manual.
+    </para>
+   </listitem>
+   <listitem>
    <para>
-     The default output of <literal>buildGoPackage</literal> is now <literal>$out</literal> instead of <literal>$bin</literal>.
+     The cc- and binutils-wrapper's "infix salt" and <literal>_BUILD_</literal> and <literal>_TARGET_</literal> user infixes have been replaced with with a "suffix salt" and suffixes and <literal>_FOR_BUILD</literal> and <literal>_FOR_TARGET</literal>.
+      This matches the autotools convention for env vars which standard for these things, making interfacing with other tools easier.
+   </para>
+   </listitem>
+   <listitem>
+   <para>
+     Additional Git documentation (HTML and text files) is now available via the <literal>git-doc</literal> package.
    </para>
    </listitem>
    <listitem>
@@ -518,6 +576,67 @@ systemd.services.nginx.serviceConfig.ReadWritePaths = [ "/var/www" ];
      <link xlink:href="https://grafana.com/docs/grafana/latest/installation/upgrading/#upgrading-to-v7-0">in the Grafana documentation</link>.
     </para>
    </listitem>
+   <listitem>
+    <para>
+     The <literal>hardware.u2f</literal> module, which was installing udev rules
+     was removed, as udev gained native support to handle FIDO security tokens.
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     With this release <literal>systemd-networkd</literal> (when enabled through <xref linkend="opt-networking.useNetworkd"/>)
+     has it's netlink socket created through a <literal>systemd.socket</literal> unit. This gives us control over
+     socket buffer sizes and other parameters. For larger setups where networkd has to create a lot of (virtual)
+     devices the default buffer size (currently 128MB) is not enough.
+    </para>
+    <para>
+     On a machine with &gt;100 virtual interfaces (e.g., wireguard tunnels, VLANs, …), that all have to
+     be brought up during system startup, the receive buffer size will spike for a brief period.
+     Eventually some of the message will be dropped since there is not enough (permitted) buffer
+     space available.
+    </para>
+    <para>
+     By having <literal>systemd-networkd</literal> start with a netlink socket created by
+     <literal>systemd</literal> we can configure the <literal>ReceiveBufferSize=</literal> parameter
+     in the socket options (i.e. <literal>systemd.sockets.systemd-networkd.socketOptions.ReceiveBufferSize</literal>)
+     without recompiling <literal>systemd-networkd</literal>.
+    </para>
+    <para>
+     Since the actual memory requirements depend on hardware, timing, exact
+     configurations etc. it isn't currently possible to infer a good default
+     from within the NixOS module system. Administrators are advised to
+     monitor the logs of <literal>systemd-networkd</literal> for <literal>rtnl: kernel receive buffer
+     overrun</literal> spam and increase the memory limit as they see fit.
+    </para>
+    <para>
+     Note: Increasing the <literal>ReceiveBufferSize=</literal> doesn't allocate any memory. It just increases
+     the upper bound on the kernel side. The memory allocation depends on the amount of messages that are
+     queued on the kernel side of the netlink socket.
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     Specifying <link linkend="opt-services.dovecot2.mailboxes">mailboxes</link> in the <package>dovecot2</package> module
+     as a list is deprecated and will break eval in 21.03. Instead, an attribute-set should be specified where the <literal>name</literal>
+     should be the key of the attribute.
+    </para>
+    <para>
+     This means that a configuration like this
+<programlisting>{
+  <link linkend="opt-services.dovecot2.mailboxes">services.dovecot2.mailboxes</link> = [
+    { name = "Junk";
+      auto = "create";
+    }
+  ];
+}</programlisting>
+    should now look like this:
+<programlisting>{
+  <link linkend="opt-services.dovecot2.mailboxes">services.dovecot2.mailboxes</link> = {
+    Junk.auto = "create";
+  };
+}</programlisting>
+    </para>
+   </listitem>
   </itemizedlist>
  </section>
 </section>
diff --git a/nixos/lib/make-disk-image.nix b/nixos/lib/make-disk-image.nix
index 5e86ea479d51..9364aab32770 100644
--- a/nixos/lib/make-disk-image.nix
+++ b/nixos/lib/make-disk-image.nix
@@ -39,7 +39,7 @@
 
 , name ? "nixos-disk-image"
 
-, # Disk image format, one of qcow2, qcow2-compressed, vpc, raw.
+, # Disk image format, one of qcow2, qcow2-compressed, vdi, vpc, raw.
   format ? "raw"
 }:
 
@@ -57,6 +57,7 @@ let format' = format; in let
 
   filename = "nixos." + {
     qcow2 = "qcow2";
+    vdi   = "vdi";
     vpc   = "vhd";
     raw   = "img";
   }.${format};
diff --git a/nixos/lib/make-iso9660-image.nix b/nixos/lib/make-iso9660-image.nix
index be8ad9912db0..12a6cf649966 100644
--- a/nixos/lib/make-iso9660-image.nix
+++ b/nixos/lib/make-iso9660-image.nix
@@ -35,7 +35,7 @@
   isohybridMbrImage ? ""
 
 , # Whether to compress the resulting ISO image with zstd.
-  compressImage ? false
+  compressImage ? false, zstd
 
 , # The volume ID.
   volumeID ? ""
diff --git a/nixos/lib/make-iso9660-image.sh b/nixos/lib/make-iso9660-image.sh
index eb9edcd70269..d64fe9a365e7 100644
--- a/nixos/lib/make-iso9660-image.sh
+++ b/nixos/lib/make-iso9660-image.sh
@@ -129,7 +129,7 @@ fi
 
 if test -n "$compressImage"; then
     echo "Compressing image..."
-    zstd -T$NIX_BUILD_CORES $out/iso/$isoName
+    zstd -T$NIX_BUILD_CORES --rm $out/iso/$isoName
 fi
 
 mkdir -p $out/nix-support
diff --git a/nixos/modules/config/i18n.nix b/nixos/modules/config/i18n.nix
index cc2ddda9d32f..feb76581a720 100644
--- a/nixos/modules/config/i18n.nix
+++ b/nixos/modules/config/i18n.nix
@@ -68,7 +68,8 @@ with lib;
   config = {
 
     environment.systemPackages =
-      optional (config.i18n.supportedLocales != []) config.i18n.glibcLocales;
+      # We increase the priority a little, so that plain glibc in systemPackages can't win.
+      optional (config.i18n.supportedLocales != []) (lib.setPrio (-1) config.i18n.glibcLocales);
 
     environment.sessionVariables =
       { LANG = config.i18n.defaultLocale;
diff --git a/nixos/modules/config/users-groups.nix b/nixos/modules/config/users-groups.nix
index 141e43fec39b..7fbbfcec7510 100644
--- a/nixos/modules/config/users-groups.nix
+++ b/nixos/modules/config/users-groups.nix
@@ -600,6 +600,38 @@ in {
       }
     ];
 
+    warnings =
+      builtins.filter (x: x != null) (
+        flip mapAttrsToList cfg.users (name: user:
+        # This regex matches a subset of the Modular Crypto Format (MCF)[1]
+        # informal standard. Since this depends largely on the OS or the
+        # specific implementation of crypt(3) we only support the (sane)
+        # schemes implemented by glibc and BSDs. In particular the original
+        # DES hash is excluded since, having no structure, it would validate
+        # common mistakes like typing the plaintext password.
+        #
+        # [1]: https://en.wikipedia.org/wiki/Crypt_(C)
+        let
+          sep = "\\$";
+          base64 = "[a-zA-Z0-9./]+";
+          id = "[a-z0-9-]+";
+          value = "[a-zA-Z0-9/+.-]+";
+          options = "${id}(=${value})?(,${id}=${value})*";
+          scheme  = "${id}(${sep}${options})?";
+          content = "${base64}${sep}${base64}";
+          mcf = "^${sep}${scheme}${sep}${content}$";
+        in
+        if (user.hashedPassword != null
+            && builtins.match mcf user.hashedPassword == null)
+        then
+        ''
+          The password hash of user "${name}" may be invalid. You must set a
+          valid hash or the user will be locked out of his account. Please
+          check the value of option `users.users."${name}".hashedPassword`.
+        ''
+        else null
+      ));
+
   };
 
 }
diff --git a/nixos/modules/installer/cd-dvd/sd-image.nix b/nixos/modules/installer/cd-dvd/sd-image.nix
index 589bf74263b9..98c12e30633c 100644
--- a/nixos/modules/installer/cd-dvd/sd-image.nix
+++ b/nixos/modules/installer/cd-dvd/sd-image.nix
@@ -190,7 +190,7 @@ in
         fsck.vfat -vn firmware_part.img
         dd conv=notrunc if=firmware_part.img of=$img seek=$START count=$SECTORS
         if test -n "$compressImage"; then
-            zstd -T$NIX_BUILD_CORES $img
+            zstd -T$NIX_BUILD_CORES --rm $img
         fi
       '';
     }) {};
diff --git a/nixos/modules/misc/ids.nix b/nixos/modules/misc/ids.nix
index 85e5534e906f..3409e7ba22ea 100644
--- a/nixos/modules/misc/ids.nix
+++ b/nixos/modules/misc/ids.nix
@@ -239,7 +239,6 @@ in
       shout = 206;
       gateone = 207;
       namecoin = 208;
-      dnschain = 209;
       #lxd = 210; # unused
       kibana = 211;
       xtreemfs = 212;
@@ -549,7 +548,6 @@ in
       #shout = 206; #unused
       gateone = 207;
       namecoin = 208;
-      #dnschain = 209; #unused
       lxd = 210; # unused
       #kibana = 211;
       xtreemfs = 212;
diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix
index 978d33e75852..448d432853d3 100644
--- a/nixos/modules/module-list.nix
+++ b/nixos/modules/module-list.nix
@@ -126,6 +126,7 @@
   ./programs/gpaste.nix
   ./programs/gnupg.nix
   ./programs/gphoto2.nix
+  ./programs/hamster.nix
   ./programs/iftop.nix
   ./programs/iotop.nix
   ./programs/java.nix
@@ -334,6 +335,7 @@
   ./services/games/minecraft-server.nix
   ./services/games/minetest-server.nix
   ./services/games/openarena.nix
+  ./services/games/teeworlds.nix
   ./services/games/terraria.nix
   ./services/hardware/acpid.nix
   ./services/hardware/actkbd.nix
@@ -361,7 +363,6 @@
   ./services/hardware/throttled.nix
   ./services/hardware/trezord.nix
   ./services/hardware/triggerhappy.nix
-  ./services/hardware/u2f.nix
   ./services/hardware/udev.nix
   ./services/hardware/udisks2.nix
   ./services/hardware/upower.nix
@@ -601,7 +602,6 @@
   ./services/networking/dhcpcd.nix
   ./services/networking/dhcpd.nix
   ./services/networking/dnscache.nix
-  ./services/networking/dnschain.nix
   ./services/networking/dnscrypt-proxy2.nix
   ./services/networking/dnscrypt-wrapper.nix
   ./services/networking/dnsdist.nix
@@ -624,6 +624,7 @@
   ./services/networking/gdomap.nix
   ./services/networking/git-daemon.nix
   ./services/networking/gnunet.nix
+  ./services/networking/go-neb.nix
   ./services/networking/go-shadowsocks2.nix
   ./services/networking/gogoclient.nix
   ./services/networking/gvpe.nix
diff --git a/nixos/modules/programs/hamster.nix b/nixos/modules/programs/hamster.nix
new file mode 100644
index 000000000000..ddf26a22fb53
--- /dev/null
+++ b/nixos/modules/programs/hamster.nix
@@ -0,0 +1,15 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+  meta.maintainers = maintainers.fabianhauser;
+
+  options.programs.hamster.enable =
+    mkEnableOption "Whether to enable hamster time tracking.";
+
+  config = lib.mkIf config.programs.hamster.enable {
+    environment.systemPackages = [ pkgs.hamster ];
+    services.dbus.packages = [ pkgs.hamster ];
+  };
+}
diff --git a/nixos/modules/programs/ssh.nix b/nixos/modules/programs/ssh.nix
index 44e65ee8a9a0..a983ffa4b890 100644
--- a/nixos/modules/programs/ssh.nix
+++ b/nixos/modules/programs/ssh.nix
@@ -194,6 +194,33 @@ in
         '';
       };
 
+      kexAlgorithms = mkOption {
+        type = types.nullOr (types.listOf types.str);
+        default = null;
+        example = [ "curve25519-sha256@libssh.org" "diffie-hellman-group-exchange-sha256" ];
+        description = ''
+          Specifies the available KEX (Key Exchange) algorithms.
+        '';
+      };
+
+      ciphers = mkOption {
+        type = types.nullOr (types.listOf types.str);
+        default = null;
+        example = [ "chacha20-poly1305@openssh.com" "aes256-gcm@openssh.com" ];
+        description = ''
+          Specifies the ciphers allowed and their order of preference.
+        '';
+      };
+
+      macs = mkOption {
+        type = types.nullOr (types.listOf types.str);
+        default = null;
+        example = [ "hmac-sha2-512-etm@openssh.com" "hmac-sha1" ];
+        description = ''
+          Specifies the MAC (message authentication code) algorithms in order of preference. The MAC algorithm is used
+          for data integrity protection.
+        '';
+      };
     };
 
   };
@@ -232,6 +259,9 @@ in
 
         ${optionalString (cfg.pubkeyAcceptedKeyTypes != []) "PubkeyAcceptedKeyTypes ${concatStringsSep "," cfg.pubkeyAcceptedKeyTypes}"}
         ${optionalString (cfg.hostKeyAlgorithms != []) "HostKeyAlgorithms ${concatStringsSep "," cfg.hostKeyAlgorithms}"}
+        ${optionalString (cfg.kexAlgorithms != null) "KexAlgorithms ${concatStringsSep "," cfg.kexAlgorithms}"}
+        ${optionalString (cfg.ciphers != null) "Ciphers ${concatStringsSep "," cfg.ciphers}"}
+        ${optionalString (cfg.macs != null) "MACs ${concatStringsSep "," cfg.macs}"}
       '';
 
     environment.etc."ssh/ssh_known_hosts".text = knownHostsText;
diff --git a/nixos/modules/programs/ssmtp.nix b/nixos/modules/programs/ssmtp.nix
index c7a947393496..15d2750c193f 100644
--- a/nixos/modules/programs/ssmtp.nix
+++ b/nixos/modules/programs/ssmtp.nix
@@ -21,9 +21,11 @@ in
     (mkRenamedOptionModule [ "networking" "defaultMailServer" "useTLS" ] [ "services" "ssmtp" "useTLS" ])
     (mkRenamedOptionModule [ "networking" "defaultMailServer" "useSTARTTLS" ] [ "services" "ssmtp" "useSTARTTLS" ])
     (mkRenamedOptionModule [ "networking" "defaultMailServer" "authUser" ] [ "services" "ssmtp" "authUser" ])
-    (mkRenamedOptionModule [ "networking" "defaultMailServer" "authPass" ] [ "services" "ssmtp" "authPass" ])
     (mkRenamedOptionModule [ "networking" "defaultMailServer" "authPassFile" ] [ "services" "ssmtp" "authPassFile" ])
     (mkRenamedOptionModule [ "networking" "defaultMailServer" "setSendmail" ] [ "services" "ssmtp" "setSendmail" ])
+
+    (mkRemovedOptionModule [ "networking" "defaultMailServer" "authPass" ] "authPass has been removed since it leaks the clear-text password into the world-readable store. Use authPassFile instead and make sure it's not a store path")
+    (mkRemovedOptionModule [ "services" "ssmtp" "authPass" ] "authPass has been removed since it leaks the clear-text password into the world-readable store. Use authPassFile instead and make sure it's not a store path")
   ];
 
   options = {
@@ -45,6 +47,21 @@ in
         '';
       };
 
+      settings = mkOption {
+        type = with types; attrsOf (oneOf [ bool str ]);
+        default = {};
+        description = ''
+          <citerefentry><refentrytitle>ssmtp</refentrytitle><manvolnum>5</manvolnum></citerefentry> configuration. Refer
+          to <link xlink:href="https://linux.die.net/man/5/ssmtp.conf"/> for details on supported values.
+        '';
+        example = literalExample ''
+          {
+            Debug = true;
+            FromLineOverride = false;
+          }
+        '';
+      };
+
       hostName = mkOption {
         type = types.str;
         example = "mail.example.org";
@@ -101,18 +118,6 @@ in
         '';
       };
 
-      authPass = mkOption {
-        type = types.str;
-        default = "";
-        example = "correctHorseBatteryStaple";
-        description = ''
-          Password used for SMTP auth. (STORED PLAIN TEXT, WORLD-READABLE IN NIX STORE)
-
-          It's recommended to use <option>authPassFile</option>
-          which takes precedence over <option>authPass</option>.
-        '';
-      };
-
       authPassFile = mkOption {
         type = types.nullOr types.str;
         default = null;
@@ -121,11 +126,6 @@ in
           Path to a file that contains the password used for SMTP auth. The file
           should not contain a trailing newline, if the password does not contain one.
           This file should be readable by the users that need to execute ssmtp.
-
-          <option>authPassFile</option> takes precedence over <option>authPass</option>.
-
-          Warning: when <option>authPass</option> is non-empty <option>authPassFile</option>
-          defaults to a file in the WORLD-READABLE Nix store containing that password.
         '';
       };
 
@@ -142,25 +142,28 @@ in
 
   config = mkIf cfg.enable {
 
-    services.ssmtp.authPassFile = mkIf (cfg.authPass != "")
-      (mkDefault (toString (pkgs.writeTextFile {
-        name = "ssmtp-authpass";
-        text = cfg.authPass;
-      })));
-
-    environment.etc."ssmtp/ssmtp.conf".text =
-      let yesNo = yes : if yes then "YES" else "NO"; in
-      ''
-        MailHub=${cfg.hostName}
-        FromLineOverride=YES
-        ${optionalString (cfg.root   != "") "root=${cfg.root}"}
-        ${optionalString (cfg.domain != "") "rewriteDomain=${cfg.domain}"}
-        UseTLS=${yesNo cfg.useTLS}
-        UseSTARTTLS=${yesNo cfg.useSTARTTLS}
-        #Debug=YES
-        ${optionalString (cfg.authUser != "")       "AuthUser=${cfg.authUser}"}
-        ${optionalString (cfg.authPassFile != null) "AuthPassFile=${cfg.authPassFile}"}
-      '';
+    services.ssmtp.settings = mkMerge [
+      ({
+        MailHub = cfg.hostName;
+        FromLineOverride = mkDefault true;
+        UseTLS = cfg.useTLS;
+        UseSTARTTLS = cfg.useSTARTTLS;
+      })
+      (mkIf (cfg.root != "") { root = cfg.root; })
+      (mkIf (cfg.domain != "") { rewriteDomain = cfg.domain; })
+      (mkIf (cfg.authUser != "") { AuthUser = cfg.authUser; })
+      (mkIf (cfg.authPassFile != null) { AuthPassFile = cfg.authPassFile; })
+    ];
+
+    environment.etc."ssmtp/ssmtp.conf".source =
+      let
+        toStr = value:
+          if value == true then "YES"
+          else if value == false then "NO"
+          else builtins.toString value
+        ;
+      in
+        pkgs.writeText "ssmtp.conf" (concatStringsSep "\n" (mapAttrsToList (key: value: "${key}=${toStr value}") cfg.settings));
 
     environment.systemPackages = [pkgs.ssmtp];
 
diff --git a/nixos/modules/rename.nix b/nixos/modules/rename.nix
index c91febdb710f..1dc7f8533176 100644
--- a/nixos/modules/rename.nix
+++ b/nixos/modules/rename.nix
@@ -55,6 +55,12 @@ with lib;
       prey-bash-client is deprecated upstream
     '')
 
+    (mkRemovedOptionModule ["hardware" "u2f" ] ''
+      The U2F modules module was removed, as all it did was adding the
+      udev rules from libu2f-host to the system. Udev gained native support
+      to handle FIDO security tokens, so this isn't necessary anymore.
+    '')
+
     # Do NOT add any option renames here, see top of the file
   ];
 }
diff --git a/nixos/modules/security/pam.nix b/nixos/modules/security/pam.nix
index e1a94b0121ac..688344852aeb 100644
--- a/nixos/modules/security/pam.nix
+++ b/nixos/modules/security/pam.nix
@@ -436,6 +436,8 @@ let
               "session required ${pkgs.pam}/lib/security/pam_lastlog.so silent"}
           ${optionalString config.security.pam.enableEcryptfs
               "session optional ${pkgs.ecryptfs}/lib/security/pam_ecryptfs.so"}
+          ${optionalString cfg.pamMount
+              "session optional ${pkgs.pam_mount}/lib/security/pam_mount.so"}
           ${optionalString use_ldap
               "session optional ${pam_ldap}/lib/security/pam_ldap.so"}
           ${optionalString config.services.sssd.enable
@@ -452,8 +454,6 @@ let
               "session required ${pkgs.pam}/lib/security/pam_limits.so conf=${makeLimitsConf cfg.limits}"}
           ${optionalString (cfg.showMotd && config.users.motd != null)
               "session optional ${pkgs.pam}/lib/security/pam_motd.so motd=${motd}"}
-          ${optionalString cfg.pamMount
-              "session optional ${pkgs.pam_mount}/lib/security/pam_mount.so"}
           ${optionalString (cfg.enableAppArmor && config.security.apparmor.enable)
               "session optional ${pkgs.apparmor-pam}/lib/security/pam_apparmor.so order=user,group,default debug"}
           ${optionalString (cfg.enableKwallet)
diff --git a/nixos/modules/services/databases/mysql.nix b/nixos/modules/services/databases/mysql.nix
index 51885881cf73..2e8c5b7640b2 100644
--- a/nixos/modules/services/databases/mysql.nix
+++ b/nixos/modules/services/databases/mysql.nix
@@ -334,7 +334,8 @@ in
     environment.etc."my.cnf".source = cfg.configFile;
 
     systemd.tmpfiles.rules = [
-      "d '${cfg.dataDir}' 0700 ${cfg.user} mysql -"
+      "d '${cfg.dataDir}' 0700 ${cfg.user} mysql - -"
+      "z '${cfg.dataDir}' 0700 ${cfg.user} mysql - -"
     ];
 
     systemd.services.mysql = let
@@ -357,21 +358,17 @@ in
         preStart = if isMariaDB then ''
           if ! test -e ${cfg.dataDir}/mysql; then
             ${mysql}/bin/mysql_install_db --defaults-file=/etc/my.cnf ${mysqldOptions}
-            touch /tmp/mysql_init
+            touch ${cfg.dataDir}/mysql_init
           fi
         '' else ''
           if ! test -e ${cfg.dataDir}/mysql; then
             ${mysql}/bin/mysqld --defaults-file=/etc/my.cnf ${mysqldOptions} --initialize-insecure
-            touch /tmp/mysql_init
+            touch ${cfg.dataDir}/mysql_init
           fi
         '';
 
         serviceConfig = {
-          User = cfg.user;
-          Group = "mysql";
           Type = if hasNotify then "notify" else "simple";
-          RuntimeDirectory = "mysqld";
-          RuntimeDirectoryMode = "0755";
           Restart = "on-abort";
           RestartSec = "5s";
           # The last two environment variables are used for starting Galera clusters
@@ -398,7 +395,7 @@ in
                   done
                 ''}
 
-                if [ -f /tmp/mysql_init ]
+                if [ -f ${cfg.dataDir}/mysql_init ]
                 then
                     ${concatMapStrings (database: ''
                       # Create initial databases
@@ -452,7 +449,7 @@ in
                         cat ${toString cfg.initialScript} | ${mysql}/bin/mysql -u root -N
                       ''}
 
-                    rm /tmp/mysql_init
+                    rm ${cfg.dataDir}/mysql_init
                 fi
 
                 ${optionalString (cfg.ensureDatabases != []) ''
@@ -476,6 +473,35 @@ in
               # ensureDatbases & ensureUsers depends on this script being run as root
               # when the user has secured their mysql install
               "+${setupScript}";
+          # User and group
+          User = cfg.user;
+          Group = "mysql";
+          # Runtime directory and mode
+          RuntimeDirectory = "mysqld";
+          RuntimeDirectoryMode = "0755";
+          # Access write directories
+          ReadWritePaths = [ cfg.dataDir ];
+          # Capabilities
+          CapabilityBoundingSet = "";
+          # Security
+          NoNewPrivileges = true;
+          # Sandboxing
+          ProtectSystem = "strict";
+          ProtectHome = true;
+          PrivateTmp = true;
+          PrivateDevices = true;
+          ProtectHostname = true;
+          ProtectKernelTunables = true;
+          ProtectKernelModules = true;
+          ProtectControlGroups = true;
+          RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" ];
+          LockPersonality = true;
+          MemoryDenyWriteExecute = true;
+          RestrictRealtime = true;
+          RestrictSUIDSGID = true;
+          PrivateMounts = true;
+          # System Call Filtering
+          SystemCallArchitectures = "native";
         };
       };
 
diff --git a/nixos/modules/services/databases/redis.nix b/nixos/modules/services/databases/redis.nix
index 799c3db62166..f1777854e141 100644
--- a/nixos/modules/services/databases/redis.nix
+++ b/nixos/modules/services/databases/redis.nix
@@ -218,6 +218,7 @@ in
       description = "Redis database user";
       isSystemUser = true;
     };
+    users.groups.redis = {};
 
     environment.systemPackages = [ cfg.package ];
 
@@ -240,6 +241,7 @@ in
         StateDirectory = "redis";
         Type = "notify";
         User = "redis";
+        Group = "redis";
       };
     };
   };
diff --git a/nixos/modules/services/games/teeworlds.nix b/nixos/modules/services/games/teeworlds.nix
new file mode 100644
index 000000000000..babf989c98ca
--- /dev/null
+++ b/nixos/modules/services/games/teeworlds.nix
@@ -0,0 +1,119 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.teeworlds;
+  register = cfg.register;
+
+  teeworldsConf = pkgs.writeText "teeworlds.cfg" ''
+    sv_port ${toString cfg.port}
+    sv_register ${if cfg.register then "1" else "0"}
+    ${optionalString (cfg.name != null) "sv_name ${cfg.name}"}
+    ${optionalString (cfg.motd != null) "sv_motd ${cfg.motd}"}
+    ${optionalString (cfg.password != null) "password ${cfg.password}"}
+    ${optionalString (cfg.rconPassword != null) "sv_rcon_password ${cfg.rconPassword}"}
+    ${concatStringsSep "\n" cfg.extraOptions}
+  '';
+
+in
+{
+  options = {
+    services.teeworlds = {
+      enable = mkEnableOption "Teeworlds Server";
+
+      openPorts = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Whether to open firewall ports for Teeworlds";
+      };
+
+      name = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = ''
+          Name of the server. Defaults to 'unnamed server'.
+        '';
+      };
+
+      register = mkOption {
+        type = types.bool;
+        example = true;
+        default = false;
+        description = ''
+          Whether the server registers as public server in the global server list. This is disabled by default because of privacy.
+        '';
+      };
+
+      motd = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = ''
+          Set the server message of the day text.
+        '';
+      };
+
+      password = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = ''
+          Password to connect to the server.
+        '';
+      };
+
+      rconPassword = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = ''
+          Password to access the remote console. If not set, a randomly generated one is displayed in the server log.
+        '';
+      };
+
+      port = mkOption {
+        type = types.int;
+        default = 8303;
+        description = ''
+          Port the server will listen on.
+        '';
+      };
+
+      extraOptions = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        description = ''
+          Extra configuration lines for the <filename>teeworlds.cfg</filename>. See <link xlink:href="https://www.teeworlds.com/?page=docs&amp;wiki=server_settings">Teeworlds Documentation</link>.
+        '';
+        example = [ "sv_map dm1" "sv_gametype dm" ];
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    networking.firewall = mkIf cfg.openPorts {
+      allowedUDPPorts = [ cfg.port ];
+    };
+
+    systemd.services.teeworlds = {
+      description = "Teeworlds Server";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+
+      serviceConfig = {
+        DynamicUser = true;
+        ExecStart = "${pkgs.teeworlds}/bin/teeworlds_srv -f ${teeworldsConf}";
+
+        # Hardening
+        CapabilityBoundingSet = false;
+        PrivateDevices = true;
+        PrivateUsers = true;
+        ProtectHome = true;
+        ProtectKernelLogs = true;
+        ProtectKernelModules = true;
+        ProtectKernelTunables = true;
+        RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ];
+        RestrictNamespaces = true;
+        SystemCallArchitectures = "native";
+      };
+    };
+  };
+}
diff --git a/nixos/modules/services/hardware/fwupd.nix b/nixos/modules/services/hardware/fwupd.nix
index e586af25c2b1..222ac8e487eb 100644
--- a/nixos/modules/services/hardware/fwupd.nix
+++ b/nixos/modules/services/hardware/fwupd.nix
@@ -6,6 +6,23 @@ with lib;
 
 let
   cfg = config.services.fwupd;
+
+  customEtc = {
+    "fwupd/daemon.conf" = {
+      source = pkgs.writeText "daemon.conf" ''
+        [fwupd]
+        BlacklistDevices=${lib.concatStringsSep ";" cfg.blacklistDevices}
+        BlacklistPlugins=${lib.concatStringsSep ";" cfg.blacklistPlugins}
+      '';
+    };
+    "fwupd/uefi.conf" = {
+      source = pkgs.writeText "uefi.conf" ''
+        [uefi]
+        OverrideESPMountPoint=${config.boot.loader.efi.efiSysMountPoint}
+      '';
+    };
+  };
+
   originalEtc =
     let
       mkEtcFile = n: nameValuePair n { source = "${cfg.package}/etc/${n}"; };
@@ -96,22 +113,8 @@ in {
 
     environment.systemPackages = [ cfg.package ];
 
-    environment.etc = {
-      "fwupd/daemon.conf" = {
-        source = pkgs.writeText "daemon.conf" ''
-          [fwupd]
-          BlacklistDevices=${lib.concatStringsSep ";" cfg.blacklistDevices}
-          BlacklistPlugins=${lib.concatStringsSep ";" cfg.blacklistPlugins}
-        '';
-      };
-      "fwupd/uefi.conf" = {
-        source = pkgs.writeText "uefi.conf" ''
-          [uefi]
-          OverrideESPMountPoint=${config.boot.loader.efi.efiSysMountPoint}
-        '';
-      };
-
-    } // originalEtc // extraTrustedKeys // testRemote;
+    # customEtc overrides some files from the package
+    environment.etc = originalEtc // customEtc // extraTrustedKeys // testRemote;
 
     services.dbus.packages = [ cfg.package ];
 
diff --git a/nixos/modules/services/hardware/u2f.nix b/nixos/modules/services/hardware/u2f.nix
deleted file mode 100644
index bb4b2f05f890..000000000000
--- a/nixos/modules/services/hardware/u2f.nix
+++ /dev/null
@@ -1,23 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-with lib;
-let
-  cfg = config.hardware.u2f;
-in {
-  options = {
-    hardware.u2f = {
-      enable = mkOption {
-        type = types.bool;
-        default = false;
-        description = ''
-          Enable U2F hardware support.
-        '';
-      };
-    };
-  };
-
-  config = mkIf cfg.enable {
-    services.udev.packages = [ pkgs.libu2f-host ];
-  };
-}
-
diff --git a/nixos/modules/services/mail/dovecot.nix b/nixos/modules/services/mail/dovecot.nix
index 9fbf0c19752c..51cbcbf1cbc8 100644
--- a/nixos/modules/services/mail/dovecot.nix
+++ b/nixos/modules/services/mail/dovecot.nix
@@ -125,6 +125,8 @@ let
   mailboxConfig = mailbox: ''
     mailbox "${mailbox.name}" {
       auto = ${toString mailbox.auto}
+  '' + optionalString (mailbox.autoexpunge != null) ''
+    autoexpunge = ${mailbox.autoexpunge}
   '' + optionalString (mailbox.specialUse != null) ''
     special_use = \${toString mailbox.specialUse}
   '' + "}";
@@ -132,8 +134,9 @@ let
   mailboxes = { ... }: {
     options = {
       name = mkOption {
-        type = types.strMatching ''[^"]+'';
+        type = types.nullOr (types.strMatching ''[^"]+'');
         example = "Spam";
+        default = null;
         description = "The name of the mailbox.";
       };
       auto = mkOption {
@@ -148,6 +151,15 @@ let
         example = "Junk";
         description = "Null if no special use flag is set. Other than that every use flag mentioned in the RFC is valid.";
       };
+      autoexpunge = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        example = "60d";
+        description = ''
+          To automatically remove all email from the mailbox which is older than the
+          specified time.
+        '';
+      };
     };
   };
 in
@@ -323,9 +335,24 @@ in
     };
 
     mailboxes = mkOption {
-      type = types.listOf (types.submodule mailboxes);
-      default = [];
-      example = [ { name = "Spam"; specialUse = "Junk"; auto = "create"; } ];
+      type = with types; let m = submodule mailboxes; in either (listOf m) (attrsOf m);
+      default = {};
+      apply = x:
+        if isList x then warn "Declaring `services.dovecot2.mailboxes' as a list is deprecated and will break eval in 21.03!" x
+        else mapAttrsToList (name: value:
+          if value.name != null
+            then throw ''
+              When specifying dovecot2 mailboxes as attributes, declaring
+              a `name'-attribute is prohibited! The name ${value.name} should
+              be the attribute key!
+            ''
+          else value // { inherit name; }
+        ) x;
+      example = literalExample ''
+        {
+          Spam = { specialUse = "Junk"; auto = "create"; };
+        }
+      '';
       description = "Configure mailboxes and auto create or subscribe them.";
     };
 
diff --git a/nixos/modules/services/mail/opensmtpd.nix b/nixos/modules/services/mail/opensmtpd.nix
index 1fabe2da45c5..c838d3b949db 100644
--- a/nixos/modules/services/mail/opensmtpd.nix
+++ b/nixos/modules/services/mail/opensmtpd.nix
@@ -17,6 +17,10 @@ in {
 
   ###### interface
 
+  imports = [
+    (mkRenamedOptionModule [ "services" "opensmtpd" "addSendmailToSystemPath" ] [ "services" "opensmtpd" "setSendmail" ])
+  ];
+
   options = {
 
     services.opensmtpd = {
@@ -34,13 +38,10 @@ in {
         description = "The OpenSMTPD package to use.";
       };
 
-      addSendmailToSystemPath = mkOption {
+      setSendmail = mkOption {
         type = types.bool;
         default = true;
-        description = ''
-          Whether to add OpenSMTPD's sendmail binary to the
-          system path or not.
-        '';
+        description = "Whether to set the system sendmail to OpenSMTPD's.";
       };
 
       extraServerArgs = mkOption {
@@ -82,7 +83,7 @@ in {
 
   ###### implementation
 
-  config = mkIf cfg.enable {
+  config = mkIf cfg.enable rec {
     users.groups = {
       smtpd.gid = config.ids.gids.smtpd;
       smtpq.gid = config.ids.gids.smtpq;
@@ -101,6 +102,14 @@ in {
       };
     };
 
+    security.wrappers.smtpctl = {
+      group = "smtpq";
+      setgid = true;
+      source = "${cfg.package}/bin/smtpctl";
+    };
+
+    services.mail.sendmailSetuidWrapper = mkIf cfg.setSendmail security.wrappers.smtpctl;
+
     systemd.tmpfiles.rules = [
       "d /var/spool/smtpd 711 root - - -"
       "d /var/spool/smtpd/offline 770 root smtpq - -"
@@ -119,7 +128,5 @@ in {
       serviceConfig.ExecStart = "${cfg.package}/sbin/smtpd -d -f ${conf} ${args}";
       environment.OPENSMTPD_PROC_PATH = "${procEnv}/libexec/opensmtpd";
     };
-
-    environment.systemPackages = mkIf cfg.addSendmailToSystemPath [ sendmail ];
   };
 }
diff --git a/nixos/modules/services/misc/matrix-synapse.nix b/nixos/modules/services/misc/matrix-synapse.nix
index 703bc9416f88..e982eb16fa70 100644
--- a/nixos/modules/services/misc/matrix-synapse.nix
+++ b/nixos/modules/services/misc/matrix-synapse.nix
@@ -9,6 +9,9 @@ let
   logConfigFile = pkgs.writeText "log_config.yaml" cfg.logConfig;
   mkResource = r: ''{names: ${builtins.toJSON r.names}, compress: ${boolToString r.compress}}'';
   mkListener = l: ''{port: ${toString l.port}, bind_address: "${l.bind_address}", type: ${l.type}, tls: ${boolToString l.tls}, x_forwarded: ${boolToString l.x_forwarded}, resources: [${concatStringsSep "," (map mkResource l.resources)}]}'';
+  pluginsEnv = cfg.package.python.buildEnv.override {
+    extraLibs = cfg.plugins;
+  };
   configFile = pkgs.writeText "homeserver.yaml" ''
 ${optionalString (cfg.tls_certificate_path != null) ''
 tls_certificate_path: "${cfg.tls_certificate_path}"
@@ -125,6 +128,14 @@ in {
           Overridable attribute of the matrix synapse server package to use.
         '';
       };
+      plugins = mkOption {
+        type = types.listOf types.package;
+        default = [ ];
+        defaultText = "with config.services.matrix-synapse.package.plugins [ matrix-synapse-ldap3 matrix-synapse-pam ]";
+        description = ''
+          List of additional Matrix plugins to make available.
+        '';
+      };
       no_tls = mkOption {
         type = types.bool;
         default = false;
@@ -686,6 +697,7 @@ in {
           --keys-directory ${cfg.dataDir} \
           --generate-keys
       '';
+      environment.PYTHONPATH = makeSearchPathOutput "lib" cfg.package.python.sitePackages [ pluginsEnv ];
       serviceConfig = {
         Type = "notify";
         User = "matrix-synapse";
@@ -715,5 +727,6 @@ in {
   ];
 
   meta.doc = ./matrix-synapse.xml;
+  meta.maintainers = teams.matrix.members;
 
 }
diff --git a/nixos/modules/services/monitoring/prometheus/exporters.nix b/nixos/modules/services/monitoring/prometheus/exporters.nix
index f9ad1457fc85..0318acae50f7 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters.nix
@@ -21,6 +21,7 @@ let
   #  `serviceOpts.script` or `serviceOpts.serviceConfig.ExecStart`
 
   exporterOpts = genAttrs [
+    "apcupsd"
     "bind"
     "blackbox"
     "collectd"
@@ -28,6 +29,8 @@ let
     "dovecot"
     "fritzbox"
     "json"
+    "keylight"
+    "lnd"
     "mail"
     "mikrotik"
     "minio"
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/apcupsd.nix b/nixos/modules/services/monitoring/prometheus/exporters/apcupsd.nix
new file mode 100644
index 000000000000..57c35a742c5f
--- /dev/null
+++ b/nixos/modules/services/monitoring/prometheus/exporters/apcupsd.nix
@@ -0,0 +1,38 @@
+{ config, lib, pkgs, options }:
+
+with lib;
+
+let
+  cfg = config.services.prometheus.exporters.apcupsd;
+in
+{
+  port = 9162;
+  extraOpts = {
+    apcupsdAddress = mkOption {
+      type = types.str;
+      default = ":3551";
+      description = ''
+        Address of the apcupsd Network Information Server (NIS).
+      '';
+    };
+
+    apcupsdNetwork = mkOption {
+      type = types.enum ["tcp" "tcp4" "tcp6"];
+      default = "tcp";
+      description = ''
+        Network of the apcupsd Network Information Server (NIS): one of "tcp", "tcp4", or "tcp6".
+      '';
+    };
+  };
+  serviceOpts = {
+    serviceConfig = {
+      ExecStart = ''
+        ${pkgs.prometheus-apcupsd-exporter}/bin/apcupsd_exporter \
+          -telemetry.addr ${cfg.listenAddress}:${toString cfg.port} \
+          -apcupsd.addr ${cfg.apcupsdAddress} \
+          -apcupsd.network ${cfg.apcupsdNetwork} \
+          ${concatStringsSep " \\\n  " cfg.extraFlags}
+      '';
+    };
+  };
+}
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/keylight.nix b/nixos/modules/services/monitoring/prometheus/exporters/keylight.nix
new file mode 100644
index 000000000000..dfa56343b871
--- /dev/null
+++ b/nixos/modules/services/monitoring/prometheus/exporters/keylight.nix
@@ -0,0 +1,19 @@
+{ config, lib, pkgs, options }:
+
+with lib;
+
+let
+  cfg = config.services.prometheus.exporters.keylight;
+in
+{
+  port = 9288;
+  serviceOpts = {
+    serviceConfig = {
+      ExecStart = ''
+        ${pkgs.prometheus-keylight-exporter}/bin/keylight_exporter \
+          -metrics.addr ${cfg.listenAddress}:${toString cfg.port} \
+          ${concatStringsSep " \\\n  " cfg.extraFlags}
+      '';
+    };
+  };
+}
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/lnd.nix b/nixos/modules/services/monitoring/prometheus/exporters/lnd.nix
new file mode 100644
index 000000000000..35f972020574
--- /dev/null
+++ b/nixos/modules/services/monitoring/prometheus/exporters/lnd.nix
@@ -0,0 +1,46 @@
+{ config, lib, pkgs, options }:
+
+with lib;
+
+let
+  cfg = config.services.prometheus.exporters.lnd;
+in
+{
+  port = 9092;
+  extraOpts = {
+    lndHost = mkOption {
+      type = types.str;
+      default = "localhost:10009";
+      description = ''
+        lnd instance gRPC address:port.
+      '';
+    };
+
+    lndTlsPath = mkOption {
+      type = types.path;
+      description = ''
+        Path to lnd TLS certificate.
+      '';
+    };
+
+    lndMacaroonDir = mkOption {
+      type = types.path;
+      description = ''
+        Path to lnd macaroons.
+      '';
+    };
+  };
+  serviceOpts.serviceConfig = {
+    ExecStart = ''
+      ${pkgs.prometheus-lnd-exporter}/bin/lndmon \
+        --prometheus.listenaddr=${cfg.listenAddress}:${toString cfg.port} \
+        --prometheus.logdir=/var/log/prometheus-lnd-exporter \
+        --lnd.host=${cfg.lndHost} \
+        --lnd.tlspath=${cfg.lndTlsPath} \
+        --lnd.macaroondir=${cfg.lndMacaroonDir} \
+        ${concatStringsSep " \\\n  " cfg.extraFlags}
+    '';
+    LogsDirectory = "prometheus-lnd-exporter";
+    ReadOnlyPaths = [ cfg.lndTlsPath cfg.lndMacaroonDir ];
+  };
+}
diff --git a/nixos/modules/services/network-filesystems/ipfs.nix b/nixos/modules/services/network-filesystems/ipfs.nix
index 1f5c14d777d7..a3bd40135d19 100644
--- a/nixos/modules/services/network-filesystems/ipfs.nix
+++ b/nixos/modules/services/network-filesystems/ipfs.nix
@@ -1,69 +1,17 @@
-{ config, lib, pkgs, ... }:
+{ config, lib, pkgs, options, ... }:
 with lib;
 let
-  inherit (pkgs) ipfs runCommand makeWrapper;
-
   cfg = config.services.ipfs;
+  opt = options.services.ipfs;
 
   ipfsFlags = toString ([
     (optionalString  cfg.autoMount                   "--mount")
-    #(optionalString  cfg.autoMigrate                 "--migrate")
     (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);
 
-  defaultDataDir = if versionAtLeast config.system.stateVersion "17.09" then
-    "/var/lib/ipfs" else
-    "/var/lib/ipfs/.ipfs";
-
-  # Wrapping the ipfs binary with the environment variable IPFS_PATH set to dataDir because we can't set it in the user environment
-  wrapped = runCommand "ipfs" { buildInputs = [ makeWrapper ]; preferLocalBuild = true; } ''
-    mkdir -p "$out/bin"
-    makeWrapper "${ipfs}/bin/ipfs" "$out/bin/ipfs" \
-      --set IPFS_PATH ${cfg.dataDir} \
-      --prefix PATH : /run/wrappers/bin
-  '';
-
-
-  commonEnv = {
-    environment.IPFS_PATH = cfg.dataDir;
-    path = [ wrapped ];
-    serviceConfig.User = cfg.user;
-    serviceConfig.Group = cfg.group;
-  };
-
-  baseService = recursiveUpdate commonEnv {
-    wants = [ "ipfs-init.service" ];
-    # NB: migration must be performed prior to pre-start, else we get the failure message!
-    preStart = optionalString cfg.autoMount ''
-      ipfs --local config Mounts.FuseAllowOther --json true
-      ipfs --local config Mounts.IPFS ${cfg.ipfsMountDir}
-      ipfs --local config Mounts.IPNS ${cfg.ipnsMountDir}
-    '' + 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 --local config --json "${concatStringsSep "." path}" "$value"
-            '')
-            ({ Addresses.API = cfg.apiAddress;
-               Addresses.Gateway = cfg.gatewayAddress;
-               Addresses.Swarm = cfg.swarmAddress;
-            } //
-            cfg.extraConfig))
-        );
-    serviceConfig = {
-      ExecStart = "${wrapped}/bin/ipfs daemon ${ipfsFlags}";
-      Restart = "on-failure";
-      RestartSec = 1;
-    } // optionalAttrs (cfg.serviceFdlimit != null) { LimitNOFILE = cfg.serviceFdlimit; };
-  };
 in {
 
   ###### interface
@@ -88,7 +36,9 @@ in {
 
       dataDir = mkOption {
         type = types.str;
-        default = defaultDataDir;
+        default = if versionAtLeast config.system.stateVersion "17.09"
+                  then "/var/lib/ipfs"
+                  else "/var/lib/ipfs/.ipfs";
         description = "The data dir for IPFS";
       };
 
@@ -98,18 +48,6 @@ in {
         description = "systemd service that is enabled by default";
       };
 
-      /*
-      autoMigrate = mkOption {
-        type = types.bool;
-        default = false;
-        description = ''
-          Whether IPFS should try to migrate the file system automatically.
-
-          The daemon will need to be able to download a binary from https://ipfs.io to perform the migration.
-        '';
-      };
-      */
-
       autoMount = mkOption {
         type = types.bool;
         default = false;
@@ -199,13 +137,21 @@ in {
         example = 64*1024;
       };
 
+      startWhenNeeded = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Whether to use socket activation to start IPFS when needed.";
+      };
+
     };
   };
 
   ###### implementation
 
   config = mkIf cfg.enable {
-    environment.systemPackages = [ wrapped ];
+    environment.systemPackages = [ pkgs.ipfs ];
+    environment.variables.IPFS_PATH = cfg.dataDir;
+
     programs.fuse = mkIf cfg.autoMount {
       userAllowOther = true;
     };
@@ -234,10 +180,14 @@ in {
       "d '${cfg.ipnsMountDir}' - ${cfg.user} ${cfg.group} - -"
     ];
 
-    systemd.services.ipfs-init = recursiveUpdate commonEnv {
+    systemd.packages = [ pkgs.ipfs ];
+
+    systemd.services.ipfs-init = {
       description = "IPFS Initializer";
 
-      before = [ "ipfs.service" "ipfs-offline.service" "ipfs-norouting.service" ];
+      environment.IPFS_PATH = cfg.dataDir;
+
+      path = [ pkgs.ipfs ];
 
       script = ''
         if [[ ! -f ${cfg.dataDir}/config ]]; then
@@ -251,34 +201,63 @@ in {
         fi
       '';
 
+      wantedBy = [ "default.target" ];
+
       serviceConfig = {
         Type = "oneshot";
         RemainAfterExit = true;
+        User = cfg.user;
+        Group = cfg.group;
       };
     };
 
-    # TODO These 3 definitions possibly be further abstracted through use of a function
-    # like: mutexServices "ipfs" [ "", "offline", "norouting" ] { ... shared conf here ... }
+    systemd.services.ipfs = {
+      path = [ "/run/wrappers" pkgs.ipfs ];
+      environment.IPFS_PATH = cfg.dataDir;
+
+      wants = [ "ipfs-init.service" ];
+      after = [ "ipfs-init.service" ];
 
-    systemd.services.ipfs = recursiveUpdate baseService {
-      description = "IPFS Daemon";
-      wantedBy = mkIf (cfg.defaultMode == "online") [ "multi-user.target" ];
-      after = [ "network.target" "ipfs-init.service" ];
-      conflicts = [ "ipfs-offline.service" "ipfs-norouting.service"];
+      preStart = optionalString cfg.autoMount ''
+        ipfs --local config Mounts.FuseAllowOther --json true
+        ipfs --local config Mounts.IPFS ${cfg.ipfsMountDir}
+        ipfs --local config Mounts.IPNS ${cfg.ipnsMountDir}
+      '' + 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 --local config --json "${concatStringsSep "." path}" "$value"
+              '')
+              ({ Addresses.API = cfg.apiAddress;
+                 Addresses.Gateway = cfg.gatewayAddress;
+                 Addresses.Swarm = cfg.swarmAddress;
+              } //
+              cfg.extraConfig))
+          );
+      serviceConfig = {
+        ExecStart = ["" "${pkgs.ipfs}/bin/ipfs daemon ${ipfsFlags}"];
+        User = cfg.user;
+        Group = cfg.group;
+      } // optionalAttrs (cfg.serviceFdlimit != null) { LimitNOFILE = cfg.serviceFdlimit; };
+    } // optionalAttrs (!cfg.startWhenNeeded) {
+      wantedBy = [ "default.target" ];
     };
 
-    systemd.services.ipfs-offline = recursiveUpdate baseService {
-      description = "IPFS Daemon (offline mode)";
-      wantedBy = mkIf (cfg.defaultMode == "offline") [ "multi-user.target" ];
-      after = [ "ipfs-init.service" ];
-      conflicts = [ "ipfs.service" "ipfs-norouting.service"];
+    systemd.sockets.ipfs-gateway = {
+      wantedBy = [ "sockets.target" ];
+      socketConfig.ListenStream = [ "" ]
+        ++ lib.optional (cfg.gatewayAddress == opt.gatewayAddress.default) [ "127.0.0.1:8080" "[::1]:8080" ];
     };
 
-    systemd.services.ipfs-norouting = recursiveUpdate baseService {
-      description = "IPFS Daemon (no routing mode)";
-      wantedBy = mkIf (cfg.defaultMode == "norouting") [ "multi-user.target" ];
-      after = [ "ipfs-init.service" ];
-      conflicts = [ "ipfs.service" "ipfs-offline.service"];
+    systemd.sockets.ipfs-api = {
+      wantedBy = [ "sockets.target" ];
+      socketConfig.ListenStream = [ "" "%t/ipfs.sock" ]
+        ++ lib.optional (cfg.apiAddress == opt.apiAddress.default) [ "127.0.0.1:5001" "[::1]:5001" ];
     };
 
   };
diff --git a/nixos/modules/services/networking/corerad.nix b/nixos/modules/services/networking/corerad.nix
index 1a2c4aec6651..5d73c0a0d779 100644
--- a/nixos/modules/services/networking/corerad.nix
+++ b/nixos/modules/services/networking/corerad.nix
@@ -4,14 +4,50 @@ with lib;
 
 let
   cfg = config.services.corerad;
+
+  writeTOML = name: x:
+    pkgs.runCommandNoCCLocal name {
+      passAsFile = ["config"];
+      config = builtins.toJSON x;
+      buildInputs = [ pkgs.go-toml ];
+    } "jsontoml < $configPath > $out";
+
 in {
-  meta = {
-    maintainers = with maintainers; [ mdlayher ];
-  };
+  meta.maintainers = with maintainers; [ mdlayher ];
 
   options.services.corerad = {
     enable = mkEnableOption "CoreRAD IPv6 NDP RA daemon";
 
+    settings = mkOption {
+      type = types.uniq types.attrs;
+      example = literalExample ''
+        {
+          interfaces = [
+            # eth0 is an upstream interface monitoring for IPv6 router advertisements.
+            {
+              name = "eth0";
+              monitor = true;
+            }
+            # eth1 is a downstream interface advertising IPv6 prefixes for SLAAC.
+            {
+              name = "eth1";
+              advertise = true;
+              prefix = [{ prefix = "::/64"; }];
+            }
+          ];
+          # Optionally enable Prometheus metrics.
+          debug = {
+            address = "localhost:9430";
+            prometheus = true;
+          };
+        }
+      '';
+      description = ''
+        Configuration for CoreRAD, see <link xlink:href="https://github.com/mdlayher/corerad/blob/master/internal/config/default.toml"/>
+        for supported values. Ignored if configFile is set.
+      '';
+    };
+
     configFile = mkOption {
       type = types.path;
       example = literalExample "\"\${pkgs.corerad}/etc/corerad/corerad.toml\"";
@@ -27,6 +63,9 @@ in {
   };
 
   config = mkIf cfg.enable {
+    # Prefer the config file over settings if both are set.
+    services.corerad.configFile = mkDefault (writeTOML "corerad.toml" cfg.settings);
+
     systemd.services.corerad = {
       description = "CoreRAD IPv6 NDP RA daemon";
       after = [ "network.target" ];
diff --git a/nixos/modules/services/networking/dnschain.nix b/nixos/modules/services/networking/dnschain.nix
deleted file mode 100644
index 003609ea7054..000000000000
--- a/nixos/modules/services/networking/dnschain.nix
+++ /dev/null
@@ -1,184 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-with lib;
-
-let
-  cfgs = config.services;
-  cfg  = cfgs.dnschain;
-
-  dataDir  = "/var/lib/dnschain";
-  username = "dnschain";
-
-  configFile = pkgs.writeText "dnschain.conf" ''
-    [log]
-    level = info
-
-    [dns]
-    host = ${cfg.dns.address}
-    port = ${toString cfg.dns.port}
-    oldDNSMethod = NO_OLD_DNS
-    externalIP = ${cfg.dns.externalAddress}
-
-    [http]
-    host = ${cfg.api.hostname}
-    port = ${toString cfg.api.port}
-    tlsPort = ${toString cfg.api.tlsPort}
-
-    ${cfg.extraConfig}
-  '';
-
-in
-
-{
-
-  ###### interface
-
-  options = {
-
-    services.dnschain = {
-
-      enable = mkEnableOption ''
-        DNSChain, a blockchain based DNS + HTTP server.
-        To resolve .bit domains set <literal>services.namecoind.enable = true;</literal>
-        and an RPC username/password.
-      '';
-
-      dns.address = mkOption {
-        type = types.str;
-        default = "127.0.0.1";
-        description = ''
-          The IP address the DNSChain resolver will bind to.
-          Leave this unchanged if you do not wish to directly expose the resolver.
-        '';
-      };
-
-      dns.externalAddress = mkOption {
-        type = types.str;
-        default = cfg.dns.address;
-        description = ''
-           The IP address used by clients to reach the resolver and the value of
-           the <literal>namecoin.dns</literal> record. Set this in case the bind address
-           is not the actual IP address (e.g. the machine is behind a NAT).
-        '';
-      };
-
-      dns.port = mkOption {
-        type = types.int;
-        default = 5333;
-        description = ''
-          The port the DNSChain resolver will bind to.
-        '';
-      };
-
-      api.hostname = mkOption {
-        type = types.str;
-        default = "0.0.0.0";
-        description = ''
-          The hostname (or IP address) the DNSChain API server will bind to.
-        '';
-      };
-
-      api.port = mkOption {
-        type = types.int;
-        default = 8080;
-        description = ''
-          The port the DNSChain API server (HTTP) will bind to.
-        '';
-      };
-
-      api.tlsPort = mkOption {
-        type = types.int;
-        default = 4433;
-        description = ''
-          The port the DNSChain API server (HTTPS) will bind to.
-        '';
-      };
-
-      extraConfig = mkOption {
-        type = types.lines;
-        default = "";
-        example = ''
-          [log]
-          level = debug
-        '';
-        description = ''
-          Additional options that will be appended to the configuration file.
-        '';
-      };
-
-    };
-
-    services.dnsmasq.resolveDNSChainQueries = mkOption {
-      type = types.bool;
-      default = false;
-      description = ''
-        Resolve <literal>.bit</literal> top-level domains using DNSChain and namecoin.
-      '';
-    };
-
-    services.pdns-recursor.resolveDNSChainQueries = mkOption {
-      type = types.bool;
-      default = false;
-      description = ''
-        Resolve <literal>.bit</literal> top-level domains using DNSChain and namecoin.
-      '';
-    };
-
-  };
-
-
-  ###### implementation
-
-  config = mkIf cfg.enable {
-
-    services.dnsmasq.servers = optionals cfgs.dnsmasq.resolveDNSChainQueries
-      [ "/.bit/127.0.0.1#${toString cfg.dns.port}"
-        "/.dns/127.0.0.1#${toString cfg.dns.port}"
-      ];
-
-    services.pdns-recursor = mkIf cfgs.pdns-recursor.resolveDNSChainQueries {
-      forwardZonesRecurse =
-        { bit = "127.0.0.1:${toString cfg.dns.port}";
-          dns = "127.0.0.1:${toString cfg.dns.port}";
-        };
-      luaConfig =''
-        addNTA("bit", "namecoin doesn't support DNSSEC")
-        addNTA("dns", "namecoin doesn't support DNSSEC")
-      '';
-    };
-
-    users.users.${username} = {
-      description = "DNSChain daemon user";
-      home = dataDir;
-      createHome = true;
-      uid = config.ids.uids.dnschain;
-      extraGroups = optional cfgs.namecoind.enable "namecoin";
-    };
-
-    systemd.services.dnschain = {
-      description = "DNSChain daemon";
-      after    = optional cfgs.namecoind.enable "namecoind.target";
-      wantedBy = [ "multi-user.target" ];
-
-      serviceConfig = {
-        User = "dnschain";
-        Restart = "on-failure";
-        ExecStart = "${pkgs.nodePackages.dnschain}/bin/dnschain";
-      };
-
-      preStart = ''
-        # Link configuration file into dnschain home directory
-        configPath=${dataDir}/.dnschain/dnschain.conf
-        mkdir -p ${dataDir}/.dnschain
-        if [ "$(realpath $configPath)" != "${configFile}" ]; then
-          rm -f $configPath
-          ln -s ${configFile} $configPath
-        fi
-      '';
-    };
-
-  };
-
-  meta.maintainers = with lib.maintainers; [ rnhmjoj ];
-
-}
diff --git a/nixos/modules/services/networking/go-neb.nix b/nixos/modules/services/networking/go-neb.nix
new file mode 100644
index 000000000000..991ae38f30a5
--- /dev/null
+++ b/nixos/modules/services/networking/go-neb.nix
@@ -0,0 +1,53 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.go-neb;
+
+  configFile = pkgs.writeText "config.yml" (builtins.toJSON cfg.config);
+in {
+  options.services.go-neb = {
+    enable = mkEnableOption "Extensible matrix bot written in Go";
+
+    bindAddress = mkOption {
+      type = types.str;
+      description = "Port (and optionally address) to listen on.";
+      default = ":4050";
+    };
+
+    baseUrl = mkOption {
+      type = types.str;
+      description = "Public-facing endpoint that can receive webhooks.";
+    };
+
+    config = mkOption {
+      type = types.uniq types.attrs;
+      description = ''
+        Your <filename>config.yaml</filename> as a Nix attribute set.
+        See <link xlink:href="https://github.com/matrix-org/go-neb/blob/master/config.sample.yaml">config.sample.yaml</link>
+        for possible options.
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.go-neb = {
+      description = "Extensible matrix bot written in Go";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      environment = {
+        BASE_URL = cfg.baseUrl;
+        BIND_ADDRESS = cfg.bindAddress;
+        CONFIG_FILE = configFile;
+      };
+
+      serviceConfig = {
+        ExecStart = "${pkgs.go-neb}/bin/go-neb";
+        DynamicUser = true;
+      };
+    };
+  };
+
+  meta.maintainers = with maintainers; [ hexa maralorn ];
+}
diff --git a/nixos/modules/services/networking/namecoind.nix b/nixos/modules/services/networking/namecoind.nix
index ead7f0859434..6ca99e1321bd 100644
--- a/nixos/modules/services/networking/namecoind.nix
+++ b/nixos/modules/services/networking/namecoind.nix
@@ -149,11 +149,6 @@ in
 
   config = mkIf cfg.enable {
 
-    services.dnschain.extraConfig = ''
-      [namecoin]
-      config = ${configFile}
-    '';
-
     users.users.namecoin = {
       uid  = config.ids.uids.namecoin;
       description = "Namecoin daemon user";
diff --git a/nixos/modules/services/security/physlock.nix b/nixos/modules/services/security/physlock.nix
index 61bcd84f2e64..690eb70079d8 100644
--- a/nixos/modules/services/security/physlock.nix
+++ b/nixos/modules/services/security/physlock.nix
@@ -107,6 +107,7 @@ in
                 ++ cfg.lockOn.extraTargets;
         before   = optional cfg.lockOn.suspend   "systemd-suspend.service"
                 ++ optional cfg.lockOn.hibernate "systemd-hibernate.service"
+                ++ optional (cfg.lockOn.hibernate || cfg.lockOn.suspend) "systemd-suspend-then-hibernate.service"
                 ++ cfg.lockOn.extraTargets;
         serviceConfig = {
           Type = "forking";
diff --git a/nixos/modules/services/torrent/transmission.nix b/nixos/modules/services/torrent/transmission.nix
index e7f5aaed844e..1bfcf2de82f8 100644
--- a/nixos/modules/services/torrent/transmission.nix
+++ b/nixos/modules/services/torrent/transmission.nix
@@ -179,6 +179,8 @@ in
           ${getLib pkgs.utillinuxMinimal.out}/lib/libblkid.so.* mr,
           ${getLib pkgs.utillinuxMinimal.out}/lib/libmount.so.* mr,
           ${getLib pkgs.utillinuxMinimal.out}/lib/libuuid.so.* mr,
+          ${getLib pkgs.gcc.cc.lib}/lib/libstdc++.so.* mr,
+          ${getLib pkgs.gcc.cc.lib}/lib/libgcc_s.so.* mr,
 
           @{PROC}/sys/kernel/random/uuid   r,
           @{PROC}/sys/vm/overcommit_memory r,
diff --git a/nixos/modules/services/x11/desktop-managers/gnome3.nix b/nixos/modules/services/x11/desktop-managers/gnome3.nix
index 803419f5d6c6..69cf98321720 100644
--- a/nixos/modules/services/x11/desktop-managers/gnome3.nix
+++ b/nixos/modules/services/x11/desktop-managers/gnome3.nix
@@ -320,8 +320,8 @@ in
         gnome-shell
         gnome-shell-extensions
         gnome-themes-extra
-        nixos-artwork.wallpapers.simple-dark-gray
-        nixos-artwork.wallpapers.simple-dark-gray-bottom
+        pkgs.nixos-artwork.wallpapers.simple-dark-gray
+        pkgs.nixos-artwork.wallpapers.simple-dark-gray-bottom
         pkgs.gnome-user-docs
         pkgs.orca
         pkgs.glib # for gsettings
diff --git a/nixos/modules/services/x11/xserver.nix b/nixos/modules/services/x11/xserver.nix
index 6aec1c0753a2..400173745d3f 100644
--- a/nixos/modules/services/x11/xserver.nix
+++ b/nixos/modules/services/x11/xserver.nix
@@ -246,7 +246,7 @@ in
       videoDrivers = mkOption {
         type = types.listOf types.str;
         # !!! We'd like "nv" here, but it segfaults the X server.
-        default = [ "radeon" "cirrus" "vesa" "vmware" "modesetting" ];
+        default = [ "radeon" "cirrus" "vesa" "modesetting" ];
         example = [
           "ati_unfree" "amdgpu" "amdgpu-pro"
           "nv" "nvidia" "nvidiaLegacy390" "nvidiaLegacy340" "nvidiaLegacy304"
diff --git a/nixos/modules/system/boot/kernel_config.nix b/nixos/modules/system/boot/kernel_config.nix
index a316782dfc57..85bd420e3044 100644
--- a/nixos/modules/system/boot/kernel_config.nix
+++ b/nixos/modules/system/boot/kernel_config.nix
@@ -22,7 +22,7 @@ let
 
   mergeFalseByDefault = locs: defs:
     if defs == [] then abort "This case should never happen."
-    else if any (x: x == false) defs then false
+    else if any (x: x == false) (getValues defs) then false
     else true;
 
   kernelItem = types.submodule {
@@ -55,6 +55,7 @@ let
         default = false;
         description = ''
           Wether option should generate a failure when unused.
+          Upon merging values, mandatory wins over optional.
         '';
       };
     };
@@ -121,7 +122,7 @@ in
       type = types.attrsOf kernelItem;
       example = literalExample '' with lib.kernel; {
         "9P_NET" = yes;
-        USB = optional yes;
+        USB = option yes;
         MMC_BLOCK_MINORS = freeform "32";
       }'';
       description = ''
diff --git a/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py b/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py
index f48a085ce57a..d8baed65c6df 100644
--- a/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py
+++ b/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py
@@ -47,9 +47,9 @@ def write_loader_conf(profile, generation):
         if "@timeout@" != "":
             f.write("timeout @timeout@\n")
         if profile:
-            f.write("default nixos-%s-generation-%d\n" % (profile, generation))
+            f.write("default nixos-%s-generation-%d.conf\n".format(profile, generation))
         else:
-            f.write("default nixos-generation-%d\n" % (generation))
+            f.write("default nixos-generation-%d.conf\n".format(generation))
         if not @editor@:
             f.write("editor 0\n");
         f.write("console-mode @consoleMode@\n");
@@ -197,6 +197,22 @@ def main():
             subprocess.check_call(["@systemd@/bin/bootctl", "--path=@efiSysMountPoint@", "install"])
         else:
             subprocess.check_call(["@systemd@/bin/bootctl", "--path=@efiSysMountPoint@", "--no-variables", "install"])
+    else:
+        # Update bootloader to latest if needed
+        systemd_version = subprocess.check_output(["@systemd@/bin/bootctl", "--version"], universal_newlines=True).split()[1]
+        sdboot_status = subprocess.check_output(["@systemd@/bin/bootctl", "--path=@efiSysMountPoint@", "status"], universal_newlines=True)
+
+        # See status_binaries() in systemd bootctl.c for code which generates this
+        m = re.search("^\W+File:.*/EFI/(BOOT|systemd)/.*\.efi \(systemd-boot (\d+)\)$",
+                      sdboot_status, re.IGNORECASE | re.MULTILINE)
+        if m is None:
+            print("could not find any previously installed systemd-boot")
+        else:
+            sdboot_version = m.group(2)
+            if systemd_version > sdboot_version:
+                print("updating systemd-boot from %s to %s" % (sdboot_version, systemd_version))
+                subprocess.check_call(["@systemd@/bin/bootctl", "--path=@efiSysMountPoint@", "update"])
+
 
     mkdir_p("@efiSysMountPoint@/efi/nixos")
     mkdir_p("@efiSysMountPoint@/loader/entries")
diff --git a/nixos/modules/system/boot/networkd.nix b/nixos/modules/system/boot/networkd.nix
index 9b34b12e73a5..b0545363b330 100644
--- a/nixos/modules/system/boot/networkd.nix
+++ b/nixos/modules/system/boot/networkd.nix
@@ -1178,14 +1178,22 @@ in
       users.users.systemd-network.group = "systemd-network";
 
       systemd.additionalUpstreamSystemUnits = [
-        "systemd-networkd.service" "systemd-networkd-wait-online.service"
+        "systemd-networkd-wait-online.service"
+        "systemd-networkd.service"
+        "systemd-networkd.socket"
       ];
 
       systemd.network.units = mapAttrs' (n: v: nameValuePair "${n}.netdev" (netdevToUnit n v)) cfg.netdevs
         // mapAttrs' (n: v: nameValuePair "${n}.network" (networkToUnit n v)) cfg.networks;
 
+      # systemd-networkd is socket-activated by kernel netlink route change
+      # messages. It is important to have systemd buffer those on behalf of
+      # networkd.
+      systemd.sockets.systemd-networkd.wantedBy = [ "sockets.target" ];
+
       systemd.services.systemd-networkd = {
         wantedBy = [ "multi-user.target" ];
+        aliases = [ "dbus-org.freedesktop.network1.service" ];
         restartTriggers = map (x: x.source) (attrValues unitFiles);
         # prevent race condition with interface renaming (#39069)
         requires = [ "systemd-udev-settle.service" ];
diff --git a/nixos/modules/system/boot/resolved.nix b/nixos/modules/system/boot/resolved.nix
index b7aaef575ac1..b024f9cf5ee9 100644
--- a/nixos/modules/system/boot/resolved.nix
+++ b/nixos/modules/system/boot/resolved.nix
@@ -148,6 +148,7 @@ in
 
     systemd.services.systemd-resolved = {
       wantedBy = [ "multi-user.target" ];
+      aliases = [ "dbus-org.freedesktop.resolve1.service" ];
       restartTriggers = [ config.environment.etc."systemd/resolved.conf".source ];
     };
 
diff --git a/nixos/modules/system/boot/systemd.nix b/nixos/modules/system/boot/systemd.nix
index 99892a28115c..a8e51fc09014 100644
--- a/nixos/modules/system/boot/systemd.nix
+++ b/nixos/modules/system/boot/systemd.nix
@@ -826,8 +826,13 @@ in
   config = {
 
     warnings = concatLists (mapAttrsToList (name: service:
-      optional (service.serviceConfig.Type or "" == "oneshot" && service.serviceConfig.Restart or "no" != "no")
-        "Service ‘${name}.service’ with ‘Type=oneshot’ must have ‘Restart=no’") cfg.services);
+      let
+        type = service.serviceConfig.Type or "";
+        restart = service.serviceConfig.Restart or "no";
+      in optional
+      (type == "oneshot" && (restart == "always" || restart == "on-success"))
+      "Service '${name}.service' with 'Type=oneshot' cannot have 'Restart=always' or 'Restart=on-success'")
+      cfg.services);
 
     system.build.units = cfg.units;
 
diff --git a/nixos/modules/system/boot/timesyncd.nix b/nixos/modules/system/boot/timesyncd.nix
index 9e2f36ca01f8..35fb5578b070 100644
--- a/nixos/modules/system/boot/timesyncd.nix
+++ b/nixos/modules/system/boot/timesyncd.nix
@@ -41,6 +41,7 @@ with lib;
 
     systemd.services.systemd-timesyncd = {
       wantedBy = [ "sysinit.target" ];
+      aliases = [ "dbus-org.freedesktop.timesync1.service" ];
       restartTriggers = [ config.environment.etc."systemd/timesyncd.conf".source ];
     };
 
diff --git a/nixos/modules/tasks/network-interfaces-scripted.nix b/nixos/modules/tasks/network-interfaces-scripted.nix
index d895c58bab03..2e87197176b6 100644
--- a/nixos/modules/tasks/network-interfaces-scripted.nix
+++ b/nixos/modules/tasks/network-interfaces-scripted.nix
@@ -232,18 +232,22 @@ let
               '';
             preStop = ''
               state="/run/nixos/network/routes/${i.name}"
-              while read cidr; do
-                echo -n "deleting route $cidr... "
-                ip route del "$cidr" dev "${i.name}" >/dev/null 2>&1 && echo "done" || echo "failed"
-              done < "$state"
-              rm -f "$state"
+              if [ -e "$state" ]; then
+                while read cidr; do
+                  echo -n "deleting route $cidr... "
+                  ip route del "$cidr" dev "${i.name}" >/dev/null 2>&1 && echo "done" || echo "failed"
+                done < "$state"
+                rm -f "$state"
+              fi
 
               state="/run/nixos/network/addresses/${i.name}"
-              while read cidr; do
-                echo -n "deleting address $cidr... "
-                ip addr del "$cidr" dev "${i.name}" >/dev/null 2>&1 && echo "done" || echo "failed"
-              done < "$state"
-              rm -f "$state"
+              if [ -e "$state" ]; then
+                while read cidr; do
+                  echo -n "deleting address $cidr... "
+                  ip addr del "$cidr" dev "${i.name}" >/dev/null 2>&1 && echo "done" || echo "failed"
+                done < "$state"
+                rm -f "$state"
+              fi
             '';
           };
 
diff --git a/nixos/modules/virtualisation/lxd.nix b/nixos/modules/virtualisation/lxd.nix
index 53b89a9f55b1..3958fc2c1d7c 100644
--- a/nixos/modules/virtualisation/lxd.nix
+++ b/nixos/modules/virtualisation/lxd.nix
@@ -15,7 +15,6 @@ in
   ###### interface
 
   options = {
-
     virtualisation.lxd = {
       enable = mkOption {
         type = types.bool;
@@ -25,12 +24,18 @@ in
           containers. Users in the "lxd" group can interact with
           the daemon (e.g. to start or stop containers) using the
           <command>lxc</command> command line tool, among others.
+
+          Most of the time, you'll also want to start lxcfs, so
+          that containers can "see" the limits:
+          <code>
+            virtualisation.lxc.lxcfs.enable = true;
+          </code>
         '';
       };
 
       package = mkOption {
         type = types.package;
-        default = pkgs.lxd;
+        default = pkgs.lxd.override { nftablesSupport = config.networking.nftables.enable; };
         defaultText = "pkgs.lxd";
         description = ''
           The LXD package to use.
@@ -65,6 +70,7 @@ in
           with nixos.
         '';
       };
+
       recommendedSysctlSettings = mkOption {
         type = types.bool;
         default = false;
@@ -83,7 +89,6 @@ in
   ###### implementation
 
   config = mkIf cfg.enable {
-
     environment.systemPackages = [ cfg.package ];
 
     security.apparmor = {
@@ -115,6 +120,12 @@ in
         LimitNOFILE = "1048576";
         LimitNPROC = "infinity";
         TasksMax = "infinity";
+
+        # By default, `lxd` loads configuration files from hard-coded
+        # `/usr/share/lxc/config` - since this is a no-go for us, we have to
+        # explicitly tell it where the actual configuration files are
+        Environment = mkIf (config.virtualisation.lxc.lxcfs.enable)
+          "LXD_LXC_TEMPLATE_CONFIG=${pkgs.lxcfs}/share/lxc/config";
       };
     };
 
diff --git a/nixos/modules/virtualisation/qemu-vm.nix b/nixos/modules/virtualisation/qemu-vm.nix
index b8d0606be7c1..be06d6feb11f 100644
--- a/nixos/modules/virtualisation/qemu-vm.nix
+++ b/nixos/modules/virtualisation/qemu-vm.nix
@@ -448,7 +448,7 @@ in
         description =
           ''
             An alternate BIOS (such as <package>qboot</package>) with which to start the VM.
-            Should containin a file named <literal>bios.bin</literal>.
+            Should contain a file named <literal>bios.bin</literal>.
             If <literal>null</literal>, QEMU's builtin SeaBIOS will be used.
           '';
       };
diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix
index 8e262d8eee7d..bd26fc906aaa 100644
--- a/nixos/tests/all-tests.nix
+++ b/nixos/tests/all-tests.nix
@@ -119,6 +119,7 @@ in
   installed-tests = pkgs.recurseIntoAttrs (handleTest ./installed-tests {});
   gocd-agent = handleTest ./gocd-agent.nix {};
   gocd-server = handleTest ./gocd-server.nix {};
+  go-neb = handleTest ./go-neb.nix {};
   google-oslogin = handleTest ./google-oslogin {};
   grafana = handleTest ./grafana.nix {};
   graphite = handleTest ./graphite.nix {};
@@ -178,6 +179,8 @@ in
   limesurvey = handleTest ./limesurvey.nix {};
   login = handleTest ./login.nix {};
   loki = handleTest ./loki.nix {};
+  lxd = handleTest ./lxd.nix {};
+  lxd-nftables = handleTest ./lxd-nftables.nix {};
   #logstash = handleTest ./logstash.nix {};
   lorri = handleTest ./lorri/default.nix {};
   magnetico = handleTest ./magnetico.nix {};
diff --git a/nixos/tests/corerad.nix b/nixos/tests/corerad.nix
index 741fa448f680..72ab255b1916 100644
--- a/nixos/tests/corerad.nix
+++ b/nixos/tests/corerad.nix
@@ -3,7 +3,7 @@ import ./make-test-python.nix (
     nodes = {
       router = {config, pkgs, ...}: { 
         config = {
-          # This machines simulates a router with IPv6 forwarding and a static IPv6 address.
+          # This machine simulates a router with IPv6 forwarding and a static IPv6 address.
           boot.kernel.sysctl = {
             "net.ipv6.conf.all.forwarding" = true;
           };
@@ -14,13 +14,25 @@ import ./make-test-python.nix (
             enable = true;
             # Serve router advertisements to the client machine with prefix information matching
             # any IPv6 /64 prefixes configured on this interface.
-            configFile = pkgs.writeText "corerad.toml" ''
-              [[interfaces]]
-              name = "eth1"
-              advertise = true
-                [[interfaces.prefix]]
-                prefix = "::/64"
-            '';
+            #
+            # This configuration is identical to the example in the CoreRAD NixOS module.
+            settings = {
+              interfaces = [
+                {
+                  name = "eth0";
+                  monitor = true;
+                }
+                {
+                  name = "eth1";
+                  advertise = true;
+                  prefix = [{ prefix = "::/64"; }];
+                }
+              ];
+              debug = {
+                address = "localhost:9430";
+                prometheus = true;
+              };
+            };
           };
         };
       };
@@ -66,5 +78,12 @@ import ./make-test-python.nix (
           assert (
               "/64 scope global temporary" in addrs
           ), "SLAAC temporary address was not configured on client after router advertisement"
+
+      with subtest("Verify HTTP debug server is configured"):
+          out = router.succeed("curl localhost:9430/metrics")
+
+          assert (
+              "corerad_build_info" in out
+          ), "Build info metric was not found in Prometheus output"
     '';
   })
diff --git a/nixos/tests/go-neb.nix b/nixos/tests/go-neb.nix
new file mode 100644
index 000000000000..d9e5db0b4a53
--- /dev/null
+++ b/nixos/tests/go-neb.nix
@@ -0,0 +1,44 @@
+import ./make-test-python.nix ({ pkgs, ... }:
+{
+  name = "go-neb";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ hexa maralorn ];
+  };
+
+  nodes = {
+    server = {
+      services.go-neb = {
+        enable = true;
+        baseUrl = "http://localhost";
+        config = {
+          clients = [ {
+            UserId = "@test:localhost";
+            AccessToken = "changeme";
+            HomeServerUrl = "http://localhost";
+            Sync = false;
+            AutoJoinRooms = false;
+            DisplayName = "neverbeseen";
+          } ];
+          services = [ {
+            ID = "wikipedia_service";
+            Type = "wikipedia";
+            UserID = "@test:localhost";
+            Config = { };
+          } ];
+        };
+      };
+    };
+  };
+
+  testScript = ''
+    start_all()
+    server.wait_for_unit("go-neb.service")
+    server.wait_until_succeeds(
+        "curl -L http://localhost:4050/services/hooks/d2lraXBlZGlhX3NlcnZpY2U"
+    )
+    server.wait_until_succeeds(
+        "journalctl -eu go-neb -o cat | grep -q service_id=wikipedia_service"
+    )
+  '';
+
+})
diff --git a/nixos/tests/hydra/db-migration.nix b/nixos/tests/hydra/db-migration.nix
index cf74acfd67aa..ca65e2e66aa1 100644
--- a/nixos/tests/hydra/db-migration.nix
+++ b/nixos/tests/hydra/db-migration.nix
@@ -61,7 +61,7 @@ with pkgs.lib;
           'curl -L -s http://localhost:3000/build/1 -H "Accept: application/json" |  jq .buildstatus | xargs test 0 -eq'
       )
 
-      out = original.succeed("su -l postgres -c 'psql -d hydra <<< \"\\d+ jobs\" -A'")
+      out = original.succeed("su -l postgres -c 'psql -d hydra <<< \"\\d+ builds\" -A'")
       assert "jobset_id" not in out
 
       original.succeed(
@@ -69,7 +69,7 @@ with pkgs.lib;
       )
       original.wait_for_unit("hydra-init.service")
 
-      out = original.succeed("su -l postgres -c 'psql -d hydra <<< \"\\d+ jobs\" -A'")
+      out = original.succeed("su -l postgres -c 'psql -d hydra <<< \"\\d+ builds\" -A'")
       assert "jobset_id|integer|||" in out
 
       original.succeed("hydra-backfill-ids")
@@ -79,7 +79,7 @@ with pkgs.lib;
       )
       original.wait_for_unit("hydra-init.service")
 
-      out = original.succeed("su -l postgres -c 'psql -d hydra <<< \"\\d+ jobs\" -A'")
+      out = original.succeed("su -l postgres -c 'psql -d hydra <<< \"\\d+ builds\" -A'")
       assert "jobset_id|integer||not null|" in out
 
       original.wait_until_succeeds(
diff --git a/nixos/tests/ipfs.nix b/nixos/tests/ipfs.nix
index 4d721aec0c73..82234f969226 100644
--- a/nixos/tests/ipfs.nix
+++ b/nixos/tests/ipfs.nix
@@ -21,5 +21,12 @@ import ./make-test-python.nix ({ pkgs, ...} : {
     )
 
     machine.succeed(f"ipfs cat /ipfs/{ipfs_hash.strip()} | grep fnord")
+
+    ipfs_hash = machine.succeed(
+        "echo fnord2 | ipfs --api /unix/run/ipfs.sock add | awk '{ print $2 }'"
+    )
+    machine.succeed(
+        f"ipfs --api /unix/run/ipfs.sock cat /ipfs/{ipfs_hash.strip()} | grep fnord2"
+    )
   '';
 })
diff --git a/nixos/tests/lxd-nftables.nix b/nixos/tests/lxd-nftables.nix
new file mode 100644
index 000000000000..25517914db85
--- /dev/null
+++ b/nixos/tests/lxd-nftables.nix
@@ -0,0 +1,50 @@
+# This test makes sure that lxd stops implicitly depending on iptables when
+# user enabled nftables.
+#
+# It has been extracted from `lxd.nix` for clarity, and because switching from
+# iptables to nftables requires a full reboot, which is a bit hard inside NixOS
+# tests.
+
+import ./make-test-python.nix ({ pkgs, ...} : {
+  name = "lxd-nftables";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ patryk27 ];
+  };
+
+  machine = { lib, ... }: {
+    virtualisation = {
+      lxd.enable = true;
+    };
+
+    networking = {
+      firewall.enable = false;
+      nftables.enable = true;
+      nftables.ruleset = ''
+        table inet filter {
+          chain incoming {
+            type filter hook input priority 0;
+            policy accept;
+          }
+
+          chain forward {
+            type filter hook forward priority 0;
+            policy accept;
+          }
+
+          chain output {
+            type filter hook output priority 0;
+            policy accept;
+          }
+        }
+      '';
+    };
+  };
+
+  testScript = ''
+    machine.wait_for_unit("network.target")
+
+    with subtest("When nftables are enabled, lxd doesn't depend on iptables anymore"):
+        machine.succeed("lsmod | grep nf_tables")
+        machine.fail("lsmod | grep ip_tables")
+  '';
+})
diff --git a/nixos/tests/lxd.nix b/nixos/tests/lxd.nix
new file mode 100644
index 000000000000..db2d44dff557
--- /dev/null
+++ b/nixos/tests/lxd.nix
@@ -0,0 +1,135 @@
+import ./make-test-python.nix ({ pkgs, ...} :
+
+let
+  # Since we don't have access to the internet during the tests, we have to
+  # pre-fetch lxd containers beforehand.
+  #
+  # I've chosen to import Alpine Linux, because its image is turbo-tiny and,
+  # generally, sufficient for our tests.
+
+  alpine-meta = pkgs.fetchurl {
+    url = "https://uk.images.linuxcontainers.org/images/alpine/3.11/i386/default/20200608_13:00/lxd.tar.xz";
+    sha256 = "1hkvaj3rr333zmx1759njy435lps33gl4ks8zfm7m4nqvipm26a0";
+  };
+
+  alpine-rootfs = pkgs.fetchurl {
+    url = "https://uk.images.linuxcontainers.org/images/alpine/3.11/i386/default/20200608_13:00/rootfs.tar.xz";
+    sha256 = "1v82zdra4j5xwsff09qlp7h5vbsg54s0j7rdg4rynichfid3r347";
+  };
+
+  lxd-config = pkgs.writeText "config.yaml" ''
+    storage_pools:
+      - name: default
+        driver: dir
+        config:
+          source: /var/lxd-pool
+
+    networks:
+      - name: lxdbr0
+        type: bridge
+        config:
+          ipv4.address: auto
+          ipv6.address: none
+
+    profiles:
+      - name: default
+        devices:
+          eth0:
+            name: eth0
+            network: lxdbr0
+            type: nic
+          root:
+            path: /
+            pool: default
+            type: disk
+  '';
+
+in {
+  name = "lxd";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ patryk27 ];
+  };
+
+  machine = { lib, ... }: {
+    virtualisation = {
+      # Since we're testing `limits.cpu`, we've gotta have a known number of
+      # cores to lay on
+      cores = 2;
+
+      # Ditto, for `limits.memory`
+      memorySize = 512;
+
+      lxc.lxcfs.enable = true;
+      lxd.enable = true;
+    };
+  };
+
+  testScript = ''
+    machine.wait_for_unit("sockets.target")
+    machine.wait_for_unit("lxd.service")
+
+    # It takes additional second for lxd to settle
+    machine.sleep(1)
+
+    # lxd expects the pool's directory to already exist
+    machine.succeed("mkdir /var/lxd-pool")
+
+    machine.succeed(
+        "cat ${lxd-config} | lxd init --preseed"
+    )
+
+    machine.succeed(
+        "lxc image import ${alpine-meta} ${alpine-rootfs} --alias alpine"
+    )
+
+    with subtest("Containers can be launched and destroyed"):
+        machine.succeed("lxc launch alpine test")
+        machine.succeed("lxc exec test true")
+        machine.succeed("lxc delete -f test")
+
+    with subtest("Containers are being mounted with lxcfs inside"):
+        machine.succeed("lxc launch alpine test")
+
+        ## ---------- ##
+        ## limits.cpu ##
+
+        machine.succeed("lxc config set test limits.cpu 1")
+
+        # Since Alpine doesn't have `nproc` pre-installed, we've gotta resort
+        # to the primal methods
+        assert (
+            "1"
+            == machine.succeed("lxc exec test grep -- -c ^processor /proc/cpuinfo").strip()
+        )
+
+        machine.succeed("lxc config set test limits.cpu 2")
+
+        assert (
+            "2"
+            == machine.succeed("lxc exec test grep -- -c ^processor /proc/cpuinfo").strip()
+        )
+
+        ## ------------- ##
+        ## limits.memory ##
+
+        machine.succeed("lxc config set test limits.memory 64MB")
+
+        assert (
+            "MemTotal:          62500 kB"
+            == machine.succeed("lxc exec test grep -- MemTotal /proc/meminfo").strip()
+        )
+
+        machine.succeed("lxc config set test limits.memory 128MB")
+
+        assert (
+            "MemTotal:         125000 kB"
+            == machine.succeed("lxc exec test grep -- MemTotal /proc/meminfo").strip()
+        )
+
+        machine.succeed("lxc delete -f test")
+
+    with subtest("Unless explicitly changed, lxd leans on iptables"):
+        machine.succeed("lsmod | grep ip_tables")
+        machine.fail("lsmod | grep nf_tables")
+  '';
+})
diff --git a/nixos/tests/matrix-synapse.nix b/nixos/tests/matrix-synapse.nix
index f3623aa3c094..9ca808721763 100644
--- a/nixos/tests/matrix-synapse.nix
+++ b/nixos/tests/matrix-synapse.nix
@@ -29,8 +29,8 @@ import ./make-test-python.nix ({ pkgs, ... } : let
 in {
 
   name = "matrix-synapse";
-  meta = with pkgs.stdenv.lib.maintainers; {
-    maintainers = [ corngood ];
+  meta = with pkgs.stdenv.lib; {
+    maintainers = teams.matrix.members;
   };
 
   nodes = {
diff --git a/nixos/tests/prometheus-exporters.nix b/nixos/tests/prometheus-exporters.nix
index 4fc3668cfafb..4dbd64312224 100644
--- a/nixos/tests/prometheus-exporters.nix
+++ b/nixos/tests/prometheus-exporters.nix
@@ -56,6 +56,21 @@ let
  */
 
   exporterTests = {
+     apcupsd = {
+      exporterConfig = {
+        enable = true;
+      };
+      metricProvider = {
+        services.apcupsd.enable = true;
+      };
+      exporterTest = ''
+        wait_for_unit("apcupsd.service")
+        wait_for_open_port(3551)
+        wait_for_unit("prometheus-apcupsd-exporter.service")
+        wait_for_open_port(9162)
+        succeed("curl -sSf http://localhost:9162/metrics | grep -q 'apcupsd_info'")
+      '';
+    };
 
     bind = {
       exporterConfig = {
@@ -202,6 +217,69 @@ let
       '';
     };
 
+    keylight = {
+      # A hardware device is required to properly test this exporter, so just
+      # perform a couple of basic sanity checks that the exporter is running
+      # and requires a target, but cannot reach a specified target.
+      exporterConfig = {
+        enable = true;
+      };
+      exporterTest = ''
+        wait_for_unit("prometheus-keylight-exporter.service")
+        wait_for_open_port(9288)
+        succeed(
+            "curl -sS --write-out '%{http_code}' -o /dev/null http://localhost:9288/metrics | grep -q '400'"
+        )
+        succeed(
+            "curl -sS --write-out '%{http_code}' -o /dev/null http://localhost:9288/metrics?target=nosuchdevice | grep -q '500'"
+        )
+      '';
+    };
+
+    lnd = {
+      exporterConfig = {
+        enable = true;
+        lndTlsPath = "/var/lib/lnd/tls.cert";
+        lndMacaroonDir = "/var/lib/lnd";
+      };
+      metricProvider = {
+        systemd.services.prometheus-lnd-exporter.serviceConfig.DynamicUser = false;
+        services.bitcoind.enable = true;
+        services.bitcoind.extraConfig = ''
+          rpcauth=bitcoinrpc:e8fe33f797e698ac258c16c8d7aadfbe$872bdb8f4d787367c26bcfd75e6c23c4f19d44a69f5d1ad329e5adf3f82710f7
+          bitcoind.zmqpubrawblock=tcp://127.0.0.1:28332
+          bitcoind.zmqpubrawtx=tcp://127.0.0.1:28333
+        '';
+        systemd.services.lnd = {
+          serviceConfig.ExecStart = ''
+          ${pkgs.lnd}/bin/lnd \
+            --datadir=/var/lib/lnd \
+            --tlscertpath=/var/lib/lnd/tls.cert \
+            --tlskeypath=/var/lib/lnd/tls.key \
+            --logdir=/var/log/lnd \
+            --bitcoin.active \
+            --bitcoin.mainnet \
+            --bitcoin.node=bitcoind \
+            --bitcoind.rpcuser=bitcoinrpc \
+            --bitcoind.rpcpass=hunter2 \
+            --bitcoind.zmqpubrawblock=tcp://127.0.0.1:28332 \
+            --bitcoind.zmqpubrawtx=tcp://127.0.0.1:28333 \
+            --readonlymacaroonpath=/var/lib/lnd/readonly.macaroon
+          '';
+          serviceConfig.StateDirectory = "lnd";
+          wantedBy = [ "multi-user.target" ];
+          after = [ "network.target" ];
+        };
+      };
+      exporterTest = ''
+        wait_for_unit("lnd.service")
+        wait_for_open_port(10009)
+        wait_for_unit("prometheus-lnd-exporter.service")
+        wait_for_open_port(9092)
+        succeed("curl -sSf localhost:9092/metrics | grep -q '^promhttp_metric_handler'")
+      '';
+    };
+
     mail = {
       exporterConfig = {
         enable = true;
diff --git a/nixos/tests/systemd-boot.nix b/nixos/tests/systemd-boot.nix
index e911c3933616..eba4729d6de8 100644
--- a/nixos/tests/systemd-boot.nix
+++ b/nixos/tests/systemd-boot.nix
@@ -6,26 +6,53 @@
 with import ../lib/testing-python.nix { inherit system pkgs; };
 with pkgs.lib;
 
-makeTest {
-  name = "systemd-boot";
-  meta.maintainers = with pkgs.stdenv.lib.maintainers; [ danielfullmer ];
-
-  machine = { pkgs, lib, ... }: {
+let
+  common = {
     virtualisation.useBootLoader = true;
     virtualisation.useEFIBoot = true;
     boot.loader.systemd-boot.enable = true;
   };
+in
+{
+  basic = makeTest {
+    name = "systemd-boot";
+    meta.maintainers = with pkgs.stdenv.lib.maintainers; [ danielfullmer ];
+
+    machine = common;
+
+    testScript = ''
+      machine.start()
+      machine.wait_for_unit("multi-user.target")
 
-  testScript = ''
-    machine.start()
-    machine.wait_for_unit("multi-user.target")
+      machine.succeed("test -e /boot/loader/entries/nixos-generation-1.conf")
+
+      # Ensure we actually booted using systemd-boot
+      # Magic number is the vendor UUID used by systemd-boot.
+      machine.succeed(
+          "test -e /sys/firmware/efi/efivars/LoaderEntrySelected-4a67b082-0a4c-41cf-b6c7-440b29bb8c4f"
+      )
+    '';
+  };
 
-    machine.succeed("test -e /boot/loader/entries/nixos-generation-1.conf")
+  update = makeTest {
+    name = "systemd-boot-update";
+    meta.maintainers = with pkgs.stdenv.lib.maintainers; [ danielfullmer ];
 
-    # Ensure we actually booted using systemd-boot.
-    # Magic number is the vendor UUID used by systemd-boot.
-    machine.succeed(
-        "test -e /sys/firmware/efi/efivars/LoaderEntrySelected-4a67b082-0a4c-41cf-b6c7-440b29bb8c4f"
-    )
-  '';
+    machine = common;
+
+    testScript = ''
+      machine.succeed("mount -o remount,rw /boot")
+
+      # Replace version inside sd-boot with something older. See magic[] string in systemd src/boot/efi/boot.c
+      machine.succeed(
+          """
+        find /boot -iname '*.efi' -print0 | \
+        xargs -0 -I '{}' sed -i 's/#### LoaderInfo: systemd-boot .* ####/#### LoaderInfo: systemd-boot 001 ####/' '{}'
+      """
+      )
+
+      output = machine.succeed("/run/current-system/bin/switch-to-configuration boot")
+      assert "updating systemd-boot from 001 to " in output
+    '';
+  };
 }
diff --git a/nixos/tests/teeworlds.nix b/nixos/tests/teeworlds.nix
new file mode 100644
index 000000000000..edf588968788
--- /dev/null
+++ b/nixos/tests/teeworlds.nix
@@ -0,0 +1,55 @@
+import ./make-test-python.nix ({ pkgs, ... }:
+
+let
+  client =
+    { pkgs, ... }:
+
+    { imports = [ ./common/x11.nix ];
+      environment.systemPackages = [ pkgs.teeworlds ];
+    };
+
+in {
+  name = "teeworlds";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ hax404 ];
+  };
+
+  nodes =
+    { server =
+      { services.teeworlds = {
+          enable = true;
+          openPorts = true;
+        };
+      };
+
+      client1 = client;
+      client2 = client;
+    };
+
+    testScript =
+    ''
+      start_all()
+
+      server.wait_for_unit("teeworlds.service")
+      server.wait_until_succeeds("ss --numeric --udp --listening | grep -q 8303")
+
+      client1.wait_for_x()
+      client2.wait_for_x()
+
+      client1.execute("teeworlds 'player_name Alice;connect server'&")
+      server.wait_until_succeeds(
+          'journalctl -u teeworlds -e | grep --extended-regexp -q "team_join player=\'[0-9]:Alice"'
+      )
+
+      client2.execute("teeworlds 'player_name Bob;connect server'&")
+      server.wait_until_succeeds(
+          'journalctl -u teeworlds -e | grep --extended-regexp -q "team_join player=\'[0-9]:Bob"'
+      )
+
+      server.sleep(10)  # wait for a while to get a nice screenshot
+
+      client1.screenshot("screen_client1")
+      client2.screenshot("screen_client2")
+    '';
+
+})