summary refs log tree commit diff
path: root/nixos
diff options
context:
space:
mode:
Diffstat (limited to 'nixos')
-rw-r--r--nixos/doc/manual/Makefile2
-rw-r--r--nixos/doc/manual/administration/container-networking.xml4
-rw-r--r--nixos/doc/manual/administration/declarative-containers.xml2
-rw-r--r--nixos/doc/manual/configuration/config-file.xml4
-rw-r--r--nixos/doc/manual/configuration/firewall.xml9
-rw-r--r--nixos/doc/manual/configuration/linux-kernel.xml19
-rw-r--r--nixos/doc/manual/default.nix11
-rw-r--r--nixos/doc/manual/development/building-parts.xml2
-rw-r--r--nixos/doc/manual/development/debugging-nixos-tests.xml37
-rw-r--r--nixos/doc/manual/development/nixos-tests.xml1
-rw-r--r--nixos/doc/manual/development/running-nixos-tests-interactively.xml10
-rw-r--r--nixos/doc/manual/development/writing-nixos-tests.xml2
-rw-r--r--nixos/doc/manual/installation/installing-behind-a-proxy.xml47
-rw-r--r--nixos/doc/manual/installation/installing-usb.xml48
-rw-r--r--nixos/doc/manual/installation/installing.xml707
-rw-r--r--nixos/doc/manual/installation/upgrading.xml9
-rw-r--r--nixos/doc/manual/manual.xml4
-rw-r--r--nixos/doc/manual/options-to-docbook.xsl11
-rw-r--r--nixos/doc/manual/postprocess-option-descriptions.xsl115
-rw-r--r--nixos/doc/manual/release-notes/release-notes.xml1
-rw-r--r--nixos/doc/manual/release-notes/rl-1509.xml10
-rw-r--r--nixos/doc/manual/release-notes/rl-1809.xml656
-rw-r--r--nixos/doc/manual/release-notes/rl-1903.xml204
-rw-r--r--nixos/lib/build-vms.nix2
-rw-r--r--nixos/lib/eval-config.nix5
-rw-r--r--nixos/lib/test-driver/Machine.pm9
-rw-r--r--nixos/modules/config/iproute2.nix35
-rw-r--r--nixos/modules/config/krb5/default.nix2
-rw-r--r--nixos/modules/config/networking.nix67
-rw-r--r--nixos/modules/config/pulseaudio.nix24
-rw-r--r--nixos/modules/config/shells-environment.nix33
-rw-r--r--nixos/modules/config/system-path.nix8
-rw-r--r--nixos/modules/config/users-groups.nix2
-rw-r--r--nixos/modules/config/xdg/mime.nix14
-rw-r--r--nixos/modules/hardware/opengl.nix22
-rw-r--r--nixos/modules/hardware/steam-hardware.nix25
-rw-r--r--nixos/modules/hardware/video/nvidia.nix132
-rw-r--r--nixos/modules/i18n/input-method/default.xml319
-rw-r--r--nixos/modules/installer/cd-dvd/installation-cd-graphical-base.nix49
-rw-r--r--nixos/modules/installer/cd-dvd/installation-cd-graphical-gnome.nix63
-rw-r--r--nixos/modules/installer/cd-dvd/installation-cd-graphical-kde.nix49
-rw-r--r--nixos/modules/installer/cd-dvd/installation-cd-minimal.nix2
-rw-r--r--nixos/modules/installer/cd-dvd/iso-image.nix2
-rw-r--r--nixos/modules/installer/cd-dvd/system-tarball-sheevaplug.nix2
-rw-r--r--nixos/modules/installer/tools/nix-fallback-paths.nix8
-rw-r--r--nixos/modules/installer/tools/nixos-generate-config.pl3
-rw-r--r--nixos/modules/installer/tools/nixos-install.sh1
-rw-r--r--nixos/modules/installer/tools/nixos-option.sh4
-rw-r--r--nixos/modules/installer/virtualbox-demo.nix38
-rw-r--r--nixos/modules/misc/documentation.nix101
-rw-r--r--nixos/modules/misc/ids.nix14
-rw-r--r--nixos/modules/misc/version.nix7
-rw-r--r--nixos/modules/module-list.nix24
-rw-r--r--nixos/modules/profiles/base.nix5
-rw-r--r--nixos/modules/profiles/clone-config.nix9
-rw-r--r--nixos/modules/profiles/graphical.nix7
-rw-r--r--nixos/modules/profiles/hardened.nix18
-rw-r--r--nixos/modules/profiles/installation-device.nix8
-rw-r--r--nixos/modules/profiles/minimal.nix1
-rw-r--r--nixos/modules/programs/bash/bash.nix25
-rw-r--r--nixos/modules/programs/dconf.nix2
-rw-r--r--nixos/modules/programs/digitalbitbox/doc.xml97
-rw-r--r--nixos/modules/programs/fish.nix32
-rw-r--r--nixos/modules/programs/light.nix5
-rw-r--r--nixos/modules/programs/plotinus.xml37
-rw-r--r--nixos/modules/programs/rootston.nix103
-rw-r--r--nixos/modules/programs/shell.nix10
-rw-r--r--nixos/modules/programs/sway-beta.nix54
-rw-r--r--nixos/modules/programs/thefuck.nix2
-rw-r--r--nixos/modules/programs/wavemon.nix28
-rw-r--r--nixos/modules/programs/yabar.nix17
-rw-r--r--nixos/modules/programs/zsh/oh-my-zsh.xml182
-rw-r--r--nixos/modules/programs/zsh/zsh.nix26
-rw-r--r--nixos/modules/rename.nix7
-rw-r--r--nixos/modules/security/acme.nix8
-rw-r--r--nixos/modules/security/acme.xml104
-rw-r--r--nixos/modules/security/apparmor-suid.nix2
-rw-r--r--nixos/modules/security/dhparams.nix2
-rw-r--r--nixos/modules/security/hidepid.xml37
-rw-r--r--nixos/modules/security/lock-kernel-modules.nix4
-rw-r--r--nixos/modules/security/misc.nix39
-rw-r--r--nixos/modules/security/pam.nix7
-rw-r--r--nixos/modules/security/polkit.nix10
-rw-r--r--nixos/modules/security/rngd.nix4
-rw-r--r--nixos/modules/security/wrappers/default.nix29
-rw-r--r--nixos/modules/services/admin/salt/master.nix3
-rw-r--r--nixos/modules/services/admin/salt/minion.nix21
-rw-r--r--nixos/modules/services/amqp/rabbitmq.nix90
-rw-r--r--nixos/modules/services/cluster/kubernetes/default.nix8
-rw-r--r--nixos/modules/services/computing/slurm/slurm.nix123
-rw-r--r--nixos/modules/services/continuous-integration/buildbot/master.nix67
-rw-r--r--nixos/modules/services/continuous-integration/buildbot/worker.nix77
-rw-r--r--nixos/modules/services/databases/foundationdb.xml621
-rw-r--r--nixos/modules/services/databases/postgresql.nix12
-rw-r--r--nixos/modules/services/databases/postgresql.xml96
-rw-r--r--nixos/modules/services/desktops/flatpak.xml71
-rw-r--r--nixos/modules/services/desktops/gnome3/rygel.nix30
-rw-r--r--nixos/modules/services/desktops/gsignond.nix43
-rw-r--r--nixos/modules/services/desktops/profile-sync-daemon.nix136
-rw-r--r--nixos/modules/services/editors/emacs.xml821
-rw-r--r--nixos/modules/services/hardware/lirc.nix89
-rw-r--r--nixos/modules/services/hardware/ratbagd.nix32
-rw-r--r--nixos/modules/services/hardware/trezord.nix17
-rw-r--r--nixos/modules/services/hardware/triggerhappy.nix114
-rw-r--r--nixos/modules/services/hardware/upower.nix26
-rw-r--r--nixos/modules/services/logging/journaldriver.nix2
-rw-r--r--nixos/modules/services/mail/clamsmtp.nix2
-rw-r--r--nixos/modules/services/mail/dkimproxy-out.nix2
-rw-r--r--nixos/modules/services/mail/dovecot.nix6
-rw-r--r--nixos/modules/services/mail/exim.nix18
-rw-r--r--nixos/modules/services/mail/postfix.nix18
-rw-r--r--nixos/modules/services/mail/rmilter.nix7
-rw-r--r--nixos/modules/services/mail/rspamd.nix85
-rw-r--r--nixos/modules/services/misc/airsonic.nix19
-rw-r--r--nixos/modules/services/misc/emby.nix19
-rw-r--r--nixos/modules/services/misc/gitlab.nix19
-rw-r--r--nixos/modules/services/misc/gitlab.xml124
-rw-r--r--nixos/modules/services/misc/home-assistant.nix1
-rw-r--r--nixos/modules/services/misc/lidarr.nix46
-rw-r--r--nixos/modules/services/misc/nix-daemon.nix11
-rw-r--r--nixos/modules/services/misc/nix-optimise.nix2
-rw-r--r--nixos/modules/services/misc/nixos-manual.nix133
-rw-r--r--nixos/modules/services/misc/redmine.nix168
-rw-r--r--nixos/modules/services/misc/sickbeard.nix92
-rw-r--r--nixos/modules/services/misc/taskserver/doc.xml209
-rw-r--r--nixos/modules/services/misc/weechat.nix58
-rw-r--r--nixos/modules/services/misc/weechat.xml66
-rw-r--r--nixos/modules/services/monitoring/datadog-agent.nix41
-rw-r--r--nixos/modules/services/monitoring/grafana.nix117
-rw-r--r--nixos/modules/services/monitoring/munin.nix12
-rw-r--r--nixos/modules/services/monitoring/prometheus/alertmanager.nix23
-rw-r--r--nixos/modules/services/monitoring/prometheus/default.nix11
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters.nix22
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters.xml148
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/snmp.nix8
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/tor.nix40
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/varnish.nix1
-rw-r--r--nixos/modules/services/monitoring/riemann.nix22
-rw-r--r--nixos/modules/services/network-filesystems/openafs/client.nix2
-rw-r--r--nixos/modules/services/network-filesystems/openafs/server.nix1
-rw-r--r--nixos/modules/services/networking/bitlbee.nix43
-rw-r--r--nixos/modules/services/networking/charybdis.nix2
-rw-r--r--nixos/modules/services/networking/chrony.nix52
-rw-r--r--nixos/modules/services/networking/ddclient.nix3
-rw-r--r--nixos/modules/services/networking/dnscrypt-proxy.xml69
-rw-r--r--nixos/modules/services/networking/epmd.nix56
-rw-r--r--nixos/modules/services/networking/eternal-terminal.nix89
-rw-r--r--nixos/modules/services/networking/hostapd.nix6
-rw-r--r--nixos/modules/services/networking/hylafax/default.nix29
-rw-r--r--nixos/modules/services/networking/hylafax/faxq-default.nix12
-rwxr-xr-xnixos/modules/services/networking/hylafax/faxq-wait.sh29
-rw-r--r--nixos/modules/services/networking/hylafax/hfaxd-default.nix10
-rw-r--r--nixos/modules/services/networking/hylafax/modem-default.nix22
-rw-r--r--nixos/modules/services/networking/hylafax/options.nix375
-rwxr-xr-xnixos/modules/services/networking/hylafax/spool.sh111
-rw-r--r--nixos/modules/services/networking/hylafax/systemd.nix249
-rw-r--r--nixos/modules/services/networking/i2pd.nix604
-rw-r--r--nixos/modules/services/networking/iperf3.nix87
-rw-r--r--nixos/modules/services/networking/iwd.nix2
-rw-r--r--nixos/modules/services/networking/miniupnpd.nix24
-rw-r--r--nixos/modules/services/networking/murmur.nix2
-rw-r--r--nixos/modules/services/networking/networkmanager.nix14
-rw-r--r--nixos/modules/services/networking/nullidentdmod.nix34
-rw-r--r--nixos/modules/services/networking/pptpd.nix2
-rw-r--r--nixos/modules/services/networking/redsocks.nix2
-rw-r--r--nixos/modules/services/networking/shairport-sync.nix3
-rw-r--r--nixos/modules/services/networking/strongswan-swanctl/swanctl-params.nix83
-rw-r--r--nixos/modules/services/networking/syncthing.nix21
-rw-r--r--nixos/modules/services/networking/teamspeak3.nix2
-rw-r--r--nixos/modules/services/networking/tinc.nix8
-rw-r--r--nixos/modules/services/networking/xl2tpd.nix2
-rw-r--r--nixos/modules/services/networking/xrdp.nix2
-rw-r--r--nixos/modules/services/networking/zeronet.nix20
-rw-r--r--nixos/modules/services/networking/znc.nix431
-rw-r--r--nixos/modules/services/networking/znc/default.nix306
-rw-r--r--nixos/modules/services/networking/znc/options.nix270
-rw-r--r--nixos/modules/services/printing/cupsd.nix2
-rw-r--r--nixos/modules/services/security/clamav.nix7
-rw-r--r--nixos/modules/services/security/munge.nix2
-rw-r--r--nixos/modules/services/security/sks.nix100
-rw-r--r--nixos/modules/services/security/tor.nix7
-rw-r--r--nixos/modules/services/system/kerberos.nix6
-rw-r--r--nixos/modules/services/system/saslauthd.nix2
-rw-r--r--nixos/modules/services/ttys/kmscon.nix13
-rw-r--r--nixos/modules/services/web-apps/codimd.nix920
-rw-r--r--nixos/modules/services/web-apps/matomo-doc.xml131
-rw-r--r--nixos/modules/services/web-apps/matomo.nix15
-rw-r--r--nixos/modules/services/web-apps/nextcloud.nix482
-rw-r--r--nixos/modules/services/web-apps/tt-rss.nix6
-rw-r--r--nixos/modules/services/web-servers/nginx/default.nix52
-rw-r--r--nixos/modules/services/web-servers/nginx/location-options.nix10
-rw-r--r--nixos/modules/services/web-servers/tomcat.nix31
-rw-r--r--nixos/modules/services/x11/compton.nix6
-rw-r--r--nixos/modules/services/x11/desktop-managers/enlightenment.nix2
-rw-r--r--nixos/modules/services/x11/desktop-managers/gnome3.nix2
-rw-r--r--nixos/modules/services/x11/desktop-managers/plasma5.nix16
-rw-r--r--nixos/modules/services/x11/desktop-managers/xfce.nix3
-rw-r--r--nixos/modules/services/x11/display-managers/default.nix13
-rw-r--r--nixos/modules/services/x11/display-managers/gdm.nix17
-rw-r--r--nixos/modules/services/x11/display-managers/lightdm-greeters/enso-os.nix159
-rw-r--r--nixos/modules/services/x11/display-managers/lightdm-greeters/gtk.nix2
-rw-r--r--nixos/modules/services/x11/display-managers/lightdm.nix30
-rw-r--r--nixos/modules/services/x11/display-managers/sddm.nix8
-rw-r--r--nixos/modules/services/x11/display-managers/startx.nix44
-rw-r--r--nixos/modules/services/x11/gdk-pixbuf.nix45
-rw-r--r--nixos/modules/services/x11/hardware/libinput.nix2
-rw-r--r--nixos/modules/services/x11/xserver.nix12
-rw-r--r--nixos/modules/system/activation/activation-script.nix64
-rw-r--r--nixos/modules/system/activation/switch-to-configuration.pl3
-rw-r--r--nixos/modules/system/activation/top-level.nix63
-rw-r--r--nixos/modules/system/boot/initrd-network.nix27
-rw-r--r--nixos/modules/system/boot/kexec.nix2
-rw-r--r--nixos/modules/system/boot/loader/raspberrypi/builder_uboot.sh29
-rw-r--r--nixos/modules/system/boot/loader/raspberrypi/raspberrypi-builder.nix10
-rw-r--r--nixos/modules/system/boot/loader/raspberrypi/raspberrypi-builder.sh (renamed from nixos/modules/system/boot/loader/raspberrypi/builder.sh)74
-rw-r--r--nixos/modules/system/boot/loader/raspberrypi/raspberrypi.nix24
-rw-r--r--nixos/modules/system/boot/loader/raspberrypi/uboot-builder.nix (renamed from nixos/modules/system/boot/loader/raspberrypi/builder_uboot.nix)13
-rw-r--r--nixos/modules/system/boot/loader/raspberrypi/uboot-builder.sh38
-rw-r--r--nixos/modules/system/boot/luksroot.nix67
-rw-r--r--nixos/modules/system/boot/networkd.nix6
-rw-r--r--nixos/modules/system/boot/stage-1.nix6
-rw-r--r--nixos/modules/system/boot/stage-2-init.sh8
-rw-r--r--nixos/modules/system/boot/systemd-lib.nix2
-rw-r--r--nixos/modules/system/boot/systemd-nspawn.nix4
-rw-r--r--nixos/modules/system/boot/systemd-unit-options.nix2
-rw-r--r--nixos/modules/system/boot/systemd.nix31
-rw-r--r--nixos/modules/tasks/filesystems/zfs.nix2
-rw-r--r--nixos/modules/tasks/network-interfaces-scripted.nix3
-rw-r--r--nixos/modules/tasks/network-interfaces.nix2
-rw-r--r--nixos/modules/testing/test-instrumentation.nix3
-rw-r--r--nixos/modules/virtualisation/amazon-image.nix6
-rw-r--r--nixos/modules/virtualisation/container-config.nix7
-rw-r--r--nixos/modules/virtualisation/containers.nix10
-rw-r--r--nixos/modules/virtualisation/ec2-amis.nix19
-rw-r--r--nixos/modules/virtualisation/google-compute-image.nix44
-rw-r--r--nixos/modules/virtualisation/hyperv-guest.nix35
-rw-r--r--nixos/modules/virtualisation/kvmgt.nix8
-rw-r--r--nixos/modules/virtualisation/libvirtd.nix2
-rw-r--r--nixos/modules/virtualisation/qemu-guest-agent.nix2
-rw-r--r--nixos/modules/virtualisation/virtualbox-image.nix10
-rw-r--r--nixos/modules/virtualisation/xe-guest-utilities.nix2
-rw-r--r--nixos/release.nix47
-rw-r--r--nixos/tests/acme.nix29
-rw-r--r--nixos/tests/atd.nix1
-rw-r--r--nixos/tests/bittorrent.nix101
-rw-r--r--nixos/tests/buildbot.nix216
-rw-r--r--nixos/tests/ceph.nix7
-rw-r--r--nixos/tests/chromium.nix6
-rw-r--r--nixos/tests/cjdns.nix3
-rw-r--r--nixos/tests/codimd.nix54
-rw-r--r--nixos/tests/common/letsencrypt/common.nix27
-rw-r--r--nixos/tests/containers-bridge.nix1
-rw-r--r--nixos/tests/containers-extra_veth.nix1
-rw-r--r--nixos/tests/containers-imperative.nix11
-rw-r--r--nixos/tests/containers-ipv4.nix1
-rw-r--r--nixos/tests/containers-ipv6.nix1
-rw-r--r--nixos/tests/containers-portforward.nix1
-rw-r--r--nixos/tests/containers-restart_networking.nix1
-rw-r--r--nixos/tests/docker-tools.nix11
-rw-r--r--nixos/tests/ferm.nix4
-rw-r--r--nixos/tests/gdk-pixbuf.nix6
-rw-r--r--nixos/tests/hibernate.nix2
-rw-r--r--nixos/tests/home-assistant.nix3
-rw-r--r--nixos/tests/hound.nix2
-rw-r--r--nixos/tests/installer.nix87
-rw-r--r--nixos/tests/kernel-copperhead.nix19
-rw-r--r--nixos/tests/misc.nix4
-rw-r--r--nixos/tests/nat.nix2
-rw-r--r--nixos/tests/networking.nix13
-rw-r--r--nixos/tests/nextcloud/basic.nix56
-rw-r--r--nixos/tests/nextcloud/default.nix6
-rw-r--r--nixos/tests/nextcloud/with-mysql-and-memcached.nix97
-rw-r--r--nixos/tests/nextcloud/with-postgresql-and-redis.nix130
-rw-r--r--nixos/tests/nix-ssh-serve.nix4
-rw-r--r--nixos/tests/novacomd.nix8
-rw-r--r--nixos/tests/opensmtpd.nix14
-rw-r--r--nixos/tests/plasma5.nix21
-rw-r--r--nixos/tests/postgis.nix2
-rw-r--r--nixos/tests/prometheus-exporters.nix316
-rw-r--r--nixos/tests/prosody.nix3
-rw-r--r--nixos/tests/quagga.nix1
-rw-r--r--nixos/tests/redmine.nix40
-rw-r--r--nixos/tests/rspamd.nix54
-rw-r--r--nixos/tests/rsyslogd.nix38
-rw-r--r--nixos/tests/slurm.nix60
-rw-r--r--nixos/tests/upnp.nix94
-rw-r--r--nixos/tests/yabar.nix16
287 files changed, 11170 insertions, 4256 deletions
diff --git a/nixos/doc/manual/Makefile b/nixos/doc/manual/Makefile
index 2e9adf70c396..b251a1f5e2c3 100644
--- a/nixos/doc/manual/Makefile
+++ b/nixos/doc/manual/Makefile
@@ -4,7 +4,7 @@ all: manual-combined.xml format
 .PHONY: debug
 debug: generated manual-combined.xml
 
-manual-combined.xml: generated *.xml
+manual-combined.xml: generated *.xml **/*.xml
 	rm -f ./manual-combined.xml
 	nix-shell --packages xmloscopy \
 		--run "xmloscopy --docbook5 ./manual.xml ./manual-combined.xml"
diff --git a/nixos/doc/manual/administration/container-networking.xml b/nixos/doc/manual/administration/container-networking.xml
index 4b977d1d82eb..2ee8bfdd50f1 100644
--- a/nixos/doc/manual/administration/container-networking.xml
+++ b/nixos/doc/manual/administration/container-networking.xml
@@ -52,4 +52,8 @@ $ ping -c1 10.233.4.2
 networking.networkmanager.unmanaged = [ "interface-name:ve-*" ];
 </programlisting>
  </para>
+
+ <para>
+  You may need to restart your system for the changes to take effect.
+ </para>
 </section>
diff --git a/nixos/doc/manual/administration/declarative-containers.xml b/nixos/doc/manual/administration/declarative-containers.xml
index 2a98fb126231..d03dbc4d7055 100644
--- a/nixos/doc/manual/administration/declarative-containers.xml
+++ b/nixos/doc/manual/administration/declarative-containers.xml
@@ -15,7 +15,7 @@ containers.database =
   { config =
       { config, pkgs, ... }:
       { <xref linkend="opt-services.postgresql.enable"/> = true;
-      <xref linkend="opt-services.postgresql.package"/> = pkgs.postgresql96;
+      <xref linkend="opt-services.postgresql.package"/> = pkgs.postgresql_9_6;
       };
   };
 </programlisting>
diff --git a/nixos/doc/manual/configuration/config-file.xml b/nixos/doc/manual/configuration/config-file.xml
index 8a1a39c98c10..c77cfe137baa 100644
--- a/nixos/doc/manual/configuration/config-file.xml
+++ b/nixos/doc/manual/configuration/config-file.xml
@@ -197,10 +197,10 @@ swapDevices = [ { device = "/dev/disk/by-label/swap"; } ];
     pkgs.emacs
   ];
 
-<xref linkend="opt-services.postgresql.package"/> = pkgs.postgresql90;
+<xref linkend="opt-services.postgresql.package"/> = pkgs.postgresql_10;
 </programlisting>
       The latter option definition changes the default PostgreSQL package used
-      by NixOS’s PostgreSQL service to 9.0. For more information on packages,
+      by NixOS’s PostgreSQL service to 10.x. For more information on packages,
       including how to add new ones, see <xref linkend="sec-custom-packages"/>.
      </para>
     </listitem>
diff --git a/nixos/doc/manual/configuration/firewall.xml b/nixos/doc/manual/configuration/firewall.xml
index b66adcedce6e..47a19ac82c0f 100644
--- a/nixos/doc/manual/configuration/firewall.xml
+++ b/nixos/doc/manual/configuration/firewall.xml
@@ -34,13 +34,4 @@
   Similarly, UDP port ranges can be opened through
   <xref linkend="opt-networking.firewall.allowedUDPPortRanges"/>.
  </para>
-
- <para>
-  Also of interest is
-<programlisting>
-<xref linkend="opt-networking.firewall.allowPing"/> = true;
-</programlisting>
-  to allow the machine to respond to ping requests. (ICMPv6 pings are always
-  allowed.)
- </para>
 </section>
diff --git a/nixos/doc/manual/configuration/linux-kernel.xml b/nixos/doc/manual/configuration/linux-kernel.xml
index 2f766f2b32f7..644d3a33ffd2 100644
--- a/nixos/doc/manual/configuration/linux-kernel.xml
+++ b/nixos/doc/manual/configuration/linux-kernel.xml
@@ -84,18 +84,17 @@ nixpkgs.config.packageOverrides = pkgs:
     allowImportFromDerivation = true;
   };
   ]]></screen>
-
-You can edit the config with this snippet (by default <command>make menuconfig</command> won't work
-  out of the box on nixos):
-  <screen><![CDATA[
+   You can edit the config with this snippet (by default <command>make
+   menuconfig</command> won't work out of the box on nixos):
+<screen><![CDATA[
       nix-shell -E 'with import <nixpkgs> {}; kernelToOverride.overrideAttrs (o: {nativeBuildInputs=o.nativeBuildInputs ++ [ pkgconfig ncurses ];})'
   ]]></screen>
-
-
-  or you can let nixpkgs generate the configuration.
-  Nixpkgs generates it via answering the interactive kernel utility <command>make config</command>.
-  The answers depend on parameters passed to <filename>pkgs/os-specific/linux/kernel/generic.nix</filename>
-  (which you can influence by overriding <literal>extraConfig, autoModules, modDirVersion, preferBuiltin, extraConfig</literal>).
+   or you can let nixpkgs generate the configuration. Nixpkgs generates it via
+   answering the interactive kernel utility <command>make config</command>. The
+   answers depend on parameters passed to
+   <filename>pkgs/os-specific/linux/kernel/generic.nix</filename> (which you
+   can influence by overriding <literal>extraConfig, autoModules,
+   modDirVersion, preferBuiltin, extraConfig</literal>).
 <screen><![CDATA[
 
   mptcp93.override ({
diff --git a/nixos/doc/manual/default.nix b/nixos/doc/manual/default.nix
index be28c2c17afd..faae4f205443 100644
--- a/nixos/doc/manual/default.nix
+++ b/nixos/doc/manual/default.nix
@@ -90,7 +90,9 @@ let
     fi
     ${buildPackages.libxslt.bin}/bin/xsltproc \
       --stringparam revision '${revision}' \
-      -o $out ${./options-to-docbook.xsl} $optionsXML
+      -o intermediate.xml ${./options-to-docbook.xsl} $optionsXML
+    ${buildPackages.libxslt.bin}/bin/xsltproc \
+      -o "$out" ${./postprocess-option-descriptions.xsl} intermediate.xml
   '';
 
   sources = lib.sourceFilesBySuffices ./. [".xml"];
@@ -250,7 +252,7 @@ in rec {
     ''; # */
 
   # Generate the NixOS manual.
-  manual = runCommand "nixos-manual"
+  manualHTML = runCommand "nixos-manual-html"
     { inherit sources;
       nativeBuildInputs = [ buildPackages.libxml2.bin buildPackages.libxslt.bin ];
       meta.description = "The NixOS manual in HTML format";
@@ -279,6 +281,11 @@ in rec {
       echo "doc manual $dst" >> $out/nix-support/hydra-build-products
     ''; # */
 
+  # Alias for backward compatibility. TODO(@oxij): remove eventually.
+  manual = manualHTML;
+
+  # Index page of the NixOS manual.
+  manualHTMLIndex = "${manualHTML}/share/doc/nixos/index.html";
 
   manualEpub = runCommand "nixos-manual-epub"
     { inherit sources;
diff --git a/nixos/doc/manual/development/building-parts.xml b/nixos/doc/manual/development/building-parts.xml
index eaffc0ef47c2..b4791b72970f 100644
--- a/nixos/doc/manual/development/building-parts.xml
+++ b/nixos/doc/manual/development/building-parts.xml
@@ -34,7 +34,7 @@ $ nix-build -A system</screen>
    </varlistentry>
    <varlistentry>
     <term>
-     <varname>system.build.manual.manual</varname>
+     <varname>system.build.manual.manualHTML</varname>
     </term>
     <listitem>
      <para>
diff --git a/nixos/doc/manual/development/debugging-nixos-tests.xml b/nixos/doc/manual/development/debugging-nixos-tests.xml
new file mode 100644
index 000000000000..30e58e1e3554
--- /dev/null
+++ b/nixos/doc/manual/development/debugging-nixos-tests.xml
@@ -0,0 +1,37 @@
+<section xmlns="http://docbook.org/ns/docbook"
+        xmlns:xlink="http://www.w3.org/1999/xlink"
+        xmlns:xi="http://www.w3.org/2001/XInclude"
+        version="5.0"
+        xml:id="sec-debugging-nixos-tests">
+ <title>Debugging NixOS tests</title>
+
+ <para>
+  Tests may fail and infrastructure offers access to inspect machine state.
+ </para>
+
+ <para>
+  To prevent test from stopping and cleaning up, insert a sleep command:
+ </para>
+
+<programlisting>
+$machine->succeed("sleep 84000");
+</programlisting>
+
+ <para>
+  As soon as machine starts run as root:
+ </para>
+
+<programlisting>
+nix-shell -p socat --run "socat STDIO,raw,echo=0,escape=0x11 UNIX:/tmp/nix-build-vm-test-run-*.drv-0/vm-state-machine/backdoor"
+</programlisting>
+
+ <para>
+  You may need to find the correct path, replacing <literal>/tmp</literal>,
+  <literal>*</literal> or <literal>machine</literal>.
+ </para>
+
+ <para>
+  Press "enter" to open up console and login as "root". After you're done,
+  press "ctrl-q" to exit the console.
+ </para>
+</section>
diff --git a/nixos/doc/manual/development/nixos-tests.xml b/nixos/doc/manual/development/nixos-tests.xml
index 2695082e3867..d068887200a9 100644
--- a/nixos/doc/manual/development/nixos-tests.xml
+++ b/nixos/doc/manual/development/nixos-tests.xml
@@ -16,4 +16,5 @@ xlink:href="https://github.com/NixOS/nixpkgs/tree/master/nixos/tests">nixos/test
  <xi:include href="writing-nixos-tests.xml" />
  <xi:include href="running-nixos-tests.xml" />
  <xi:include href="running-nixos-tests-interactively.xml" />
+ <xi:include href="debugging-nixos-tests.xml" />
 </chapter>
diff --git a/nixos/doc/manual/development/running-nixos-tests-interactively.xml b/nixos/doc/manual/development/running-nixos-tests-interactively.xml
index 862b364a6d79..b25d3dcb9116 100644
--- a/nixos/doc/manual/development/running-nixos-tests-interactively.xml
+++ b/nixos/doc/manual/development/running-nixos-tests-interactively.xml
@@ -19,6 +19,7 @@ starting VDE switch for network 1
 &gt; startAll
 &gt; testScript
 &gt; $machine->succeed("touch /tmp/foo")
+&gt; print($machine->succeed("pwd"), "\n") # Show stdout of command
 </screen>
   The function <command>testScript</command> executes the entire test script
   and drops you back into the test driver command line upon its completion.
@@ -33,8 +34,11 @@ $ nix-build nixos/tests/login.nix -A driver
 $ ./result/bin/nixos-run-vms
 </screen>
   The script <command>nixos-run-vms</command> starts the virtual machines
-  defined by test. The root file system of the VMs is created on the fly and
-  kept across VM restarts in
-  <filename>./</filename><varname>hostname</varname><filename>.qcow2</filename>.
+  defined by test.
+ </para>
+
+ <para>
+  The machine state is kept across VM restarts in
+  <filename>/tmp/vm-state-</filename><varname>machinename</varname>.
  </para>
 </section>
diff --git a/nixos/doc/manual/development/writing-nixos-tests.xml b/nixos/doc/manual/development/writing-nixos-tests.xml
index 5935fbc049bd..983f8f9cbe3e 100644
--- a/nixos/doc/manual/development/writing-nixos-tests.xml
+++ b/nixos/doc/manual/development/writing-nixos-tests.xml
@@ -108,7 +108,7 @@ xlink:href="https://github.com/NixOS/nixpkgs/blob/master/nixos/modules/virtualis
 <programlisting>
 $machine->start;
 $machine->waitForUnit("default.target");
-$machine->succeed("uname") =~ /Linux/;
+die unless $machine->succeed("uname") =~ /Linux/;
 </programlisting>
   The first line is actually unnecessary; machines are implicitly started when
   you first execute an action on them (such as <literal>waitForUnit</literal>
diff --git a/nixos/doc/manual/installation/installing-behind-a-proxy.xml b/nixos/doc/manual/installation/installing-behind-a-proxy.xml
index c59d073c61c7..8f9baff44b51 100644
--- a/nixos/doc/manual/installation/installing-behind-a-proxy.xml
+++ b/nixos/doc/manual/installation/installing-behind-a-proxy.xml
@@ -5,28 +5,29 @@
          xml:id="sec-installing-behind-proxy">
  <title>Installing behind a proxy</title>
 
-<para>
+ <para>
   To install NixOS behind a proxy, do the following before running
   <literal>nixos-install</literal>.
-</para>
-<orderedlist numeration="arabic">
+ </para>
+
+ <orderedlist numeration="arabic">
   <listitem>
-    <para>
-      Update proxy configuration in
-      <literal>/mnt/etc/nixos/configuration.nix</literal> to keep the
-      internet accessible after reboot.
-    </para>
-    <programlisting>
+   <para>
+    Update proxy configuration in
+    <literal>/mnt/etc/nixos/configuration.nix</literal> to keep the internet
+    accessible after reboot.
+   </para>
+<programlisting>
 networking.proxy.default = &quot;http://user:password@proxy:port/&quot;;
 networking.proxy.noProxy = &quot;127.0.0.1,localhost,internal.domain&quot;;
 </programlisting>
   </listitem>
   <listitem>
-    <para>
-      Setup the proxy environment variables in the shell where you are
-      running <literal>nixos-install</literal>.
-    </para>
-    <programlisting>
+   <para>
+    Setup the proxy environment variables in the shell where you are running
+    <literal>nixos-install</literal>.
+   </para>
+<programlisting>
 # proxy_url=&quot;http://user:password@proxy:port/&quot;
 # export http_proxy=&quot;$proxy_url&quot;
 # export HTTP_PROXY=&quot;$proxy_url&quot;
@@ -34,14 +35,14 @@ networking.proxy.noProxy = &quot;127.0.0.1,localhost,internal.domain&quot;;
 # export HTTPS_PROXY=&quot;$proxy_url&quot;
 </programlisting>
   </listitem>
-</orderedlist>
+ </orderedlist>
 
-<note>
-<para>
-  If you are switching networks with different proxy configurations, use the
-  <literal>nesting.clone</literal> option in
-  <literal>configuration.nix</literal> to switch proxies at runtime.
-  Refer to <xref linkend="ch-options" /> for more information.
-</para>
-</note>
+ <note>
+  <para>
+   If you are switching networks with different proxy configurations, use the
+   <literal>nesting.clone</literal> option in
+   <literal>configuration.nix</literal> to switch proxies at runtime. Refer to
+   <xref linkend="ch-options" /> for more information.
+  </para>
+ </note>
 </section>
diff --git a/nixos/doc/manual/installation/installing-usb.xml b/nixos/doc/manual/installation/installing-usb.xml
index c5934111749c..0b311189430c 100644
--- a/nixos/doc/manual/installation/installing-usb.xml
+++ b/nixos/doc/manual/installation/installing-usb.xml
@@ -9,13 +9,12 @@
   For systems without CD drive, the NixOS live CD can be booted from a USB
   stick. You can use the <command>dd</command> utility to write the image:
   <command>dd if=<replaceable>path-to-image</replaceable>
-  of=<replaceable>/dev/sdb</replaceable></command>. Be careful about specifying
+  of=<replaceable>/dev/sdX</replaceable></command>. Be careful about specifying
   the correct drive; you can use the <command>lsblk</command> command to get a
   list of block devices.
- </para>
-
- <para>
-  On macOS:
+  <note>
+   <title>On macOS</title>
+   <para>
 <programlisting>
 $ diskutil list
 [..]
@@ -26,43 +25,16 @@ $ diskutil unmountDisk diskN
 Unmount of all volumes on diskN was successful
 $ sudo dd bs=1m if=nix.iso of=/dev/rdiskN
 </programlisting>
-  Using the 'raw' <command>rdiskN</command> device instead of
-  <command>diskN</command> completes in minutes instead of hours. After
-  <command>dd</command> completes, a GUI dialog "The disk you inserted was not
-  readable by this computer" will pop up, which can be ignored.
+    Using the 'raw' <command>rdiskN</command> device instead of
+    <command>diskN</command> completes in minutes instead of hours. After
+    <command>dd</command> completes, a GUI dialog "The disk you inserted was
+    not readable by this computer" will pop up, which can be ignored.
+   </para>
+  </note>
  </para>
 
  <para>
   The <command>dd</command> utility will write the image verbatim to the drive,
   making it the recommended option for both UEFI and non-UEFI installations.
-  For non-UEFI installations, you can alternatively use
-  <link xlink:href="http://unetbootin.sourceforge.net/">unetbootin</link>. If
-  you cannot use <command>dd</command> for a UEFI installation, you can also
-  mount the ISO, copy its contents verbatim to your drive, then either:
-  <itemizedlist>
-   <listitem>
-    <para>
-     Change the label of the disk partition to the label of the ISO (visible
-     with the blkid command), or
-    </para>
-   </listitem>
-   <listitem>
-    <para>
-     Edit <filename>loader/entries/nixos-livecd.conf</filename> on the drive
-     and change the <literal>root=</literal> field in the
-     <literal>options</literal> line to point to your drive (see the
-     documentation on <literal>root=</literal> in
-     <link xlink:href="https://www.kernel.org/doc/Documentation/admin-guide/kernel-parameters.txt">
-     the kernel documentation</link> for more details).
-    </para>
-   </listitem>
-   <listitem>
-    <para>
-     If you want to load the contents of the ISO to ram after bootin (So you
-     can remove the stick after bootup) you can append the parameter
-     <literal>copytoram</literal> to the <literal>options</literal> field.
-    </para>
-   </listitem>
-  </itemizedlist>
  </para>
 </section>
diff --git a/nixos/doc/manual/installation/installing.xml b/nixos/doc/manual/installation/installing.xml
index 916384559e24..8e94f946c5ee 100644
--- a/nixos/doc/manual/installation/installing.xml
+++ b/nixos/doc/manual/installation/installing.xml
@@ -4,60 +4,46 @@
             version="5.0"
             xml:id="sec-installation">
  <title>Installing NixOS</title>
- <para>
-  NixOS can be installed on BIOS or UEFI systems. The procedure for a UEFI
-  installation is by and large the same as a BIOS installation. The differences
-  are mentioned in the steps that follow.
- </para>
- <orderedlist>
-  <listitem>
-   <para>
-    Boot from the CD.
-   </para>
-   <variablelist>
-    <varlistentry>
-     <term>
-      UEFI systems
-     </term>
-     <listitem>
-      <para>
-       You should boot the live CD in UEFI mode (consult your specific
-       hardware's documentation for instructions). You may find the
-       <link xlink:href="http://www.rodsbooks.com/refind">rEFInd boot
-       manager</link> useful.
-      </para>
-     </listitem>
-    </varlistentry>
-   </variablelist>
-  </listitem>
-  <listitem>
-   <para>
-    The CD contains a basic NixOS installation. (It also contains Memtest86+,
-    useful if you want to test new hardware). When it’s finished booting, it
-    should have detected most of your hardware.
-   </para>
-  </listitem>
-  <listitem>
-   <para>
-    The NixOS manual is available on virtual console 8 (press Alt+F8 to access)
-    or by running <command>nixos-help</command>.
-   </para>
-  </listitem>
-  <listitem>
-   <para>
-    You get logged in as <literal>root</literal> (with empty password).
-   </para>
-  </listitem>
-  <listitem>
-   <para>
-    If you downloaded the graphical ISO image, you can run <command>systemctl
-    start display-manager</command> to start KDE. If you want to continue on
-    the terminal, you can use <command>loadkeys</command> to switch to your
-    preferred keyboard layout. (We even provide neo2 via <command>loadkeys de
-    neo</command>!)
-   </para>
-  </listitem>
-  <listitem>
+ <section xml:id="sec-installation-booting">
+  <title>Booting the system</title>
+
+  <para>
+   NixOS can be installed on BIOS or UEFI systems. The procedure for a UEFI
+   installation is by and large the same as a BIOS installation. The
+   differences are mentioned in the steps that follow.
+  </para>
+
+  <para>
+   The installation media can be burned to a CD, or now more commonly, "burned"
+   to a USB drive (see <xref linkend="sec-booting-from-usb"/>).
+  </para>
+
+  <para>
+   The installation media contains a basic NixOS installation. When it’s
+   finished booting, it should have detected most of your hardware.
+  </para>
+
+  <para>
+   The NixOS manual is available on virtual console 8 (press Alt+F8 to access)
+   or by running <command>nixos-help</command>.
+  </para>
+
+  <para>
+   You are logged-in automatically as <literal>root</literal>. (The
+   <literal>root</literal> user account has an empty password.)
+  </para>
+
+  <para>
+   If you downloaded the graphical ISO image, you can run <command>systemctl
+   start display-manager</command> to start KDE. If you want to continue on the
+   terminal, you can use <command>loadkeys</command> to switch to your
+   preferred keyboard layout. (We even provide neo2 via <command>loadkeys de
+   neo</command>!)
+  </para>
+
+  <section xml:id="sec-installation-booting-networking">
+   <title>Networking in the installer</title>
+
    <para>
     The boot process should have brought up networking (check <command>ip
     a</command>). Networking is necessary for the installer, since it will
@@ -65,60 +51,167 @@
     binaries). It’s best if you have a DHCP server on your network. Otherwise
     configure networking manually using <command>ifconfig</command>.
    </para>
+
    <para>
     To manually configure the network on the graphical installer, first disable
     network-manager with <command>systemctl stop network-manager</command>.
    </para>
+
    <para>
     To manually configure the wifi on the minimal installer, run
     <command>wpa_supplicant -B -i interface -c &lt;(wpa_passphrase 'SSID'
     'key')</command>.
    </para>
-  </listitem>
-  <listitem>
+
    <para>
     If you would like to continue the installation from a different machine you
     need to activate the SSH daemon via <literal>systemctl start
     sshd</literal>. In order to be able to login you also need to set a
     password for <literal>root</literal> using <literal>passwd</literal>.
    </para>
-  </listitem>
-  <listitem>
+  </section>
+ </section>
+ <section xml:id="sec-installation-partitioning">
+  <title>Partitioning and formatting</title>
+
+  <para>
+   The NixOS installer doesn’t do any partitioning or formatting, so you need
+   to do that yourself.
+  </para>
+
+  <para>
+   The NixOS installer ships with multiple partitioning tools. The examples
+   below use <command>parted</command>, but also provides
+   <command>fdisk</command>, <command>gdisk</command>,
+   <command>cfdisk</command>, and <command>cgdisk</command>.
+  </para>
+
+  <para>
+   The recommended partition scheme differs depending if the computer uses
+   <emphasis>Legacy Boot</emphasis> or <emphasis>UEFI</emphasis>.
+  </para>
+
+  <section xml:id="sec-installation-partitioning-UEFI">
+   <title>UEFI (GPT)</title>
+
    <para>
-    The NixOS installer doesn’t do any partitioning or formatting yet, so you
-    need to do that yourself. Use the following commands:
-    <itemizedlist>
+    Here's an example partition scheme for UEFI, using
+    <filename>/dev/sda</filename> as the device.
+    <note>
+     <para>
+      You can safely ignore <command>parted</command>'s informational message
+      about needing to update /etc/fstab.
+     </para>
+    </note>
+   </para>
+
+   <para>
+    <orderedlist>
      <listitem>
       <para>
-       For partitioning: <command>fdisk</command>.
-<screen>
-# fdisk /dev/sda # <lineannotation>(or whatever device you want to install on)</lineannotation>
--- for UEFI systems only
-> n      # <lineannotation>(create a new partition for /boot)</lineannotation>
-> 3      # <lineannotation>(make it a partition number 3)</lineannotation>
->        # <lineannotation>(press enter to accept the default)</lineannotation>
-> +512M  # <lineannotation>(the size of the UEFI boot partition)</lineannotation>
-> t      # <lineannotation>(change the partition type ...)</lineannotation>
-> 3      # <lineannotation>(... of the boot partition ...)</lineannotation>
-> 1      # <lineannotation>(... to 'UEFI System')</lineannotation>
--- for BIOS or UEFI systems
-> n      # <lineannotation>(create a new partition for /swap)</lineannotation>
-> 2      # <lineannotation>(make it a partition number 2)</lineannotation>
->        # <lineannotation>(press enter to accept the default)</lineannotation>
-> +8G    # <lineannotation>(the size of the swap partition, set to whatever you like)</lineannotation>
-> n      # <lineannotation>(create a new partition for /)</lineannotation>
-> 1      # <lineannotation>(make it a partition number 1)</lineannotation>
->        # <lineannotation>(press enter to accept the default)</lineannotation>
->        # <lineannotation>(press enter to accept the default and use the rest of the remaining space)</lineannotation>
-> a      # <lineannotation>(make the partition bootable)</lineannotation>
-> x      # <lineannotation>(enter expert mode)</lineannotation>
-> f      # <lineannotation>(fix up the partition ordering)</lineannotation>
-> r      # <lineannotation>(exit expert mode)</lineannotation>
-> w      # <lineannotation>(write the partition table to disk and exit)</lineannotation></screen>
+       Create a <emphasis>GPT</emphasis> partition table.
+<screen language="commands"># parted /dev/sda -- mklabel gpt</screen>
       </para>
      </listitem>
      <listitem>
       <para>
+       Add the <emphasis>root</emphasis> partition. This will fill the disk
+       except for the end part, where the swap will live, and the space left in
+       front (512MiB) which will be used by the boot partition.
+<screen language="commands"># parted /dev/sda -- mkpart primary 512MiB -8GiB</screen>
+      </para>
+     </listitem>
+     <listitem>
+      <para>
+       Next, add a <emphasis>swap</emphasis> partition. The size required will
+       vary according to needs, here a 8GiB one is created.
+<screen language="commands"># parted /dev/sda -- mkpart primary linux-swap -8GiB 100%</screen>
+       <note>
+        <para>
+         The swap partition size rules are no different than for other Linux
+         distributions.
+        </para>
+       </note>
+      </para>
+     </listitem>
+     <listitem>
+      <para>
+       Finally, the <emphasis>boot</emphasis> partition. NixOS by default uses
+       the ESP (EFI system partition) as its <emphasis>/boot</emphasis>
+       partition. It uses the initially reserved 512MiB at the start of the
+       disk.
+<screen language="commands"># parted /dev/sda -- mkpart ESP fat32 1MiB 512MiB
+# parted /dev/sda -- set 3 boot on</screen>
+      </para>
+     </listitem>
+    </orderedlist>
+   </para>
+
+   <para>
+    Once complete, you can follow with
+    <xref linkend="sec-installation-partitioning-formatting"/>.
+   </para>
+  </section>
+
+  <section xml:id="sec-installation-partitioning-MBR">
+   <title>Legacy Boot (MBR)</title>
+
+   <para>
+    Here's an example partition scheme for Legacy Boot, using
+    <filename>/dev/sda</filename> as the device.
+    <note>
+     <para>
+      You can safely ignore <command>parted</command>'s informational message
+      about needing to update /etc/fstab.
+     </para>
+    </note>
+   </para>
+
+   <para>
+    <orderedlist>
+     <listitem>
+      <para>
+       Create a <emphasis>MBR</emphasis> partition table.
+<screen language="commands"># parted /dev/sda -- mklabel msdos</screen>
+      </para>
+     </listitem>
+     <listitem>
+      <para>
+       Add the <emphasis>root</emphasis> partition. This will fill the the disk
+       except for the end part, where the swap will live.
+<screen language="commands"># parted /dev/sda -- mkpart primary 1MiB -8GiB</screen>
+      </para>
+     </listitem>
+     <listitem>
+      <para>
+       Finally, add a <emphasis>swap</emphasis> partition. The size required
+       will vary according to needs, here a 8GiB one is created.
+<screen language="commands"># parted /dev/sda -- mkpart primary linux-swap -8GiB 100%</screen>
+       <note>
+        <para>
+         The swap partition size rules are no different than for other Linux
+         distributions.
+        </para>
+       </note>
+      </para>
+     </listitem>
+    </orderedlist>
+   </para>
+
+   <para>
+    Once complete, you can follow with
+    <xref linkend="sec-installation-partitioning-formatting"/>.
+   </para>
+  </section>
+
+  <section xml:id="sec-installation-partitioning-formatting">
+   <title>Formatting</title>
+
+   <para>
+    Use the following commands:
+    <itemizedlist>
+     <listitem>
+      <para>
        For initialising Ext4 partitions: <command>mkfs.ext4</command>. It is
        recommended that you assign a unique symbolic label to the file system
        using the option <option>-L <replaceable>label</replaceable></option>,
@@ -169,242 +262,249 @@
      </listitem>
     </itemizedlist>
    </para>
-  </listitem>
-  <listitem>
-   <para>
-    Mount the target file system on which NixOS should be installed on
-    <filename>/mnt</filename>, e.g.
+  </section>
+ </section>
+ <section xml:id="sec-installation-installing">
+  <title>Installing</title>
+
+  <orderedlist>
+   <listitem>
+    <para>
+     Mount the target file system on which NixOS should be installed on
+     <filename>/mnt</filename>, e.g.
 <screen>
 # mount /dev/disk/by-label/nixos /mnt
 </screen>
-   </para>
-  </listitem>
-  <listitem>
-   <variablelist>
-    <varlistentry>
-     <term>
-      UEFI systems
-     </term>
-     <listitem>
-      <para>
-       Mount the boot file system on <filename>/mnt/boot</filename>, e.g.
+    </para>
+   </listitem>
+   <listitem>
+    <variablelist>
+     <varlistentry>
+      <term>
+       UEFI systems
+      </term>
+      <listitem>
+       <para>
+        Mount the boot file system on <filename>/mnt/boot</filename>, e.g.
 <screen>
 # mkdir -p /mnt/boot
 # mount /dev/disk/by-label/boot /mnt/boot
 </screen>
-      </para>
-     </listitem>
-    </varlistentry>
-   </variablelist>
-  </listitem>
-  <listitem>
-   <para>
-    If your machine has a limited amount of memory, you may want to activate
-    swap devices now (<command>swapon
-    <replaceable>device</replaceable></command>). The installer (or rather, the
-    build actions that it may spawn) may need quite a bit of RAM, depending on
-    your configuration.
+       </para>
+      </listitem>
+     </varlistentry>
+    </variablelist>
+   </listitem>
+   <listitem>
+    <para>
+     If your machine has a limited amount of memory, you may want to activate
+     swap devices now (<command>swapon
+     <replaceable>device</replaceable></command>). The installer (or rather,
+     the build actions that it may spawn) may need quite a bit of RAM,
+     depending on your configuration.
 <screen>
 # swapon /dev/sda2</screen>
-   </para>
-  </listitem>
-  <listitem>
-   <para>
-    You now need to create a file
-    <filename>/mnt/etc/nixos/configuration.nix</filename> that specifies the
-    intended configuration of the system. This is because NixOS has a
-    <emphasis>declarative</emphasis> configuration model: you create or edit a
-    description of the desired configuration of your system, and then NixOS
-    takes care of making it happen. The syntax of the NixOS configuration file
-    is described in <xref linkend="sec-configuration-syntax"/>, while a list of
-    available configuration options appears in
-    <xref
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     You now need to create a file
+     <filename>/mnt/etc/nixos/configuration.nix</filename> that specifies the
+     intended configuration of the system. This is because NixOS has a
+     <emphasis>declarative</emphasis> configuration model: you create or edit a
+     description of the desired configuration of your system, and then NixOS
+     takes care of making it happen. The syntax of the NixOS configuration file
+     is described in <xref linkend="sec-configuration-syntax"/>, while a list
+     of available configuration options appears in
+     <xref
     linkend="ch-options"/>. A minimal example is shown in
-    <xref
+     <xref
     linkend="ex-config"/>.
-   </para>
-   <para>
-    The command <command>nixos-generate-config</command> can generate an
-    initial configuration file for you:
+    </para>
+    <para>
+     The command <command>nixos-generate-config</command> can generate an
+     initial configuration file for you:
 <screen>
 # nixos-generate-config --root /mnt</screen>
-    You should then edit <filename>/mnt/etc/nixos/configuration.nix</filename>
-    to suit your needs:
+     You should then edit <filename>/mnt/etc/nixos/configuration.nix</filename>
+     to suit your needs:
 <screen>
 # nano /mnt/etc/nixos/configuration.nix
 </screen>
-    If you’re using the graphical ISO image, other editors may be available
-    (such as <command>vim</command>). If you have network access, you can also
-    install other editors — for instance, you can install Emacs by running
-    <literal>nix-env -i emacs</literal>.
-   </para>
-   <variablelist>
-    <varlistentry>
-     <term>
-      BIOS systems
-     </term>
-     <listitem>
-      <para>
-       You <emphasis>must</emphasis> set the option
-       <xref linkend="opt-boot.loader.grub.device"/> to specify on which disk
-       the GRUB boot loader is to be installed. Without it, NixOS cannot boot.
-      </para>
-     </listitem>
-    </varlistentry>
-    <varlistentry>
-     <term>
-      UEFI systems
-     </term>
-     <listitem>
-      <para>
-       You <emphasis>must</emphasis> set the option
-       <xref linkend="opt-boot.loader.systemd-boot.enable"/> to
-       <literal>true</literal>. <command>nixos-generate-config</command> should
-       do this automatically for new configurations when booted in UEFI mode.
-      </para>
-      <para>
-       You may want to look at the options starting with
-       <option><link linkend="opt-boot.loader.efi.canTouchEfiVariables">boot.loader.efi</link></option>
-       and
-       <option><link linkend="opt-boot.loader.systemd-boot.enable">boot.loader.systemd</link></option>
-       as well.
-      </para>
-     </listitem>
-    </varlistentry>
-   </variablelist>
-   <para>
-    If there are other operating systems running on the machine before
-    installing NixOS, the <xref linkend="opt-boot.loader.grub.useOSProber"/>
-    option can be set to <literal>true</literal> to automatically add them to
-    the grub menu.
-   </para>
-   <para>
-    Another critical option is <option>fileSystems</option>, specifying the
-    file systems that need to be mounted by NixOS. However, you typically
-    don’t need to set it yourself, because
-    <command>nixos-generate-config</command> sets it automatically in
-    <filename>/mnt/etc/nixos/hardware-configuration.nix</filename> from your
-    currently mounted file systems. (The configuration file
-    <filename>hardware-configuration.nix</filename> is included from
-    <filename>configuration.nix</filename> and will be overwritten by future
-    invocations of <command>nixos-generate-config</command>; thus, you
-    generally should not modify it.)
-   </para>
-   <note>
+     If you’re using the graphical ISO image, other editors may be available
+     (such as <command>vim</command>). If you have network access, you can also
+     install other editors — for instance, you can install Emacs by running
+     <literal>nix-env -i emacs</literal>.
+    </para>
+    <variablelist>
+     <varlistentry>
+      <term>
+       BIOS systems
+      </term>
+      <listitem>
+       <para>
+        You <emphasis>must</emphasis> set the option
+        <xref linkend="opt-boot.loader.grub.device"/> to specify on which disk
+        the GRUB boot loader is to be installed. Without it, NixOS cannot boot.
+       </para>
+      </listitem>
+     </varlistentry>
+     <varlistentry>
+      <term>
+       UEFI systems
+      </term>
+      <listitem>
+       <para>
+        You <emphasis>must</emphasis> set the option
+        <xref linkend="opt-boot.loader.systemd-boot.enable"/> to
+        <literal>true</literal>. <command>nixos-generate-config</command>
+        should do this automatically for new configurations when booted in UEFI
+        mode.
+       </para>
+       <para>
+        You may want to look at the options starting with
+        <option><link linkend="opt-boot.loader.efi.canTouchEfiVariables">boot.loader.efi</link></option>
+        and
+        <option><link linkend="opt-boot.loader.systemd-boot.enable">boot.loader.systemd</link></option>
+        as well.
+       </para>
+      </listitem>
+     </varlistentry>
+    </variablelist>
     <para>
-     Depending on your hardware configuration or type of file system, you may
-     need to set the option <option>boot.initrd.kernelModules</option> to
-     include the kernel modules that are necessary for mounting the root file
-     system, otherwise the installed system will not be able to boot. (If this
-     happens, boot from the CD again, mount the target file system on
-     <filename>/mnt</filename>, fix
-     <filename>/mnt/etc/nixos/configuration.nix</filename> and rerun
-     <filename>nixos-install</filename>.) In most cases,
-     <command>nixos-generate-config</command> will figure out the required
-     modules.
+     If there are other operating systems running on the machine before
+     installing NixOS, the <xref linkend="opt-boot.loader.grub.useOSProber"/>
+     option can be set to <literal>true</literal> to automatically add them to
+     the grub menu.
     </para>
-   </note>
-  </listitem>
-  <listitem>
-   <para>
-    Do the installation:
+    <para>
+     Another critical option is <option>fileSystems</option>, specifying the
+     file systems that need to be mounted by NixOS. However, you typically
+     don’t need to set it yourself, because
+     <command>nixos-generate-config</command> sets it automatically in
+     <filename>/mnt/etc/nixos/hardware-configuration.nix</filename> from your
+     currently mounted file systems. (The configuration file
+     <filename>hardware-configuration.nix</filename> is included from
+     <filename>configuration.nix</filename> and will be overwritten by future
+     invocations of <command>nixos-generate-config</command>; thus, you
+     generally should not modify it.)
+    </para>
+    <note>
+     <para>
+      Depending on your hardware configuration or type of file system, you may
+      need to set the option <option>boot.initrd.kernelModules</option> to
+      include the kernel modules that are necessary for mounting the root file
+      system, otherwise the installed system will not be able to boot. (If this
+      happens, boot from the installation media again, mount the target file
+      system on <filename>/mnt</filename>, fix
+      <filename>/mnt/etc/nixos/configuration.nix</filename> and rerun
+      <filename>nixos-install</filename>.) In most cases,
+      <command>nixos-generate-config</command> will figure out the required
+      modules.
+     </para>
+    </note>
+   </listitem>
+   <listitem>
+    <para>
+     Do the installation:
 <screen>
 # nixos-install</screen>
-    Cross fingers. If this fails due to a temporary problem (such as a network
-    issue while downloading binaries from the NixOS binary cache), you can just
-    re-run <command>nixos-install</command>. Otherwise, fix your
-    <filename>configuration.nix</filename> and then re-run
-    <command>nixos-install</command>.
-   </para>
-   <para>
-    As the last step, <command>nixos-install</command> will ask you to set the
-    password for the <literal>root</literal> user, e.g.
+     Cross fingers. If this fails due to a temporary problem (such as a network
+     issue while downloading binaries from the NixOS binary cache), you can
+     just re-run <command>nixos-install</command>. Otherwise, fix your
+     <filename>configuration.nix</filename> and then re-run
+     <command>nixos-install</command>.
+    </para>
+    <para>
+     As the last step, <command>nixos-install</command> will ask you to set the
+     password for the <literal>root</literal> user, e.g.
 <screen>
 setting root password...
 Enter new UNIX password: ***
-Retype new UNIX password: ***
-    </screen>
-    <note>
-     <para>
-      For unattended installations, it is possible to use
-      <command>nixos-install --no-root-passwd</command>
-      in order to disable the password prompt entirely.
-     </para>
-    </note>
-   </para>
-  </listitem>
-  <listitem>
-   <para>
-    If everything went well:
+Retype new UNIX password: ***</screen>
+     <note>
+      <para>
+       For unattended installations, it is possible to use
+       <command>nixos-install --no-root-passwd</command> in order to disable
+       the password prompt entirely.
+      </para>
+     </note>
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     If everything went well:
 <screen>
-        # reboot</screen>
-   </para>
-  </listitem>
-  <listitem>
-   <para>
-    You should now be able to boot into the installed NixOS. The GRUB boot menu
-    shows a list of <emphasis>available configurations</emphasis> (initially
-    just one). Every time you change the NixOS configuration (see
-    <link
+# reboot</screen>
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     You should now be able to boot into the installed NixOS. The GRUB boot
+     menu shows a list of <emphasis>available configurations</emphasis>
+     (initially just one). Every time you change the NixOS configuration (see
+     <link
         linkend="sec-changing-config">Changing Configuration</link>
-    ), a new item is added to the menu. This allows you to easily roll back to
-    a previous configuration if something goes wrong.
-   </para>
-   <para>
-    You should log in and change the <literal>root</literal> password with
-    <command>passwd</command>.
-   </para>
-   <para>
-    You’ll probably want to create some user accounts as well, which can be
-    done with <command>useradd</command>:
+     ), a new item is added to the menu. This allows you to easily roll back to
+     a previous configuration if something goes wrong.
+    </para>
+    <para>
+     You should log in and change the <literal>root</literal> password with
+     <command>passwd</command>.
+    </para>
+    <para>
+     You’ll probably want to create some user accounts as well, which can be
+     done with <command>useradd</command>:
 <screen>
 $ useradd -c 'Eelco Dolstra' -m eelco
 $ passwd eelco</screen>
-   </para>
-   <para>
-    You may also want to install some software. For instance,
+    </para>
+    <para>
+     You may also want to install some software. For instance,
 <screen>
 $ nix-env -qa \*</screen>
-    shows what packages are available, and
+     shows what packages are available, and
 <screen>
 $ nix-env -i w3m</screen>
-    install the <literal>w3m</literal> browser.
-   </para>
-  </listitem>
- </orderedlist>
- <para>
-  To summarise, <xref linkend="ex-install-sequence" /> shows a typical sequence
-  of commands for installing NixOS on an empty hard drive (here
-  <filename>/dev/sda</filename>). <xref linkend="ex-config"
+     install the <literal>w3m</literal> browser.
+    </para>
+   </listitem>
+  </orderedlist>
+ </section>
+ <section xml:id="sec-installation-summary">
+  <title>Installation summary</title>
+
+  <para>
+   To summarise, <xref linkend="ex-install-sequence" /> shows a typical
+   sequence of commands for installing NixOS on an empty hard drive (here
+   <filename>/dev/sda</filename>). <xref linkend="ex-config"
 /> shows a
-  corresponding configuration Nix expression.
- </para>
- <example xml:id='ex-install-sequence'>
-  <title>Commands for Installing NixOS on <filename>/dev/sda</filename></title>
-<screen>
-# fdisk /dev/sda # <lineannotation>(or whatever device you want to install on)</lineannotation>
--- for UEFI systems only
-> n      # <lineannotation>(create a new partition for /boot)</lineannotation>
-> 3      # <lineannotation>(make it a partition number 3)</lineannotation>
->        # <lineannotation>(press enter to accept the default)</lineannotation>
-> +512M  # <lineannotation>(the size of the UEFI boot partition)</lineannotation>
-> t      # <lineannotation>(change the partition type ...)</lineannotation>
-> 3      # <lineannotation>(... of the boot partition ...)</lineannotation>
-> 1      # <lineannotation>(... to 'UEFI System')</lineannotation>
--- for BIOS or UEFI systems
-> n      # <lineannotation>(create a new partition for /swap)</lineannotation>
-> 2      # <lineannotation>(make it a partition number 2)</lineannotation>
->        # <lineannotation>(press enter to accept the default)</lineannotation>
-> +8G    # <lineannotation>(the size of the swap partition)</lineannotation>
-> n      # <lineannotation>(create a new partition for /)</lineannotation>
-> 1      # <lineannotation>(make it a partition number 1)</lineannotation>
->        # <lineannotation>(press enter to accept the default)</lineannotation>
->        # <lineannotation>(press enter to accept the default and use the rest of the remaining space)</lineannotation>
-> a      # <lineannotation>(make the partition bootable)</lineannotation>
-> x      # <lineannotation>(enter expert mode)</lineannotation>
-> f      # <lineannotation>(fix up the partition ordering)</lineannotation>
-> r      # <lineannotation>(exit expert mode)</lineannotation>
-> w      # <lineannotation>(write the partition table to disk and exit)</lineannotation>
+   corresponding configuration Nix expression.
+  </para>
+
+  <example xml:id="ex-partition-scheme-MBR">
+   <title>Example partition schemes for NixOS on <filename>/dev/sda</filename> (MBR)</title>
+<screen language="commands">
+# parted /dev/sda -- mklabel msdos
+# parted /dev/sda -- mkpart primary 1MiB -8GiB
+# parted /dev/sda -- mkpart primary linux-swap -8GiB 100%</screen>
+  </example>
+
+  <example xml:id="ex-partition-scheme-UEFI">
+   <title>Example partition schemes for NixOS on <filename>/dev/sda</filename> (UEFI)</title>
+<screen language="commands">
+# parted /dev/sda -- mklabel gpt
+# parted /dev/sda -- mkpart primary 512MiB -8GiB
+# parted /dev/sda -- mkpart primary linux-swap -8GiB 100%
+# parted /dev/sda -- mkpart ESP fat32 1MiB 512MiB
+# parted /dev/sda -- set 3 boot on</screen>
+  </example>
+
+  <example xml:id="ex-install-sequence">
+   <title>Commands for Installing NixOS on <filename>/dev/sda</filename></title>
+   <para>
+    With a partitioned disk.
+<screen language="commands">
 # mkfs.ext4 -L nixos /dev/sda1
 # mkswap -L swap /dev/sda2
 # swapon /dev/sda2
@@ -416,9 +516,11 @@ $ nix-env -i w3m</screen>
 # nano /mnt/etc/nixos/configuration.nix
 # nixos-install
 # reboot</screen>
- </example>
- <example xml:id='ex-config'>
-  <title>NixOS Configuration</title>
+   </para>
+  </example>
+
+  <example xml:id='ex-config'>
+   <title>NixOS Configuration</title>
 <screen>
 { config, pkgs, ... }: {
   imports = [
@@ -438,10 +540,19 @@ $ nix-env -i w3m</screen>
   services.sshd.enable = true;
 }
   </screen>
- </example>
- <xi:include href="installing-usb.xml" />
- <xi:include href="installing-pxe.xml" />
- <xi:include href="installing-virtualbox-guest.xml" />
- <xi:include href="installing-from-other-distro.xml" />
- <xi:include href="installing-behind-a-proxy.xml" />
+  </example>
+ </section>
+ <section xml:id="sec-installation-additional-notes">
+  <title>Additional installation notes</title>
+
+  <xi:include href="installing-usb.xml" />
+
+  <xi:include href="installing-pxe.xml" />
+
+  <xi:include href="installing-virtualbox-guest.xml" />
+
+  <xi:include href="installing-from-other-distro.xml" />
+
+  <xi:include href="installing-behind-a-proxy.xml" />
+ </section>
 </chapter>
diff --git a/nixos/doc/manual/installation/upgrading.xml b/nixos/doc/manual/installation/upgrading.xml
index 85e5082575d3..69668b1d4bd6 100644
--- a/nixos/doc/manual/installation/upgrading.xml
+++ b/nixos/doc/manual/installation/upgrading.xml
@@ -52,10 +52,13 @@
    </listitem>
   </itemizedlist>
   To see what channels are available, go to
-  <link
-xlink:href="https://nixos.org/channels"/>. (Note that the URIs of the
+  <link xlink:href="https://nixos.org/channels"/>. (Note that the URIs of the
   various channels redirect to a directory that contains the channel’s latest
-  version and includes ISO images and VirtualBox appliances.)
+  version and includes ISO images and VirtualBox appliances.) Please note that
+  during the release process, channels that are not yet released will be
+  present here as well. See the Getting NixOS page
+  <link xlink:href="https://nixos.org/nixos/download.html"/> to find the newest
+  supported stable release.
  </para>
  <para>
   When you first install NixOS, you’re automatically subscribed to the NixOS
diff --git a/nixos/doc/manual/manual.xml b/nixos/doc/manual/manual.xml
index a5efde32885c..12f52e1997c8 100644
--- a/nixos/doc/manual/manual.xml
+++ b/nixos/doc/manual/manual.xml
@@ -17,8 +17,8 @@
   <para>
    If you encounter problems, please report them on the
    <literal
-    xlink:href="https://discourse.nixos.org">Discourse</literal>
-   or on the <link
+    xlink:href="https://discourse.nixos.org">Discourse</literal> or
+   on the <link
     xlink:href="irc://irc.freenode.net/#nixos">
    <literal>#nixos</literal> channel on Freenode</link>. Bugs should be
    reported in
diff --git a/nixos/doc/manual/options-to-docbook.xsl b/nixos/doc/manual/options-to-docbook.xsl
index 2038b0dff63e..72ac89d4ff62 100644
--- a/nixos/doc/manual/options-to-docbook.xsl
+++ b/nixos/doc/manual/options-to-docbook.xsl
@@ -4,6 +4,7 @@
                 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                 xmlns:str="http://exslt.org/strings"
                 xmlns:xlink="http://www.w3.org/1999/xlink"
+                xmlns:nixos="tag:nixos.org"
                 xmlns="http://docbook.org/ns/docbook"
                 extension-element-prefixes="str"
                 >
@@ -30,10 +31,12 @@
 
             <listitem>
 
-              <para>
-                <xsl:value-of disable-output-escaping="yes"
-                              select="attr[@name = 'description']/string/@value" />
-              </para>
+              <nixos:option-description>
+                <para>
+                  <xsl:value-of disable-output-escaping="yes"
+                                select="attr[@name = 'description']/string/@value" />
+                </para>
+              </nixos:option-description>
 
               <xsl:if test="attr[@name = 'type']">
                 <para>
diff --git a/nixos/doc/manual/postprocess-option-descriptions.xsl b/nixos/doc/manual/postprocess-option-descriptions.xsl
new file mode 100644
index 000000000000..1201c7612c2e
--- /dev/null
+++ b/nixos/doc/manual/postprocess-option-descriptions.xsl
@@ -0,0 +1,115 @@
+<?xml version="1.0"?>
+
+<xsl:stylesheet version="1.0"
+                xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+                xmlns:str="http://exslt.org/strings"
+                xmlns:exsl="http://exslt.org/common"
+                xmlns:db="http://docbook.org/ns/docbook"
+                xmlns:nixos="tag:nixos.org"
+                extension-element-prefixes="str exsl">
+  <xsl:output method='xml' encoding="UTF-8" />
+
+  <xsl:template match="@*|node()">
+    <xsl:copy>
+      <xsl:apply-templates select="@*|node()" />
+    </xsl:copy>
+  </xsl:template>
+
+  <xsl:template name="break-up-description">
+    <xsl:param name="input" />
+    <xsl:param name="buffer" />
+
+    <!-- Every time we have two newlines following each other, we want to
+         break it into </para><para>. -->
+    <xsl:variable name="parbreak" select="'&#xa;&#xa;'" />
+
+    <!-- Similar to "(head:tail) = input" in Haskell. -->
+    <xsl:variable name="head" select="$input[1]" />
+    <xsl:variable name="tail" select="$input[position() &gt; 1]" />
+
+    <xsl:choose>
+      <xsl:when test="$head/self::text() and contains($head, $parbreak)">
+        <!-- If the haystack provided to str:split() directly starts or
+             ends with $parbreak, it doesn't generate a <token/> for that,
+             so we are doing this here. -->
+        <xsl:variable name="splitted-raw">
+          <xsl:if test="starts-with($head, $parbreak)"><token /></xsl:if>
+          <xsl:for-each select="str:split($head, $parbreak)">
+            <token><xsl:value-of select="node()" /></token>
+          </xsl:for-each>
+          <!-- Something like ends-with($head, $parbreak), but there is
+               no ends-with() in XSLT, so we need to use substring(). -->
+          <xsl:if test="
+            substring($head, string-length($head) -
+                             string-length($parbreak) + 1) = $parbreak
+          "><token /></xsl:if>
+        </xsl:variable>
+        <xsl:variable name="splitted"
+                      select="exsl:node-set($splitted-raw)/token" />
+        <!-- The buffer we had so far didn't contain any text nodes that
+             contain a $parbreak, so we can put the buffer along with the
+             first token of $splitted into a para element. -->
+        <para xmlns="http://docbook.org/ns/docbook">
+          <xsl:apply-templates select="exsl:node-set($buffer)" />
+          <xsl:apply-templates select="$splitted[1]/node()" />
+        </para>
+        <!-- We have already emitted the first splitted result, so the
+             last result is going to be set as the new $buffer later
+             because its contents may not be directly followed up by a
+             $parbreak. -->
+        <xsl:for-each select="$splitted[position() &gt; 1
+                              and position() &lt; last()]">
+          <para xmlns="http://docbook.org/ns/docbook">
+            <xsl:apply-templates select="node()" />
+          </para>
+        </xsl:for-each>
+        <xsl:call-template name="break-up-description">
+          <xsl:with-param name="input" select="$tail" />
+          <xsl:with-param name="buffer" select="$splitted[last()]/node()" />
+        </xsl:call-template>
+      </xsl:when>
+      <!-- Either non-text node or one without $parbreak, which we just
+           want to buffer and continue recursing. -->
+      <xsl:when test="$input">
+        <xsl:call-template name="break-up-description">
+          <xsl:with-param name="input" select="$tail" />
+          <!-- This essentially appends $head to $buffer. -->
+          <xsl:with-param name="buffer">
+            <xsl:if test="$buffer">
+              <xsl:for-each select="exsl:node-set($buffer)">
+                <xsl:apply-templates select="." />
+              </xsl:for-each>
+            </xsl:if>
+            <xsl:apply-templates select="$head" />
+          </xsl:with-param>
+        </xsl:call-template>
+      </xsl:when>
+      <!-- No more $input, just put the remaining $buffer in a para. -->
+      <xsl:otherwise>
+        <para xmlns="http://docbook.org/ns/docbook">
+          <xsl:apply-templates select="exsl:node-set($buffer)" />
+        </para>
+      </xsl:otherwise>
+    </xsl:choose>
+  </xsl:template>
+
+  <xsl:template match="nixos:option-description">
+    <xsl:choose>
+      <!--
+        Only process nodes that are comprised of a single <para/> element,
+        because if that's not the case the description already contains
+        </para><para> in between and we need no further processing.
+      -->
+      <xsl:when test="count(db:para) > 1">
+        <xsl:apply-templates select="node()" />
+      </xsl:when>
+      <xsl:otherwise>
+        <xsl:call-template name="break-up-description">
+          <xsl:with-param name="input"
+                          select="exsl:node-set(db:para/node())" />
+        </xsl:call-template>
+      </xsl:otherwise>
+    </xsl:choose>
+  </xsl:template>
+
+</xsl:stylesheet>
diff --git a/nixos/doc/manual/release-notes/release-notes.xml b/nixos/doc/manual/release-notes/release-notes.xml
index 94f176186b6e..a222bfa29d5a 100644
--- a/nixos/doc/manual/release-notes/release-notes.xml
+++ b/nixos/doc/manual/release-notes/release-notes.xml
@@ -8,6 +8,7 @@
   This section lists the release notes for each stable version of NixOS and
   current unstable revision.
  </para>
+ <xi:include href="rl-1903.xml" />
  <xi:include href="rl-1809.xml" />
  <xi:include href="rl-1803.xml" />
  <xi:include href="rl-1709.xml" />
diff --git a/nixos/doc/manual/release-notes/rl-1509.xml b/nixos/doc/manual/release-notes/rl-1509.xml
index 4eb2f9aa0a95..e500c9d63422 100644
--- a/nixos/doc/manual/release-notes/rl-1509.xml
+++ b/nixos/doc/manual/release-notes/rl-1509.xml
@@ -435,11 +435,11 @@ system.autoUpgrade.enable = true;
 <programlisting>
 system.stateVersion = "14.12";
 </programlisting>
-     The new option <option>system.stateVersion</option> ensures that
-     certain configuration changes that could break existing systems (such as
-     the <command>sshd</command> host key setting) will maintain compatibility
-     with the specified NixOS release. NixOps sets the state version of
-     existing deployments automatically.
+     The new option <option>system.stateVersion</option> ensures that certain
+     configuration changes that could break existing systems (such as the
+     <command>sshd</command> host key setting) will maintain compatibility with
+     the specified NixOS release. NixOps sets the state version of existing
+     deployments automatically.
     </para>
    </listitem>
    <listitem>
diff --git a/nixos/doc/manual/release-notes/rl-1809.xml b/nixos/doc/manual/release-notes/rl-1809.xml
index 92a7223e0412..8715a05f508b 100644
--- a/nixos/doc/manual/release-notes/rl-1809.xml
+++ b/nixos/doc/manual/release-notes/rl-1809.xml
@@ -3,7 +3,7 @@
          xmlns:xi="http://www.w3.org/2001/XInclude"
          version="5.0"
          xml:id="sec-release-18.09">
- <title>Release 18.09 (“Jellyfish”, 2018/09/??)</title>
+ <title>Release 18.09 (“Jellyfish”, 2018/10/05)</title>
 
  <section xmlns="http://docbook.org/ns/docbook"
          xmlns:xlink="http://www.w3.org/1999/xlink"
@@ -14,18 +14,56 @@
 
   <para>
    In addition to numerous new and upgraded packages, this release has the
-   following highlights:
+   following notable updates:
   </para>
 
   <itemizedlist>
    <listitem>
-     <para>
-       Support for wrapping binaries using <literal>firejail</literal> has been
-       added through <varname>programs.firejail.wrappedBinaries</varname>.
-     </para>
-     <para>
-       For example
-     </para>
+    <para>
+     End of support is planned for end of April 2019, handing over to 19.03.
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     Platform support: x86_64-linux and x86_64-darwin as always. Support for
+     aarch64-linux is as with the previous releases, not equivalent to the
+     x86-64-linux release, but with efforts to reach parity.
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     Nix has been updated to 2.1; see its
+     <link xlink:href="https://nixos.org/nix/manual/#ssec-relnotes-2.1">release
+     notes</link>.
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     Core versions: linux: 4.14 LTS (unchanged), glibc: 2.26 → 2.27, gcc: 7
+     (unchanged), systemd: 237 → 239.
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     Desktop version changes: gnome: 3.26 → 3.28, (KDE) plasma-desktop: 5.12
+     → 5.13.
+    </para>
+   </listitem>
+  </itemizedlist>
+
+  <para>
+   Notable changes and additions for 18.09 include:
+  </para>
+
+  <itemizedlist>
+   <listitem>
+    <para>
+     Support for wrapping binaries using <literal>firejail</literal> has been
+     added through <varname>programs.firejail.wrappedBinaries</varname>.
+    </para>
+    <para>
+     For example
+    </para>
 <programlisting>
 programs.firejail = {
   enable = true;
@@ -35,9 +73,10 @@ programs.firejail = {
   };
 };
 </programlisting>
-      <para>
-        This will place <literal>firefox</literal> and <literal>mpv</literal> binaries in the global path wrapped by firejail.
-      </para>
+    <para>
+     This will place <literal>firefox</literal> and <literal>mpv</literal>
+     binaries in the global path wrapped by firejail.
+    </para>
    </listitem>
    <listitem>
     <para>
@@ -69,52 +108,355 @@ $ nix-instantiate -E '(import &lt;nixpkgsunstable&gt; {}).gitFull'
   <title>New Services</title>
 
   <para>
-   The following new services were added since the last release:
+   A curated selection of new services that were added since the last release:
   </para>
 
   <itemizedlist>
    <listitem>
     <para>
-     The <varname>services.cassandra</varname> module has been reworked and
-     was rewritten from scratch. The service has succeeding tests for
-     the versions 2.1, 2.2, 3.0 and 3.11 of <link
-     xlink:href="https://cassandra.apache.org/">Apache Cassandra</link>.
+     The <varname>services.cassandra</varname> module has been reworked and was
+     rewritten from scratch. The service has succeeding tests for the versions
+     2.1, 2.2, 3.0 and 3.11 of
+     <link
+     xlink:href="https://cassandra.apache.org/">Apache
+     Cassandra</link>.
     </para>
    </listitem>
    <listitem>
     <para>
-     There is a new <varname>services.foundationdb</varname> module for deploying
-     <link xlink:href="https://www.foundationdb.org">FoundationDB</link> clusters.
+     There is a new <varname>services.foundationdb</varname> module for
+     deploying
+     <link xlink:href="https://www.foundationdb.org">FoundationDB</link>
+     clusters.
     </para>
    </listitem>
    <listitem>
     <para>
      When enabled the <literal>iproute2</literal> will copy the files expected
      by ip route (e.g., <filename>rt_tables</filename>) in
-     <filename>/run/iproute2</filename>. This allows to write aliases for
+     <filename>/etc/iproute2</filename>. This allows to write aliases for
      routing tables for instance.
     </para>
    </listitem>
    <listitem>
     <para>
-      <varname>services.strongswan-swanctl</varname>
-      is a modern replacement for <varname>services.strongswan</varname>.
-      You can use either one of them to setup IPsec VPNs but not both at the same time.
+     <varname>services.strongswan-swanctl</varname> is a modern replacement for
+     <varname>services.strongswan</varname>. You can use either one of them to
+     setup IPsec VPNs but not both at the same time.
     </para>
     <para>
-      <varname>services.strongswan-swanctl</varname> uses the
-      <link xlink:href="https://wiki.strongswan.org/projects/strongswan/wiki/swanctl">swanctl</link>
-      command which uses the modern
-      <link xlink:href="https://github.com/strongswan/strongswan/blob/master/src/libcharon/plugins/vici/README.md">vici</link>
-      <emphasis>Versatile IKE Configuration Interface</emphasis>.
-      The deprecated <literal>ipsec</literal> command used in <varname>services.strongswan</varname> is using the legacy
-      <link xlink:href="https://github.com/strongswan/strongswan/blob/master/README_LEGACY.md">stroke configuration interface</link>.
+     <varname>services.strongswan-swanctl</varname> uses the
+     <link xlink:href="https://wiki.strongswan.org/projects/strongswan/wiki/swanctl">swanctl</link>
+     command which uses the modern
+     <link xlink:href="https://github.com/strongswan/strongswan/blob/master/src/libcharon/plugins/vici/README.md">vici</link>
+     <emphasis>Versatile IKE Configuration Interface</emphasis>. The deprecated
+     <literal>ipsec</literal> command used in
+     <varname>services.strongswan</varname> is using the legacy
+     <link xlink:href="https://github.com/strongswan/strongswan/blob/master/README_LEGACY.md">stroke
+     configuration interface</link>.
     </para>
    </listitem>
    <listitem>
     <para>
-      The new <varname>services.elasticsearch-curator</varname> service
-      periodically curates or manages, your Elasticsearch indices and snapshots.
+     The new <varname>services.elasticsearch-curator</varname> service
+     periodically curates or manages, your Elasticsearch indices and snapshots.
+    </para>
+   </listitem>
+  </itemizedlist>
+
+  <para>
+   Every new services:
+  </para>
+
+  <itemizedlist>
+   <listitem>
+    <para>
+     <literal>./config/xdg/autostart.nix</literal>
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     <literal>./config/xdg/icons.nix</literal>
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     <literal>./config/xdg/menus.nix</literal>
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     <literal>./config/xdg/mime.nix</literal>
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     <literal>./hardware/brightnessctl.nix</literal>
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     <literal>./hardware/onlykey.nix</literal>
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     <literal>./hardware/video/uvcvideo/default.nix</literal>
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     <literal>./misc/documentation.nix</literal>
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     <literal>./programs/firejail.nix</literal>
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     <literal>./programs/iftop.nix</literal>
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     <literal>./programs/sedutil.nix</literal>
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     <literal>./programs/singularity.nix</literal>
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     <literal>./programs/xss-lock.nix</literal>
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     <literal>./programs/zsh/zsh-autosuggestions.nix</literal>
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     <literal>./services/admin/oxidized.nix</literal>
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     <literal>./services/backup/duplicati.nix</literal>
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     <literal>./services/backup/restic.nix</literal>
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     <literal>./services/backup/restic-rest-server.nix</literal>
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     <literal>./services/cluster/hadoop/default.nix</literal>
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     <literal>./services/databases/aerospike.nix</literal>
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     <literal>./services/databases/monetdb.nix</literal>
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     <literal>./services/desktops/bamf.nix</literal>
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     <literal>./services/desktops/flatpak.nix</literal>
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     <literal>./services/desktops/zeitgeist.nix</literal>
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     <literal>./services/development/bloop.nix</literal>
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     <literal>./services/development/jupyter/default.nix</literal>
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     <literal>./services/hardware/lcd.nix</literal>
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     <literal>./services/hardware/undervolt.nix</literal>
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     <literal>./services/misc/clipmenu.nix</literal>
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     <literal>./services/misc/gitweb.nix</literal>
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     <literal>./services/misc/serviio.nix</literal>
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     <literal>./services/misc/safeeyes.nix</literal>
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     <literal>./services/misc/sysprof.nix</literal>
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     <literal>./services/misc/weechat.nix</literal>
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     <literal>./services/monitoring/datadog-agent.nix</literal>
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     <literal>./services/monitoring/incron.nix</literal>
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     <literal>./services/networking/dnsdist.nix</literal>
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     <literal>./services/networking/freeradius.nix</literal>
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     <literal>./services/networking/hans.nix</literal>
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     <literal>./services/networking/morty.nix</literal>
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     <literal>./services/networking/ndppd.nix</literal>
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     <literal>./services/networking/ocserv.nix</literal>
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     <literal>./services/networking/owamp.nix</literal>
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     <literal>./services/networking/quagga.nix</literal>
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     <literal>./services/networking/shadowsocks.nix</literal>
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     <literal>./services/networking/stubby.nix</literal>
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     <literal>./services/networking/zeronet.nix</literal>
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     <literal>./services/security/certmgr.nix</literal>
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     <literal>./services/security/cfssl.nix</literal>
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     <literal>./services/security/oauth2_proxy_nginx.nix</literal>
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     <literal>./services/web-apps/virtlyst.nix</literal>
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     <literal>./services/web-apps/youtrack.nix</literal>
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     <literal>./services/web-servers/hitch/default.nix</literal>
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     <literal>./services/web-servers/hydron.nix</literal>
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     <literal>./services/web-servers/meguca.nix</literal>
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     <literal>./services/web-servers/nginx/gitweb.nix</literal>
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     <literal>./virtualisation/kvmgt.nix</literal>
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     <literal>./virtualisation/qemu-guest-agent.nix</literal>
     </para>
    </listitem>
   </itemizedlist>
@@ -135,8 +477,50 @@ $ nix-instantiate -E '(import &lt;nixpkgsunstable&gt; {}).gitFull'
   <itemizedlist>
    <listitem>
     <para>
-     The deprecated <varname>services.cassandra</varname> module has
-     seen a complete rewrite. (See above.)
+     Some licenses that were incorrectly not marked as unfree now are. This is
+     the case for:
+     <itemizedlist>
+      <listitem>
+       <para>
+        cc-by-nc-sa-20: Creative Commons Attribution Non Commercial Share Alike
+        2.0
+       </para>
+      </listitem>
+      <listitem>
+       <para>
+        cc-by-nc-sa-25: Creative Commons Attribution Non Commercial Share Alike
+        2.5
+       </para>
+      </listitem>
+      <listitem>
+       <para>
+        cc-by-nc-sa-30: Creative Commons Attribution Non Commercial Share Alike
+        3.0
+       </para>
+      </listitem>
+      <listitem>
+       <para>
+        cc-by-nc-sa-40: Creative Commons Attribution Non Commercial Share Alike
+        4.0
+       </para>
+      </listitem>
+      <listitem>
+       <para>
+        cc-by-nd-30: Creative Commons Attribution-No Derivative Works v3.00
+       </para>
+      </listitem>
+      <listitem>
+       <para>
+        msrla: Microsoft Research License Agreement
+       </para>
+      </listitem>
+     </itemizedlist>
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     The deprecated <varname>services.cassandra</varname> module has seen a
+     complete rewrite. (See above.)
     </para>
    </listitem>
    <listitem>
@@ -186,41 +570,44 @@ $ nix-instantiate -E '(import &lt;nixpkgsunstable&gt; {}).gitFull'
    </listitem>
    <listitem>
     <para>
-     <varname>services.munge</varname> now runs as user (and group) <literal>munge</literal> instead of root.
-     Make sure the key file is accessible to the daemon.
+     <varname>services.munge</varname> now runs as user (and group)
+     <literal>munge</literal> instead of root. Make sure the key file is
+     accessible to the daemon.
     </para>
    </listitem>
    <listitem>
     <para>
-      <varname>dockerTools.buildImage</varname> now uses <literal>null</literal> as default value for <varname>tag</varname>,
-      which indicates that the nix output hash will be used as tag.
+     <varname>dockerTools.buildImage</varname> now uses <literal>null</literal>
+     as default value for <varname>tag</varname>, which indicates that the nix
+     output hash will be used as tag.
     </para>
    </listitem>
    <listitem>
     <para>
-     The ELK stack: <varname>elasticsearch</varname>, <varname>logstash</varname> and <varname>kibana</varname>
-     has been upgraded from 2.* to 6.3.*.
-     The 2.* versions have been <link xlink:href="https://www.elastic.co/support/eol">unsupported since last year</link>
-     so they have been removed. You can still use the 5.* versions under the names
-     <varname>elasticsearch5</varname>, <varname>logstash5</varname> and
-     <varname>kibana5</varname>.
+     The ELK stack: <varname>elasticsearch</varname>,
+     <varname>logstash</varname> and <varname>kibana</varname> has been
+     upgraded from 2.* to 6.3.*. The 2.* versions have been
+     <link xlink:href="https://www.elastic.co/support/eol">unsupported since
+     last year</link> so they have been removed. You can still use the 5.*
+     versions under the names <varname>elasticsearch5</varname>,
+     <varname>logstash5</varname> and <varname>kibana5</varname>.
     </para>
     <para>
-     The elastic beats:
-     <varname>filebeat</varname>, <varname>heartbeat</varname>,
-     <varname>metricbeat</varname> and <varname>packetbeat</varname>
-     have had the same treatment: they now target 6.3.* as well.
-     The 5.* versions are available under the names:
+     The elastic beats: <varname>filebeat</varname>,
+     <varname>heartbeat</varname>, <varname>metricbeat</varname> and
+     <varname>packetbeat</varname> have had the same treatment: they now target
+     6.3.* as well. The 5.* versions are available under the names:
      <varname>filebeat5</varname>, <varname>heartbeat5</varname>,
      <varname>metricbeat5</varname> and <varname>packetbeat5</varname>
     </para>
     <para>
      The ELK-6.3 stack now comes with
-     <link xlink:href="https://www.elastic.co/products/x-pack/open">X-Pack by default</link>.
-     Since X-Pack is licensed under the
-     <link xlink:href="https://github.com/elastic/elasticsearch/blob/master/licenses/ELASTIC-LICENSE.txt">Elastic License</link>
-     the ELK packages now have an unfree license. To use them you need to specify
-     <literal>allowUnfree = true;</literal> in your nixpkgs configuration.
+     <link xlink:href="https://www.elastic.co/products/x-pack/open">X-Pack by
+     default</link>. Since X-Pack is licensed under the
+     <link xlink:href="https://github.com/elastic/elasticsearch/blob/master/licenses/ELASTIC-LICENSE.txt">Elastic
+     License</link> the ELK packages now have an unfree license. To use them
+     you need to specify <literal>allowUnfree = true;</literal> in your nixpkgs
+     configuration.
     </para>
     <para>
      Fortunately there is also a free variant of the ELK stack without X-Pack.
@@ -231,20 +618,28 @@ $ nix-instantiate -E '(import &lt;nixpkgsunstable&gt; {}).gitFull'
    </listitem>
    <listitem>
     <para>
-      Options
-      <literal>boot.initrd.luks.devices.<replaceable>name</replaceable>.yubikey.ramfsMountPoint</literal>
-      <literal>boot.initrd.luks.devices.<replaceable>name</replaceable>.yubikey.storage.mountPoint</literal>
-      were removed. <literal>luksroot.nix</literal> module never supported more than one YubiKey at
-      a time anyway, hence those options never had any effect. You should be able to remove them
-      from your config without any issues.
+     Options
+     <literal>boot.initrd.luks.devices.<replaceable>name</replaceable>.yubikey.ramfsMountPoint</literal>
+     <literal>boot.initrd.luks.devices.<replaceable>name</replaceable>.yubikey.storage.mountPoint</literal>
+     were removed. <literal>luksroot.nix</literal> module never supported more
+     than one YubiKey at a time anyway, hence those options never had any
+     effect. You should be able to remove them from your config without any
+     issues.
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     <literal>stdenv.system</literal> and <literal>system</literal> in nixpkgs
+     now refer to the host platform instead of the build platform. For native
+     builds this is not change, let alone a breaking one. For cross builds, it
+     is a breaking change, and <literal>stdenv.buildPlatform.system</literal>
+     can be used instead for the old behavior. They should be using that
+     anyways for clarity.
     </para>
    </listitem>
    <listitem>
     <para>
-      <literal>stdenv.system</literal> and <literal>system</literal> in nixpkgs now refer to the host platform instead of the build platform.
-      For native builds this is not change, let alone a breaking one.
-      For cross builds, it is a breaking change, and <literal>stdenv.buildPlatform.system</literal> can be used instead for the old behavior.
-      They should be using that anyways for clarity.
+        Groups <literal>kvm</literal> and <literal>render</literal> are introduced now, as systemd requires them.
     </para>
    </listitem>
   </itemizedlist>
@@ -298,26 +693,33 @@ $ nix-instantiate -E '(import &lt;nixpkgsunstable&gt; {}).gitFull'
    </listitem>
    <listitem>
     <para>
-      The <literal>pkgs</literal> argument to NixOS modules can now be set directly using <literal>nixpkgs.pkgs</literal>. Previously, only the <literal>system</literal>, <literal>config</literal> and <literal>overlays</literal> arguments could be used to influence <literal>pkgs</literal>.
+     The <literal>pkgs</literal> argument to NixOS modules can now be set
+     directly using <literal>nixpkgs.pkgs</literal>. Previously, only the
+     <literal>system</literal>, <literal>config</literal> and
+     <literal>overlays</literal> arguments could be used to influence
+     <literal>pkgs</literal>.
     </para>
    </listitem>
    <listitem>
     <para>
-      A NixOS system can now be constructed more easily based on a preexisting invocation of Nixpkgs. For example:
-      <programlisting>
+     A NixOS system can now be constructed more easily based on a preexisting
+     invocation of Nixpkgs. For example:
+<programlisting>
 inherit (pkgs.nixos {
   boot.loader.grub.enable = false;
   fileSystems."/".device = "/dev/xvda1";
 }) toplevel kernel initialRamdisk manual;
       </programlisting>
-
-      This benefits evaluation performance, lets you write Nixpkgs packages that depend on NixOS images and is consistent with a deployment architecture that would be centered around Nixpkgs overlays.
+     This benefits evaluation performance, lets you write Nixpkgs packages that
+     depend on NixOS images and is consistent with a deployment architecture
+     that would be centered around Nixpkgs overlays.
     </para>
    </listitem>
    <listitem>
     <para>
-      <literal>lib.traceValIfNot</literal> has been deprecated. Use
-      <literal>if/then/else</literal> and <literal>lib.traceValSeq</literal> instead.
+     <literal>lib.traceValIfNot</literal> has been deprecated. Use
+     <literal>if/then/else</literal> and <literal>lib.traceValSeq</literal>
+     instead.
     </para>
    </listitem>
    <listitem>
@@ -336,9 +738,9 @@ inherit (pkgs.nixos {
    </listitem>
    <listitem>
     <para>
-     <literal>lib.recursiveUpdateUntil</literal> was not acting according to its
-     specification. It has been fixed to act according to the docstring, and a
-     test has been added.
+     <literal>lib.recursiveUpdateUntil</literal> was not acting according to
+     its specification. It has been fixed to act according to the docstring,
+     and a test has been added.
     </para>
    </listitem>
    <listitem>
@@ -408,11 +810,11 @@ inherit (pkgs.nixos {
     </para>
    </listitem>
    <listitem>
-     <para>
-     The Kubernetes package has been bumped to major version 1.11.
-     Please consult the
-     <link xlink:href="https://github.com/kubernetes/kubernetes/blob/release-1.11/CHANGELOG-1.11.md">release notes</link>
-     for details on new features and api changes.
+    <para>
+     The Kubernetes package has been bumped to major version 1.11. Please
+     consult the
+     <link xlink:href="https://github.com/kubernetes/kubernetes/blob/release-1.11/CHANGELOG-1.11.md">release
+     notes</link> for details on new features and api changes.
     </para>
    </listitem>
    <listitem>
@@ -432,8 +834,8 @@ inherit (pkgs.nixos {
    </listitem>
    <listitem>
     <para>
-     The option <varname>services.kubernetes.apiserver.address</varname>
-     was renamed to <varname>services.kubernetes.apiserver.bindAddress</varname>.
+     The option <varname>services.kubernetes.apiserver.address</varname> was
+     renamed to <varname>services.kubernetes.apiserver.bindAddress</varname>.
      Note that the default value has changed from 127.0.0.1 to 0.0.0.0.
     </para>
    </listitem>
@@ -445,76 +847,86 @@ inherit (pkgs.nixos {
    </listitem>
    <listitem>
     <para>
-     The option <varname>services.kubernetes.addons.dashboard.enableRBAC</varname>
-     was renamed to <varname>services.kubernetes.addons.dashboard.rbac.enable</varname>.
+     The option
+     <varname>services.kubernetes.addons.dashboard.enableRBAC</varname> was
+     renamed to
+     <varname>services.kubernetes.addons.dashboard.rbac.enable</varname>.
     </para>
    </listitem>
    <listitem>
     <para>
      The Kubernetes Dashboard now has only minimal RBAC permissions by default.
-     If dashboard cluster-admin rights are desired,
-     set <varname>services.kubernetes.addons.dashboard.rbac.clusterAdmin</varname> to true.
-     On existing clusters, in order for the revocation of privileges to take effect,
-     the current ClusterRoleBinding for kubernetes-dashboard must be manually removed:
-     <literal>kubectl delete clusterrolebinding kubernetes-dashboard</literal>
+     If dashboard cluster-admin rights are desired, set
+     <varname>services.kubernetes.addons.dashboard.rbac.clusterAdmin</varname>
+     to true. On existing clusters, in order for the revocation of privileges
+     to take effect, the current ClusterRoleBinding for kubernetes-dashboard
+     must be manually removed: <literal>kubectl delete clusterrolebinding
+     kubernetes-dashboard</literal>
     </para>
    </listitem>
    <listitem>
     <para>
      The <varname>programs.screen</varname> module provides allows to configure
-     <literal>/etc/screenrc</literal>, however the module behaved fairly counterintuitive as
-     the config exists, but the package wasn't available. Since 18.09 <literal>pkgs.screen</literal>
-     will be added to <literal>environment.systemPackages</literal>.
+     <literal>/etc/screenrc</literal>, however the module behaved fairly
+     counterintuitive as the config exists, but the package wasn't available.
+     Since 18.09 <literal>pkgs.screen</literal> will be added to
+     <literal>environment.systemPackages</literal>.
     </para>
    </listitem>
    <listitem>
     <para>
-      The module <option>services.networking.hostapd</option> now uses WPA2 by default.
+     The module <option>services.networking.hostapd</option> now uses WPA2 by
+     default.
     </para>
    </listitem>
    <listitem>
     <para>
-      <varname>s6Dns</varname>, <varname>s6Networking</varname>,
-      <varname>s6LinuxUtils</varname> and <varname>s6PortableUtils</varname>
-      renamed to
-      <varname>s6-dns</varname>, <varname>s6-networking</varname>,
-      <varname>s6-linux-utils</varname> and <varname>s6-portable-utils</varname> respectively.
+     <varname>s6Dns</varname>, <varname>s6Networking</varname>,
+     <varname>s6LinuxUtils</varname> and <varname>s6PortableUtils</varname>
+     renamed to <varname>s6-dns</varname>, <varname>s6-networking</varname>,
+     <varname>s6-linux-utils</varname> and <varname>s6-portable-utils</varname>
+     respectively.
     </para>
-  </listitem>
-  <listitem>
+   </listitem>
+   <listitem>
     <para>
-      The module option <option>nix.useSandbox</option> is now defaulted to <literal>true</literal>.
+     The module option <option>nix.useSandbox</option> is now defaulted to
+     <literal>true</literal>.
     </para>
-  </listitem>
-  <listitem>
+   </listitem>
+   <listitem>
     <para>
-      The config activation script of <literal>nixos-rebuild</literal> now
-      <link xlink:href="https://www.freedesktop.org/software/systemd/man/systemctl.html#Manager%20Lifecycle%20Commands">reloads</link>
-      all user units for each authenticated user.
+     The config activation script of <literal>nixos-rebuild</literal> now
+     <link xlink:href="https://www.freedesktop.org/software/systemd/man/systemctl.html#Manager%20Lifecycle%20Commands">reloads</link>
+     all user units for each authenticated user.
     </para>
-  </listitem>
-  <listitem>
+   </listitem>
+   <listitem>
     <para>
-      The default display manager is now LightDM.
-      To use SLiM set <literal>services.xserver.displayManager.slim.enable</literal>
-      to <literal>true</literal>.
+     The default display manager is now LightDM. To use SLiM set
+     <literal>services.xserver.displayManager.slim.enable</literal> to
+     <literal>true</literal>.
     </para>
-  </listitem>
-  <listitem>
+   </listitem>
+   <listitem>
     <para>
-      NixOS option descriptions are now automatically broken up into individual
-      paragraphs if the text contains two consecutive newlines, so it's no
-      longer necessary to use <code>&lt;/para&gt;&lt;para&gt;</code> to start
-      a new paragraph.
+     NixOS option descriptions are now automatically broken up into individual
+     paragraphs if the text contains two consecutive newlines, so it's no
+     longer necessary to use <code>&lt;/para&gt;&lt;para&gt;</code> to start a
+     new paragraph.
     </para>
-  </listitem>
-  <listitem>
+   </listitem>
+   <listitem>
     <para>
-      Top-level <literal>buildPlatform</literal>, <literal>hostPlatform</literal>, and <literal>targetPlatform</literal> in Nixpkgs are deprecated.
-      Please use their equivalents in <literal>stdenv</literal> instead:
-      <literal>stdenv.buildPlatform</literal>, <literal>stdenv.hostPlatform</literal>, and <literal>stdenv.targetPlatform</literal>.
+     Top-level <literal>buildPlatform</literal>,
+     <literal>hostPlatform</literal>, and <literal>targetPlatform</literal> in
+     Nixpkgs are deprecated. Please use their equivalents in
+     <literal>stdenv</literal> instead:
+     <literal>stdenv.buildPlatform</literal>,
+     <literal>stdenv.hostPlatform</literal>, and
+     <literal>stdenv.targetPlatform</literal>.
     </para>
-  </listitem>
+   </listitem>
   </itemizedlist>
  </section>
 </section>
diff --git a/nixos/doc/manual/release-notes/rl-1903.xml b/nixos/doc/manual/release-notes/rl-1903.xml
new file mode 100644
index 000000000000..1d9b6ecc0e2e
--- /dev/null
+++ b/nixos/doc/manual/release-notes/rl-1903.xml
@@ -0,0 +1,204 @@
+<section xmlns="http://docbook.org/ns/docbook"
+         xmlns:xlink="http://www.w3.org/1999/xlink"
+         xmlns:xi="http://www.w3.org/2001/XInclude"
+         version="5.0"
+         xml:id="sec-release-19.03">
+ <title>Release 19.03 (“Koi”, 2019/03/??)</title>
+
+ <section xmlns="http://docbook.org/ns/docbook"
+         xmlns:xlink="http://www.w3.org/1999/xlink"
+         xmlns:xi="http://www.w3.org/2001/XInclude"
+         version="5.0"
+         xml:id="sec-release-19.03-highlights">
+  <title>Highlights</title>
+
+  <para>
+   In addition to numerous new and upgraded packages, this release has the
+   following highlights:
+  </para>
+
+  <itemizedlist>
+   <listitem>
+    <para />
+   </listitem>
+  </itemizedlist>
+ </section>
+
+ <section xmlns="http://docbook.org/ns/docbook"
+         xmlns:xlink="http://www.w3.org/1999/xlink"
+         xmlns:xi="http://www.w3.org/2001/XInclude"
+         version="5.0"
+         xml:id="sec-release-19.03-new-services">
+  <title>New Services</title>
+
+  <para>
+   The following new services were added since the last release:
+  </para>
+
+  <itemizedlist>
+   <listitem>
+    <para />
+   </listitem>
+  </itemizedlist>
+ </section>
+
+ <section xmlns="http://docbook.org/ns/docbook"
+         xmlns:xlink="http://www.w3.org/1999/xlink"
+         xmlns:xi="http://www.w3.org/2001/XInclude"
+         version="5.0"
+         xml:id="sec-release-19.03-incompatibilities">
+  <title>Backward Incompatibilities</title>
+
+  <para>
+   When upgrading from a previous release, please be aware of the following
+   incompatible changes:
+  </para>
+
+  <itemizedlist>
+   <listitem>
+    <para>
+     The minimum version of Nix required to evaluate Nixpkgs is now 2.0.
+    </para>
+    <itemizedlist>
+     <listitem>
+      <para>
+       For users of NixOS 18.03 and 19.03, NixOS defaults to Nix 2.0, but
+       supports using Nix 1.11 by setting <literal>nix.package =
+       pkgs.nix1;</literal>. If this option is set to a Nix 1.11 package, you
+       will need to either unset the option or upgrade it to Nix 2.0.
+      </para>
+     </listitem>
+     <listitem>
+      <para>
+       For users of NixOS 17.09, you will first need to upgrade Nix by setting
+       <literal>nix.package = pkgs.nixStable2;</literal> and run
+       <command>nixos-rebuild switch</command> as the <literal>root</literal>
+       user.
+      </para>
+     </listitem>
+     <listitem>
+      <para>
+       For users of a daemon-less Nix installation on Linux or macOS, you can
+       upgrade Nix by running <command>curl https://nixos.org/nix/install |
+       sh</command>, or prior to doing a channel update, running
+       <command>nix-env -iA nix</command>.
+      </para>
+      <para>
+       If you have already run a channel update and Nix is no longer able to
+       evaluate Nixpkgs, the error message printed should provide adequate
+       directions for upgrading Nix.
+      </para>
+     </listitem>
+     <listitem>
+      <para>
+       For users of the Nix daemon on macOS, you can upgrade Nix by running
+       <command>sudo -i sh -c 'nix-channel --update &amp;&amp; nix-env -iA
+       nixpkgs.nix'; sudo launchctl stop org.nixos.nix-daemon; sudo launchctl
+       start org.nixos.nix-daemon</command>.
+      </para>
+     </listitem>
+    </itemizedlist>
+   </listitem>
+   <listitem>
+    <para>
+     Package <varname>rabbitmq_server</varname> is renamed to
+     <varname>rabbitmq-server</varname>.
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     The <literal>light</literal> module no longer uses setuid binaries, but
+     udev rules. As a consequence users of that module have to belong to the
+     <literal>video</literal> group in order to use the executable (i.e.
+     <literal>users.users.yourusername.extraGroups = ["video"];</literal>).
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     Buildbot now supports Python 3 and its packages have been moved to
+     <literal>pythonPackages</literal>. The options
+     <option>services.buildbot-master.package</option> and
+     <option>services.buildbot-worker.package</option> can be used to select
+     the Python 2 or 3 version of the package.
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+      Options
+      <literal>services.znc.confOptions.networks.<replaceable>name</replaceable>.userName</literal> and
+      <literal>services.znc.confOptions.networks.<replaceable>name</replaceable>.modulePackages</literal>
+      were removed. They were never used for anything and can therefore safely be removed.
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     Package <literal>wasm</literal> has been renamed <literal>proglodyte-wasm</literal>. The package
+     <literal>wasm</literal> will be pointed to <literal>ocamlPackages.wasm</literal> in 19.09, so
+     make sure to update your configuration if you want to keep <literal>proglodyte-wasm</literal>
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     OpenSMTPD has been upgraded to version 6.4.0p1. This release makes
+     backwards-incompatible changes to the configuration file format. See
+     <command>man smtpd.conf</command> for more information on the new file
+     format.
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     The versioned <varname>postgresql</varname> have been renamed to use
+     underscore number seperators. For example, <varname>postgresql96</varname>
+     has been renamed to <varname>postgresql_9_6</varname>.
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+      Slurm introduces the new option
+      <literal>services.slurm.stateSaveLocation</literal>,
+      which is now set to <literal>/var/spool/slurm</literal> by default
+      (instead of <literal>/var/spool</literal>).
+      Make sure to move all files to the new directory or to set the option accordingly.
+    </para>
+    <para>
+      The slurmctld now runs as user <literal>slurm</literal> instead of <literal>root</literal>.
+      If you want to keep slurmctld running as <literal>root</literal>, set
+      <literal>services.slurm.user = root</literal>.
+    </para>
+    <para>
+      The options <literal>services.slurm.nodeName</literal> and
+      <literal>services.slurm.partitionName</literal> are now sets of
+      strings to correctly reflect that fact that each of these
+      options can occour more than once in the configuration.
+    </para>
+   </listitem>
+  </itemizedlist>
+ </section>
+
+ <section xmlns="http://docbook.org/ns/docbook"
+         xmlns:xlink="http://www.w3.org/1999/xlink"
+         xmlns:xi="http://www.w3.org/2001/XInclude"
+         version="5.0"
+         xml:id="sec-release-19.03-notable-changes">
+  <title>Other Notable Changes</title>
+
+  <itemizedlist>
+   <listitem>
+    <para>
+     The <option>services.matomo</option> module gained the option
+     <option>services.matomo.package</option> which determines the used
+     Matomo version.
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     The deprecated <literal>truecrypt</literal> package has been removed
+     and <literal>truecrypt</literal> attribute is now an alias for
+     <literal>veracrypt</literal>. VeraCrypt is backward-compatible with
+     TrueCrypt volumes. Note that <literal>cryptsetup</literal> also
+     supports loading TrueCrypt volumes.
+    </para>
+   </listitem>
+  </itemizedlist>
+ </section>
+</section>
diff --git a/nixos/lib/build-vms.nix b/nixos/lib/build-vms.nix
index 48288cf5962c..4f65501f89c6 100644
--- a/nixos/lib/build-vms.nix
+++ b/nixos/lib/build-vms.nix
@@ -28,7 +28,7 @@ rec {
       modules = configurations ++
         [ ../modules/virtualisation/qemu-vm.nix
           ../modules/testing/test-instrumentation.nix # !!! should only get added for automated test runs
-          { key = "no-manual"; services.nixosManual.enable = false; }
+          { key = "no-manual"; documentation.nixos.enable = false; }
           { key = "qemu"; system.build.qemu = qemu; }
         ] ++ optional minimal ../modules/testing/minimal-kernel.nix;
       extraArgs = { inherit nodes; };
diff --git a/nixos/lib/eval-config.nix b/nixos/lib/eval-config.nix
index ef685949ae1f..5f05b037bdde 100644
--- a/nixos/lib/eval-config.nix
+++ b/nixos/lib/eval-config.nix
@@ -28,7 +28,7 @@
 
 let extraArgs_ = extraArgs; pkgs_ = pkgs;
     extraModules = let e = builtins.getEnv "NIXOS_EXTRA_MODULE_PATH";
-                   in if e == "" then [] else [(import (builtins.toPath e))];
+                   in if e == "" then [] else [(import e)];
 in
 
 let
@@ -53,7 +53,8 @@ in rec {
     inherit prefix check;
     modules = modules ++ extraModules ++ baseModules ++ [ pkgsModule ];
     args = extraArgs;
-    specialArgs = { modulesPath = ../modules; } // specialArgs;
+    specialArgs =
+      { modulesPath = builtins.toString ../modules; } // specialArgs;
   }) config options;
 
   # These are the extra arguments passed to every module.  In
diff --git a/nixos/lib/test-driver/Machine.pm b/nixos/lib/test-driver/Machine.pm
index b18f48464cee..abcc1c50d4d8 100644
--- a/nixos/lib/test-driver/Machine.pm
+++ b/nixos/lib/test-driver/Machine.pm
@@ -155,8 +155,10 @@ sub start {
         $ENV{USE_TMPDIR} = 1;
         $ENV{QEMU_OPTS} =
             ($self->{allowReboot} ? "" : "-no-reboot ") .
-            "-monitor unix:./monitor -chardev socket,id=shell,path=./shell " .
-            "-device virtio-serial -device virtconsole,chardev=shell " .
+            "-monitor unix:./monitor " .
+            "-chardev socket,id=shell,path=./shell -device virtio-serial -device virtconsole,chardev=shell " .
+            # socket backdoor, see "Debugging NixOS tests" section in NixOS manual
+            "-chardev socket,id=backdoor,path=./backdoor,server,nowait -device virtio-serial -device virtconsole,chardev=backdoor " .
             "-device virtio-rng-pci " .
             ($showGraphics ? "-serial stdio" : "-nographic") . " " . ($ENV{QEMU_OPTS} || "");
         chdir $self->{stateDir} or die;
@@ -248,7 +250,8 @@ sub connect {
         $self->start;
 
         local $SIG{ALRM} = sub { die "timed out waiting for the VM to connect\n"; };
-        alarm 300;
+        # 50 minutes -- increased as a test, see #49441
+        alarm 3000;
         readline $self->{socket} or die "the VM quit before connecting\n";
         alarm 0;
 
diff --git a/nixos/modules/config/iproute2.nix b/nixos/modules/config/iproute2.nix
index 881ad671a627..a1d9ebcec66b 100644
--- a/nixos/modules/config/iproute2.nix
+++ b/nixos/modules/config/iproute2.nix
@@ -4,20 +4,29 @@ with lib;
 
 let
   cfg = config.networking.iproute2;
-  confDir = "/run/iproute2";
 in
 {
-  options.networking.iproute2.enable = mkEnableOption "copy IP route configuration files";
-
-  config = mkMerge [
-    ({ nixpkgs.config.iproute2.confDir = confDir; })
-
-    (mkIf cfg.enable {
-      system.activationScripts.iproute2 = ''
-        cp -R ${pkgs.iproute}/etc/iproute2 ${confDir}
-        chmod -R 664 ${confDir}
-        chmod +x ${confDir}
+  options.networking.iproute2 = {
+    enable = mkEnableOption "copy IP route configuration files";
+    rttablesExtraConfig = mkOption {
+      type = types.lines;
+      default = "";
+      description = ''
+        Verbatim lines to add to /etc/iproute2/rt_tables
       '';
-    })
-  ];
+    };
+  };
+
+  config = mkIf cfg.enable {
+    environment.etc."iproute2/bpf_pinning" = { mode = "0644"; text = fileContents "${pkgs.iproute}/etc/iproute2/bpf_pinning"; };
+    environment.etc."iproute2/ematch_map"  = { mode = "0644"; text = fileContents "${pkgs.iproute}/etc/iproute2/ematch_map";  };
+    environment.etc."iproute2/group"       = { mode = "0644"; text = fileContents "${pkgs.iproute}/etc/iproute2/group";       };
+    environment.etc."iproute2/nl_protos"   = { mode = "0644"; text = fileContents "${pkgs.iproute}/etc/iproute2/nl_protos";   };
+    environment.etc."iproute2/rt_dsfield"  = { mode = "0644"; text = fileContents "${pkgs.iproute}/etc/iproute2/rt_dsfield";  };
+    environment.etc."iproute2/rt_protos"   = { mode = "0644"; text = fileContents "${pkgs.iproute}/etc/iproute2/rt_protos";   };
+    environment.etc."iproute2/rt_realms"   = { mode = "0644"; text = fileContents "${pkgs.iproute}/etc/iproute2/rt_realms";   };
+    environment.etc."iproute2/rt_scopes"   = { mode = "0644"; text = fileContents "${pkgs.iproute}/etc/iproute2/rt_scopes";   };
+    environment.etc."iproute2/rt_tables"   = { mode = "0644"; text = (fileContents "${pkgs.iproute}/etc/iproute2/rt_tables")
+                                                                   + (optionalString (cfg.rttablesExtraConfig != "") "\n\n${cfg.rttablesExtraConfig}"); };
+  };
 }
diff --git a/nixos/modules/config/krb5/default.nix b/nixos/modules/config/krb5/default.nix
index c22e99a0a2f1..87021a27d34f 100644
--- a/nixos/modules/config/krb5/default.nix
+++ b/nixos/modules/config/krb5/default.nix
@@ -79,7 +79,7 @@ in {
 
   options = {
     krb5 = {
-      enable = mkEnableOption "Whether to enable Kerberos V.";
+      enable = mkEnableOption "building krb5.conf, configuration file for Kerberos V";
 
       kerberos = mkOption {
         type = types.package;
diff --git a/nixos/modules/config/networking.nix b/nixos/modules/config/networking.nix
index 1ef5313d3fdd..627cce67e97d 100644
--- a/nixos/modules/config/networking.nix
+++ b/nixos/modules/config/networking.nix
@@ -16,6 +16,13 @@ let
   resolvconfOptions = cfg.resolvconfOptions
     ++ optional cfg.dnsSingleRequest "single-request"
     ++ optional cfg.dnsExtensionMechanism "edns0";
+
+
+  localhostMapped4 = cfg.hosts ? "127.0.0.1" && elem "localhost" cfg.hosts."127.0.0.1";
+  localhostMapped6 = cfg.hosts ? "::1"       && elem "localhost" cfg.hosts."::1";
+
+  localhostMultiple = any (elem "localhost") (attrValues (removeAttrs cfg.hosts [ "127.0.0.1" "::1" ]));
+
 in
 
 {
@@ -23,8 +30,7 @@ in
   options = {
 
     networking.hosts = lib.mkOption {
-      type = types.attrsOf ( types.listOf types.str );
-      default = {};
+      type = types.attrsOf (types.listOf types.str);
       example = literalExample ''
         {
           "127.0.0.1" = [ "foo.bar.baz" ];
@@ -192,6 +198,29 @@ in
 
   config = {
 
+    assertions = [{
+      assertion = localhostMapped4;
+      message = ''`networking.hosts` doesn't map "127.0.0.1" to "localhost"'';
+    } {
+      assertion = !cfg.enableIPv6 || localhostMapped6;
+      message = ''`networking.hosts` doesn't map "::1" to "localhost"'';
+    } {
+      assertion = !localhostMultiple;
+      message = ''
+        `networking.hosts` maps "localhost" to something other than "127.0.0.1"
+        or "::1". This will break some applications. Please use
+        `networking.extraHosts` if you really want to add such a mapping.
+      '';
+    }];
+
+    networking.hosts = {
+      "127.0.0.1" = [ "localhost" ];
+    } // optionalAttrs (cfg.hostName != "") {
+      "127.0.1.1" = [ cfg.hostName ];
+    } // optionalAttrs cfg.enableIPv6 {
+      "::1" = [ "localhost" ];
+    };
+
     environment.etc =
       { # /etc/services: TCP/UDP port assignments.
         "services".source = pkgs.iana-etc + "/etc/services";
@@ -199,29 +228,14 @@ in
         # /etc/protocols: IP protocol numbers.
         "protocols".source  = pkgs.iana-etc + "/etc/protocols";
 
-        # /etc/rpc: RPC program numbers.
-        "rpc".source = pkgs.glibc.out + "/etc/rpc";
-
         # /etc/hosts: Hostname-to-IP mappings.
-        "hosts".text =
-          let oneToString = set : ip : ip + " " + concatStringsSep " " ( getAttr ip set );
-              allToString = set : concatMapStringsSep "\n" ( oneToString set ) ( attrNames set );
-              userLocalHosts = optionalString
-                ( builtins.hasAttr "127.0.0.1" cfg.hosts )
-                ( concatStringsSep " " ( remove "localhost" cfg.hosts."127.0.0.1" ));
-              userLocalHosts6 = optionalString
-                ( builtins.hasAttr "::1" cfg.hosts )
-                ( concatStringsSep " " ( remove "localhost" cfg.hosts."::1" ));
-              otherHosts = allToString ( removeAttrs cfg.hosts [ "127.0.0.1" "::1" ]);
-          in
-          ''
-            127.0.0.1 ${userLocalHosts} localhost
-            ${optionalString cfg.enableIPv6 ''
-              ::1 ${userLocalHosts6} localhost
-            ''}
-            ${otherHosts}
-            ${cfg.extraHosts}
-          '';
+        "hosts".text = let
+          oneToString = set: ip: ip + " " + concatStringsSep " " set.${ip};
+          allToString = set: concatMapStringsSep "\n" (oneToString set) (attrNames set);
+        in ''
+          ${allToString cfg.hosts}
+          ${cfg.extraHosts}
+        '';
 
         # /etc/host.conf: resolver configuration file
         "host.conf".text = cfg.hostConf;
@@ -251,6 +265,9 @@ in
         "resolv.conf".source = "${pkgs.systemd}/lib/systemd/resolv.conf";
       } // optionalAttrs (config.services.resolved.enable && dnsmasqResolve) {
         "dnsmasq-resolv.conf".source = "/run/systemd/resolve/resolv.conf";
+      } // optionalAttrs (pkgs.stdenv.hostPlatform.libc == "glibc") {
+        # /etc/rpc: RPC program numbers.
+        "rpc".source = pkgs.glibc.out + "/etc/rpc";
       };
 
       networking.proxy.envVars =
@@ -296,4 +313,4 @@ in
 
   };
 
-  }
+}
diff --git a/nixos/modules/config/pulseaudio.nix b/nixos/modules/config/pulseaudio.nix
index e16a021ec20b..d4aa59506295 100644
--- a/nixos/modules/config/pulseaudio.nix
+++ b/nixos/modules/config/pulseaudio.nix
@@ -154,6 +154,18 @@ in {
         '';
       };
 
+      extraModules = mkOption {
+        type = types.listOf types.package;
+        default = [];
+        example = literalExample "[ pkgs.pulseaudio-modules-bt ]";
+        description = ''
+          Extra pulseaudio modules to use. This is intended for out-of-tree
+          pulseaudio modules like extra bluetooth codecs.
+
+          Extra modules take precedence over built-in pulseaudio modules.
+        '';
+      };
+
       daemon = {
         logLevel = mkOption {
           type = types.str;
@@ -236,6 +248,18 @@ in {
       systemd.packages = [ overriddenPackage ];
     })
 
+    (mkIf (cfg.extraModules != []) {
+      hardware.pulseaudio.daemon.config.dl-search-path = let
+        overriddenModules = builtins.map
+          (drv: drv.override { pulseaudio = overriddenPackage; })
+          cfg.extraModules;
+        modulePaths = builtins.map
+          (drv: "${drv}/lib/pulse-${overriddenPackage.version}/modules")
+          # User-provided extra modules take precedence
+          (overriddenModules ++ [ overriddenPackage ]);
+      in lib.concatStringsSep ":" modulePaths;
+    })
+
     (mkIf hasZeroconf {
       services.avahi.enable = true;
     })
diff --git a/nixos/modules/config/shells-environment.nix b/nixos/modules/config/shells-environment.nix
index 31adc9b82620..6379b52870ea 100644
--- a/nixos/modules/config/shells-environment.nix
+++ b/nixos/modules/config/shells-environment.nix
@@ -108,14 +108,14 @@ in
     };
 
     environment.shellAliases = mkOption {
-      default = {};
-      example = { ll = "ls -l"; };
+      example = { l = null; ll = "ls -l"; };
       description = ''
         An attribute set that maps aliases (the top level attribute names in
         this option) to command strings or directly to build outputs. The
         aliases are added to all users' shells.
+        Aliases mapped to <code>null</code> are ignored.
       '';
-      type = types.attrs; # types.attrsOf types.stringOrPath;
+      type = with types; attrsOf (nullOr (either str path));
     };
 
     environment.binsh = mkOption {
@@ -157,21 +157,36 @@ in
     # terminal instead of logging out of X11).
     environment.variables = config.environment.sessionVariables;
 
+    environment.shellAliases = mapAttrs (name: mkDefault) {
+      ls = "ls --color=tty";
+      ll = "ls -l";
+      l  = "ls -alh";
+    };
+
     environment.etc."shells".text =
       ''
         ${concatStringsSep "\n" (map utils.toShellPath cfg.shells)}
         /bin/sh
       '';
 
+    # For resetting environment with `. /etc/set-environment` when needed
+    # and discoverability (see motivation of #30418).
+    environment.etc."set-environment".source = config.system.build.setEnvironment;
+
     system.build.setEnvironment = pkgs.writeText "set-environment"
-       ''
-         ${exportedEnvVars}
+      ''
+        # DO NOT EDIT -- this file has been generated automatically.
+
+        # Prevent this file from being sourced by child shells.
+        export __NIXOS_SET_ENVIRONMENT_DONE=1
 
-         ${cfg.extraInit}
+        ${exportedEnvVars}
 
-         # ~/bin if it exists overrides other bin directories.
-         export PATH="$HOME/bin:$PATH"
-       '';
+        ${cfg.extraInit}
+
+        # ~/bin if it exists overrides other bin directories.
+        export PATH="$HOME/bin:$PATH"
+      '';
 
     system.activationScripts.binsh = stringAfter [ "stdio" ]
       ''
diff --git a/nixos/modules/config/system-path.nix b/nixos/modules/config/system-path.nix
index ffb437491f6c..1793dc628edf 100644
--- a/nixos/modules/config/system-path.nix
+++ b/nixos/modules/config/system-path.nix
@@ -13,13 +13,15 @@ let
       pkgs.attr
       pkgs.bashInteractive # bash with ncurses support
       pkgs.bzip2
-      pkgs.coreutils
+      pkgs.coreutils-full
       pkgs.cpio
       pkgs.curl
       pkgs.diffutils
       pkgs.findutils
       pkgs.gawk
-      pkgs.glibc # for ldd, getent
+      pkgs.stdenv.cc.libc
+      pkgs.getent
+      pkgs.getconf
       pkgs.gnugrep
       pkgs.gnupatch
       pkgs.gnused
@@ -140,7 +142,7 @@ in
           if [ -x $out/bin/glib-compile-schemas -a -w $out/share/glib-2.0/schemas ]; then
               $out/bin/glib-compile-schemas $out/share/glib-2.0/schemas
           fi
-          
+
           ${config.environment.extraSetup}
         '';
     };
diff --git a/nixos/modules/config/users-groups.nix b/nixos/modules/config/users-groups.nix
index 426e1666a814..137ee243813d 100644
--- a/nixos/modules/config/users-groups.nix
+++ b/nixos/modules/config/users-groups.nix
@@ -266,7 +266,7 @@ let
         (mkIf config.isNormalUser {
           group = mkDefault "users";
           createHome = mkDefault true;
-          home = mkDefault "/home/${name}";
+          home = mkDefault "/home/${config.name}";
           useDefaultShell = mkDefault true;
           isSystemUser = mkDefault false;
         })
diff --git a/nixos/modules/config/xdg/mime.nix b/nixos/modules/config/xdg/mime.nix
index f1b672234a34..cd1064630fbc 100644
--- a/nixos/modules/config/xdg/mime.nix
+++ b/nixos/modules/config/xdg/mime.nix
@@ -7,7 +7,7 @@ with lib;
       type = types.bool;
       default = true;
       description = ''
-        Whether to install files to support the 
+        Whether to install files to support the
         <link xlink:href="https://specifications.freedesktop.org/shared-mime-info-spec/shared-mime-info-spec-latest.html">XDG Shared MIME-info specification</link> and the
         <link xlink:href="https://specifications.freedesktop.org/mime-apps-spec/mime-apps-spec-latest.html">XDG MIME Applications specification</link>.
       '';
@@ -17,18 +17,18 @@ with lib;
   config = mkIf config.xdg.mime.enable {
     environment.pathsToLink = [ "/share/mime" ];
 
-    environment.systemPackages = [ 
-      # this package also installs some useful data, as well as its utilities 
-      pkgs.shared-mime-info 
+    environment.systemPackages = [
+      # this package also installs some useful data, as well as its utilities
+      pkgs.shared-mime-info
     ];
 
     environment.extraSetup = ''
-      if [ -w $out/share/mime ]; then
-        XDG_DATA_DIRS=$out/share ${pkgs.shared-mime-info}/bin/update-mime-database -V $out/share/mime > /dev/null
+      if [ -w $out/share/mime ] && [ -d $out/share/mime/packages ]; then
+          XDG_DATA_DIRS=$out/share ${pkgs.buildPackages.shared-mime-info}/bin/update-mime-database -V $out/share/mime > /dev/null
       fi
 
       if [ -w $out/share/applications ]; then
-        ${pkgs.desktop-file-utils}/bin/update-desktop-database $out/share/applications
+          ${pkgs.buildPackages.desktop-file-utils}/bin/update-desktop-database $out/share/applications
       fi
     '';
   };
diff --git a/nixos/modules/hardware/opengl.nix b/nixos/modules/hardware/opengl.nix
index b371af353cf9..46d06d71333a 100644
--- a/nixos/modules/hardware/opengl.nix
+++ b/nixos/modules/hardware/opengl.nix
@@ -129,17 +129,17 @@ in
       message = "Option driSupport32Bit only makes sense on a 64-bit system.";
     };
 
-    system.activationScripts.setup-opengl =
-      ''
-        ln -sfn ${package} /run/opengl-driver
-        ${if pkgs.stdenv.isi686 then ''
-          ln -sfn opengl-driver /run/opengl-driver-32
-        '' else if cfg.driSupport32Bit then ''
-          ln -sfn ${package32} /run/opengl-driver-32
-        '' else ''
-          rm -f /run/opengl-driver-32
-        ''}
-      '';
+    systemd.tmpfiles.rules = [
+      "L+ /run/opengl-driver - - - - ${package}"
+      (
+        if pkgs.stdenv.isi686 then
+          "L+ /run/opengl-driver-32 - - - - opengl-driver"
+        else if cfg.driSupport32Bit then
+          "L+ /run/opengl-driver-32 - - - - ${package32}"
+        else
+          "r /run/opengl-driver-32"
+      )
+    ];
 
     environment.sessionVariables.LD_LIBRARY_PATH =
       [ "/run/opengl-driver/lib" ] ++ optional cfg.driSupport32Bit "/run/opengl-driver-32/lib";
diff --git a/nixos/modules/hardware/steam-hardware.nix b/nixos/modules/hardware/steam-hardware.nix
new file mode 100644
index 000000000000..378aeffe71b5
--- /dev/null
+++ b/nixos/modules/hardware/steam-hardware.nix
@@ -0,0 +1,25 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.hardware.steam-hardware;
+
+in
+
+{
+  options.hardware.steam-hardware = {
+    enable = mkOption {
+      type = types.bool;
+      default = false;
+      description = "Enable udev rules for Steam hardware such as the Steam Controller, other supported controllers and the HTC Vive";
+    };
+  };
+
+  config = mkIf cfg.enable {
+    services.udev.packages = [
+      pkgs.steamPackages.steam
+    ];
+  };
+}
diff --git a/nixos/modules/hardware/video/nvidia.nix b/nixos/modules/hardware/video/nvidia.nix
index eb1952280331..f8524ab99e8a 100644
--- a/nixos/modules/hardware/video/nvidia.nix
+++ b/nixos/modules/hardware/video/nvidia.nix
@@ -1,6 +1,6 @@
 # This module provides the proprietary NVIDIA X11 / OpenGL drivers.
 
-{ config, lib, pkgs, pkgs_i686, ... }:
+{ stdenv, config, lib, pkgs, pkgs_i686, ... }:
 
 with lib;
 
@@ -23,35 +23,149 @@ let
     else null;
 
   nvidia_x11 = nvidiaForKernel config.boot.kernelPackages;
-  nvidia_libs32 = (nvidiaForKernel pkgs_i686.linuxPackages).override { libsOnly = true; kernel = null; };
+  nvidia_libs32 =
+    if versionOlder nvidia_x11.version "391" then
+      ((nvidiaForKernel pkgs_i686.linuxPackages).override { libsOnly = true; kernel = null; }).out
+    else
+      (nvidiaForKernel config.boot.kernelPackages).lib32;
 
   enabled = nvidia_x11 != null;
+
+  cfg = config.hardware.nvidia;
+  optimusCfg = cfg.optimus_prime;
 in
 
 {
+  options = {
+    hardware.nvidia.modesetting.enable = lib.mkOption {
+      type = lib.types.bool;
+      default = false;
+      description = ''
+        Enable kernel modesetting when using the NVIDIA proprietary driver.
+
+        Enabling this fixes screen tearing when using Optimus via PRIME (see
+        <option>hardware.nvidia.optimus_prime.enable</option>. This is not enabled
+        by default because it is not officially supported by NVIDIA and would not
+        work with SLI.
+      '';
+    };
+
+    hardware.nvidia.optimus_prime.enable = lib.mkOption {
+      type = lib.types.bool;
+      default = false;
+      description = ''
+        Enable NVIDIA Optimus support using the NVIDIA proprietary driver via PRIME.
+        If enabled, the NVIDIA GPU will be always on and used for all rendering,
+        while enabling output to displays attached only to the integrated Intel GPU
+        without a multiplexer.
+
+        Note that this option only has any effect if the "nvidia" driver is specified
+        in <option>services.xserver.videoDrivers</option>, and it should preferably
+        be the only driver there.
+
+        If this is enabled, then the bus IDs of the NVIDIA and Intel GPUs have to be
+        specified (<option>hardware.nvidia.optimus_prime.nvidiaBusId</option> and
+        <option>hardware.nvidia.optimus_prime.intelBusId</option>).
+
+        If you enable this, you may want to also enable kernel modesetting for the
+        NVIDIA driver (<option>hardware.nvidia.modesetting.enable</option>) in order
+        to prevent tearing.
+
+        Note that this configuration will only be successful when a display manager
+        for which the <option>services.xserver.displayManager.setupCommands</option>
+        option is supported is used; notably, SLiM is not supported.
+      '';
+    };
+
+    hardware.nvidia.optimus_prime.nvidiaBusId = lib.mkOption {
+      type = lib.types.string;
+      default = "";
+      example = "PCI:1:0:0";
+      description = ''
+        Bus ID of the NVIDIA GPU. You can find it using lspci; for example if lspci
+        shows the NVIDIA GPU at "01:00.0", set this option to "PCI:1:0:0".
+      '';
+    };
+
+    hardware.nvidia.optimus_prime.intelBusId = lib.mkOption {
+      type = lib.types.string;
+      default = "";
+      example = "PCI:0:2:0";
+      description = ''
+        Bus ID of the Intel GPU. You can find it using lspci; for example if lspci
+        shows the Intel GPU at "00:02.0", set this option to "PCI:0:2:0".
+      '';
+    };
+  };
 
   config = mkIf enabled {
     assertions = [
       {
         assertion = config.services.xserver.displayManager.gdm.wayland;
-        message = "NVidia drivers don't support wayland";
+        message = "NVIDIA drivers don't support wayland";
+      }
+      {
+        assertion = !optimusCfg.enable ||
+          (optimusCfg.nvidiaBusId != "" && optimusCfg.intelBusId != "");
+        message = ''
+          When NVIDIA Optimus via PRIME is enabled, the GPU bus IDs must configured.
+        '';
       }
     ];
 
-    services.xserver.drivers = singleton
-      { name = "nvidia"; modules = [ nvidia_x11.bin ]; libPath = [ nvidia_x11 ]; };
+    # If Optimus/PRIME is enabled, we:
+    # - Specify the configured NVIDIA GPU bus ID in the Device section for the
+    #   "nvidia" driver.
+    # - Add the AllowEmptyInitialConfiguration option to the Screen section for the
+    #   "nvidia" driver, in order to allow the X server to start without any outputs.
+    # - Add a separate Device section for the Intel GPU, using the "modesetting"
+    #   driver and with the configured BusID.
+    # - Reference that Device section from the ServerLayout section as an inactive
+    #   device.
+    # - Configure the display manager to run specific `xrandr` commands which will
+    #   configure/enable displays connected to the Intel GPU.
+
+    services.xserver.drivers = singleton {
+      name = "nvidia";
+      modules = [ nvidia_x11.bin ];
+      libPath = [ nvidia_x11 ];
+      deviceSection = optionalString optimusCfg.enable
+        ''
+          BusID "${optimusCfg.nvidiaBusId}"
+        '';
+      screenSection =
+        ''
+          Option "RandRRotation" "on"
+          ${optionalString optimusCfg.enable "Option \"AllowEmptyInitialConfiguration\""}
+        '';
+    };
 
-    services.xserver.screenSection =
+    services.xserver.extraConfig = optionalString optimusCfg.enable
+      ''
+        Section "Device"
+          Identifier "nvidia-optimus-intel"
+          Driver "modesetting"
+          BusID  "${optimusCfg.intelBusId}"
+          Option "AccelMethod" "none"
+        EndSection
+      '';
+    services.xserver.serverLayoutSection = optionalString optimusCfg.enable
       ''
-        Option "RandRRotation" "on"
+        Inactive "nvidia-optimus-intel"
       '';
 
+    services.xserver.displayManager.setupCommands = optionalString optimusCfg.enable ''
+      # Added by nvidia configuration module for Optimus/PRIME.
+      ${pkgs.xorg.xrandr}/bin/xrandr --setprovideroutputsource modesetting NVIDIA-0
+      ${pkgs.xorg.xrandr}/bin/xrandr --auto
+    '';
+
     environment.etc."nvidia/nvidia-application-profiles-rc" = mkIf nvidia_x11.useProfiles {
       source = "${nvidia_x11.bin}/share/nvidia/nvidia-application-profiles-rc";
     };
 
     hardware.opengl.package = nvidia_x11.out;
-    hardware.opengl.package32 = nvidia_libs32.out;
+    hardware.opengl.package32 = nvidia_libs32;
 
     environment.systemPackages = [ nvidia_x11.bin nvidia_x11.settings ]
       ++ lib.filter (p: p != null) [ nvidia_x11.persistenced ];
@@ -62,6 +176,8 @@ in
     boot.kernelModules = [ "nvidia-uvm" ] ++
       lib.optionals config.services.xserver.enable [ "nvidia" "nvidia_modeset" "nvidia_drm" ];
 
+    # If requested enable modesetting via kernel parameter.
+    boot.kernelParams = optional cfg.modesetting.enable "nvidia-drm.modeset=1";
 
     # Create /dev/nvidia-uvm when the nvidia-uvm module is loaded.
     services.udev.extraRules =
diff --git a/nixos/modules/i18n/input-method/default.xml b/nixos/modules/i18n/input-method/default.xml
index ab918a9fb23e..117482fb0d57 100644
--- a/nixos/modules/i18n/input-method/default.xml
+++ b/nixos/modules/i18n/input-method/default.xml
@@ -3,32 +3,50 @@
          xmlns:xi="http://www.w3.org/2001/XInclude"
          version="5.0"
          xml:id="module-services-input-methods">
-
-<title>Input Methods</title>
-
-<para>Input methods are an operating system component that allows any data, such
-  as keyboard strokes or mouse movements, to be received as input. In this way
-  users can enter characters and symbols not found on their input devices. Using
-  an input method is obligatory for any language that has more graphemes than
-  there are keys on the keyboard.</para>
-
-<para>The following input methods are available in NixOS:</para>
-
-<itemizedlist>
-  <listitem><para>IBus: The intelligent input bus.</para></listitem>
-  <listitem><para>Fcitx: A customizable lightweight input
-      method.</para></listitem>
-  <listitem><para>Nabi: A Korean input method based on XIM.</para></listitem>
-  <listitem><para>Uim: The universal input method, is a library with a XIM
-      bridge.</para></listitem>
-</itemizedlist>
-
-<section xml:id="module-services-input-methods-ibus"><title>IBus</title>
-
-<para>IBus is an Intelligent Input Bus. It provides full featured and user
-  friendly input method user interface.</para>
-
-<para>The following snippet can be used to configure IBus:</para>
+ <title>Input Methods</title>
+ <para>
+  Input methods are an operating system component that allows any data, such as
+  keyboard strokes or mouse movements, to be received as input. In this way
+  users can enter characters and symbols not found on their input devices.
+  Using an input method is obligatory for any language that has more graphemes
+  than there are keys on the keyboard.
+ </para>
+ <para>
+  The following input methods are available in NixOS:
+ </para>
+ <itemizedlist>
+  <listitem>
+   <para>
+    IBus: The intelligent input bus.
+   </para>
+  </listitem>
+  <listitem>
+   <para>
+    Fcitx: A customizable lightweight input method.
+   </para>
+  </listitem>
+  <listitem>
+   <para>
+    Nabi: A Korean input method based on XIM.
+   </para>
+  </listitem>
+  <listitem>
+   <para>
+    Uim: The universal input method, is a library with a XIM bridge.
+   </para>
+  </listitem>
+ </itemizedlist>
+ <section xml:id="module-services-input-methods-ibus">
+  <title>IBus</title>
+
+  <para>
+   IBus is an Intelligent Input Bus. It provides full featured and user
+   friendly input method user interface.
+  </para>
+
+  <para>
+   The following snippet can be used to configure IBus:
+  </para>
 
 <programlisting>
 i18n.inputMethod = {
@@ -37,57 +55,89 @@ i18n.inputMethod = {
 };
 </programlisting>
 
-<para><literal>i18n.inputMethod.ibus.engines</literal> is optional and can be
-  used to add extra IBus engines.</para>
-
-<para>Available extra IBus engines are:</para>
-
-<itemizedlist>
-  <listitem><para>Anthy (<literal>ibus-engines.anthy</literal>): Anthy is a
-      system for Japanese input method. It converts Hiragana text to Kana Kanji
-      mixed text.</para></listitem>
-  <listitem><para>Hangul (<literal>ibus-engines.hangul</literal>): Korean input
-      method.</para></listitem>
-  <listitem><para>m17n (<literal>ibus-engines.m17n</literal>): m17n is an input
-      method that uses input methods and corresponding icons in the m17n
-      database.</para></listitem>
-  <listitem><para>mozc (<literal>ibus-engines.mozc</literal>): A Japanese input
-      method from Google.</para></listitem>
-  <listitem><para>Table (<literal>ibus-engines.table</literal>): An input method
-      that load tables of input methods.</para></listitem>
-  <listitem><para>table-others (<literal>ibus-engines.table-others</literal>):
-      Various table-based input methods. To use this, and any other table-based
-      input methods, it must appear in the list of engines along with
-      <literal>table</literal>. For example:
+  <para>
+   <literal>i18n.inputMethod.ibus.engines</literal> is optional and can be used
+   to add extra IBus engines.
+  </para>
+
+  <para>
+   Available extra IBus engines are:
+  </para>
+
+  <itemizedlist>
+   <listitem>
+    <para>
+     Anthy (<literal>ibus-engines.anthy</literal>): Anthy is a system for
+     Japanese input method. It converts Hiragana text to Kana Kanji mixed text.
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     Hangul (<literal>ibus-engines.hangul</literal>): Korean input method.
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     m17n (<literal>ibus-engines.m17n</literal>): m17n is an input method that
+     uses input methods and corresponding icons in the m17n database.
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     mozc (<literal>ibus-engines.mozc</literal>): A Japanese input method from
+     Google.
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     Table (<literal>ibus-engines.table</literal>): An input method that load
+     tables of input methods.
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     table-others (<literal>ibus-engines.table-others</literal>): Various
+     table-based input methods. To use this, and any other table-based input
+     methods, it must appear in the list of engines along with
+     <literal>table</literal>. For example:
 <programlisting>
 ibus.engines = with pkgs.ibus-engines; [ table table-others ];
 </programlisting>
-  </para></listitem>
-</itemizedlist>
-
-<para>To use any input method, the package must be added in the configuration,
-  as shown above, and also (after running <literal>nixos-rebuild</literal>) the
-  input method must be added from IBus' preference dialog.</para>
-
-<simplesect xml:id="module-services-input-methods-troubleshooting">
-  <title>Troubleshooting</title>
-  <para>If IBus works in some applications but not others, a likely cause of
-  this is that IBus is depending on a different version of
-  <literal>glib</literal> to what the applications are depending on. This can
-  be checked by running <literal>nix-store -q --requisites &lt;path&gt; | grep
-  glib</literal>, where <literal>&lt;path&gt;</literal> is the path of either
-  IBus or an application in the Nix store. The <literal>glib</literal>
-  packages must match exactly. If they do not, uninstalling and reinstalling
-  the application is a likely fix.</para>
-</simplesect>
-</section>
-
-<section xml:id="module-services-input-methods-fcitx"><title>Fcitx</title>
-
-<para>Fcitx is an input method framework with extension support. It has three
-  built-in Input Method Engine, Pinyin, QuWei and Table-based input
-  methods.</para>
-<para>The following snippet can be used to configure Fcitx:</para>
+    </para>
+   </listitem>
+  </itemizedlist>
+
+  <para>
+   To use any input method, the package must be added in the configuration, as
+   shown above, and also (after running <literal>nixos-rebuild</literal>) the
+   input method must be added from IBus' preference dialog.
+  </para>
+
+  <simplesect xml:id="module-services-input-methods-troubleshooting">
+   <title>Troubleshooting</title>
+   <para>
+    If IBus works in some applications but not others, a likely cause of this
+    is that IBus is depending on a different version of <literal>glib</literal>
+    to what the applications are depending on. This can be checked by running
+    <literal>nix-store -q --requisites &lt;path&gt; | grep glib</literal>,
+    where <literal>&lt;path&gt;</literal> is the path of either IBus or an
+    application in the Nix store. The <literal>glib</literal> packages must
+    match exactly. If they do not, uninstalling and reinstalling the
+    application is a likely fix.
+   </para>
+  </simplesect>
+ </section>
+ <section xml:id="module-services-input-methods-fcitx">
+  <title>Fcitx</title>
+
+  <para>
+   Fcitx is an input method framework with extension support. It has three
+   built-in Input Method Engine, Pinyin, QuWei and Table-based input methods.
+  </para>
+
+  <para>
+   The following snippet can be used to configure Fcitx:
+  </para>
 
 <programlisting>
 i18n.inputMethod = {
@@ -96,51 +146,89 @@ i18n.inputMethod = {
 };
 </programlisting>
 
-<para><literal>i18n.inputMethod.fcitx.engines</literal> is optional and can be
-  used to add extra Fcitx engines.</para>
-
-<para>Available extra Fcitx engines are:</para>
-
-<itemizedlist>
-  <listitem><para>Anthy (<literal>fcitx-engines.anthy</literal>): Anthy is a
-      system for Japanese input method. It converts Hiragana text to Kana Kanji
-      mixed text.</para></listitem>
-  <listitem><para>Chewing (<literal>fcitx-engines.chewing</literal>): Chewing is
-      an intelligent Zhuyin input method. It is one of the most popular input
-      methods among Traditional Chinese Unix users.</para></listitem>
-  <listitem><para>Hangul (<literal>fcitx-engines.hangul</literal>): Korean input
-      method.</para></listitem>
-  <listitem><para>Unikey (<literal>fcitx-engines.unikey</literal>): Vietnamese input
-      method.</para></listitem>
-  <listitem><para>m17n (<literal>fcitx-engines.m17n</literal>): m17n is an input
-      method that uses input methods and corresponding icons in the m17n
-      database.</para></listitem>
-  <listitem><para>mozc (<literal>fcitx-engines.mozc</literal>): A Japanese input
-      method from Google.</para></listitem>
-  <listitem><para>table-others (<literal>fcitx-engines.table-others</literal>):
-      Various table-based input methods.</para></listitem>
-</itemizedlist>
-</section>
-
-<section xml:id="module-services-input-methods-nabi"><title>Nabi</title>
-
-<para>Nabi is an easy to use Korean X input method. It allows you to enter
-  phonetic Korean characters (hangul) and pictographic Korean characters
-  (hanja).</para>
-<para>The following snippet can be used to configure Nabi:</para>
+  <para>
+   <literal>i18n.inputMethod.fcitx.engines</literal> is optional and can be
+   used to add extra Fcitx engines.
+  </para>
+
+  <para>
+   Available extra Fcitx engines are:
+  </para>
+
+  <itemizedlist>
+   <listitem>
+    <para>
+     Anthy (<literal>fcitx-engines.anthy</literal>): Anthy is a system for
+     Japanese input method. It converts Hiragana text to Kana Kanji mixed text.
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     Chewing (<literal>fcitx-engines.chewing</literal>): Chewing is an
+     intelligent Zhuyin input method. It is one of the most popular input
+     methods among Traditional Chinese Unix users.
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     Hangul (<literal>fcitx-engines.hangul</literal>): Korean input method.
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     Unikey (<literal>fcitx-engines.unikey</literal>): Vietnamese input method.
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     m17n (<literal>fcitx-engines.m17n</literal>): m17n is an input method that
+     uses input methods and corresponding icons in the m17n database.
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     mozc (<literal>fcitx-engines.mozc</literal>): A Japanese input method from
+     Google.
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     table-others (<literal>fcitx-engines.table-others</literal>): Various
+     table-based input methods.
+    </para>
+   </listitem>
+  </itemizedlist>
+ </section>
+ <section xml:id="module-services-input-methods-nabi">
+  <title>Nabi</title>
+
+  <para>
+   Nabi is an easy to use Korean X input method. It allows you to enter
+   phonetic Korean characters (hangul) and pictographic Korean characters
+   (hanja).
+  </para>
+
+  <para>
+   The following snippet can be used to configure Nabi:
+  </para>
 
 <programlisting>
 i18n.inputMethod = {
   <link linkend="opt-i18n.inputMethod.enabled">enabled</link> = "nabi";
 };
 </programlisting>
-</section>
+ </section>
+ <section xml:id="module-services-input-methods-uim">
+  <title>Uim</title>
 
-<section xml:id="module-services-input-methods-uim"><title>Uim</title>
+  <para>
+   Uim (short for "universal input method") is a multilingual input method
+   framework. Applications can use it through so-called bridges.
+  </para>
 
-<para>Uim (short for "universal input method") is a multilingual input method
-  framework. Applications can use it through so-called bridges.</para>
-<para>The following snippet can be used to configure uim:</para>
+  <para>
+   The following snippet can be used to configure uim:
+  </para>
 
 <programlisting>
 i18n.inputMethod = {
@@ -148,8 +236,9 @@ i18n.inputMethod = {
 };
 </programlisting>
 
-<para>Note: The <xref linkend="opt-i18n.inputMethod.uim.toolbar"/> option can be
-  used to choose uim toolbar.</para>
-
-</section>
+  <para>
+   Note: The <xref linkend="opt-i18n.inputMethod.uim.toolbar"/> option can be
+   used to choose uim toolbar.
+  </para>
+ </section>
 </chapter>
diff --git a/nixos/modules/installer/cd-dvd/installation-cd-graphical-base.nix b/nixos/modules/installer/cd-dvd/installation-cd-graphical-base.nix
new file mode 100644
index 000000000000..228ef371d252
--- /dev/null
+++ b/nixos/modules/installer/cd-dvd/installation-cd-graphical-base.nix
@@ -0,0 +1,49 @@
+# This module contains the basic configuration for building a graphical NixOS
+# installation CD.
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+  imports = [ ./installation-cd-base.nix ];
+
+  services.xserver = {
+    enable = true;
+
+    # Don't start the X server by default.
+    autorun = mkForce false;
+
+    # Automatically login as root.
+    displayManager.slim = {
+      enable = true;
+      defaultUser = "root";
+      autoLogin = true;
+    };
+
+  };
+
+  # Provide networkmanager for easy wireless configuration.
+  networking.networkmanager.enable = true;
+  networking.wireless.enable = mkForce false;
+
+  # KDE complains if power management is disabled (to be precise, if
+  # there is no power management backend such as upower).
+  powerManagement.enable = true;
+
+  environment.systemPackages = [
+    # Include gparted for partitioning disks.
+    pkgs.gparted
+
+    # Include some editors.
+    pkgs.vim
+    pkgs.bvi # binary editor
+    pkgs.joe
+
+    # Firefox for reading the manual.
+    pkgs.firefox
+
+    pkgs.glxinfo
+  ];
+
+}
diff --git a/nixos/modules/installer/cd-dvd/installation-cd-graphical-gnome.nix b/nixos/modules/installer/cd-dvd/installation-cd-graphical-gnome.nix
index 4c4e69d60d9c..42b5ec882272 100644
--- a/nixos/modules/installer/cd-dvd/installation-cd-graphical-gnome.nix
+++ b/nixos/modules/installer/cd-dvd/installation-cd-graphical-gnome.nix
@@ -6,47 +6,11 @@
 with lib;
 
 {
-  imports = [ ./installation-cd-base.nix ];
+  imports = [ ./installation-cd-graphical-base.nix ];
 
-  services.xserver = {
-    enable = true;
-    # GDM doesn't start in virtual machines with ISO
-    displayManager.slim = {
-      enable = true;
-      defaultUser = "root";
-      autoLogin = true;
-    };
-    desktopManager.gnome3 = {
-      enable = true;
-      extraGSettingsOverrides = ''
-        [org.gnome.desktop.background]
-        show-desktop-icons=true
-
-        [org.gnome.nautilus.desktop]
-        trash-icon-visible=false
-        volumes-visible=false
-        home-icon-visible=false
-        network-icon-visible=false
-      '';
-
-      extraGSettingsOverridePackages = [ pkgs.gnome3.nautilus ];
-    };
-  };
-
-  environment.systemPackages =
-    [ # Include gparted for partitioning disks.
-      pkgs.gparted
+  services.xserver.desktopManager.gnome3.enable = true;
 
-      # Include some editors.
-      pkgs.vim
-      pkgs.bvi # binary editor
-      pkgs.joe
-
-      pkgs.glxinfo
-    ];
-
-  # Don't start the X server by default.
-  services.xserver.autorun = mkForce false;
+  services.xserver.displayManager.slim.enable = mkForce false;
 
   # Auto-login as root.
   services.xserver.displayManager.gdm.autoLogin = {
@@ -54,25 +18,4 @@ with lib;
     user = "root";
   };
 
-  system.activationScripts.installerDesktop = let
-    # Must be executable
-    desktopFile = pkgs.writeScript "nixos-manual.desktop" ''
-      [Desktop Entry]
-      Version=1.0
-      Type=Link
-      Name=NixOS Manual
-      URL=${config.system.build.manual.manual}/share/doc/nixos/index.html
-      Icon=system-help
-    '';
-
-  # use cp and chmod +x, we must be sure the apps are in the nix store though
-  in ''
-    mkdir -p /root/Desktop
-    ln -sfT ${desktopFile} /root/Desktop/nixos-manual.desktop
-    cp ${pkgs.gnome3.gnome-terminal}/share/applications/gnome-terminal.desktop /root/Desktop/gnome-terminal.desktop
-    chmod a+rx /root/Desktop/gnome-terminal.desktop
-    cp ${pkgs.gparted}/share/applications/gparted.desktop /root/Desktop/gparted.desktop
-    chmod a+rx /root/Desktop/gparted.desktop
-  '';
-
 }
diff --git a/nixos/modules/installer/cd-dvd/installation-cd-graphical-kde.nix b/nixos/modules/installer/cd-dvd/installation-cd-graphical-kde.nix
index 63227d573495..1c3c9cb30b41 100644
--- a/nixos/modules/installer/cd-dvd/installation-cd-graphical-kde.nix
+++ b/nixos/modules/installer/cd-dvd/installation-cd-graphical-kde.nix
@@ -1,23 +1,14 @@
 # This module defines a NixOS installation CD that contains X11 and
-# KDE 5.
+# Plasma5.
 
 { config, lib, pkgs, ... }:
 
 with lib;
 
 {
-  imports = [ ./installation-cd-base.nix ];
+  imports = [ ./installation-cd-graphical-base.nix ];
 
   services.xserver = {
-    enable = true;
-
-    # Automatically login as root.
-    displayManager.slim = {
-      enable = true;
-      defaultUser = "root";
-      autoLogin = true;
-    };
-
     desktopManager.plasma5 = {
       enable = true;
       enableQt4Support = false;
@@ -27,45 +18,25 @@ with lib;
     synaptics.enable = true;
   };
 
-  environment.systemPackages =
-    [ pkgs.glxinfo
-
-      # Include gparted for partitioning disks.
-      pkgs.gparted
-
-      # Firefox for reading the manual.
-      pkgs.firefox
-
-      # Include some editors.
-      pkgs.vim
-      pkgs.bvi # binary editor
-      pkgs.joe
-    ];
-
-  # Provide networkmanager for easy wireless configuration.
-  networking.networkmanager.enable = true;
-  networking.wireless.enable = mkForce false;
-
-  # KDE complains if power management is disabled (to be precise, if
-  # there is no power management backend such as upower).
-  powerManagement.enable = true;
-
-  # Don't start the X server by default.
-  services.xserver.autorun = mkForce false;
+  environment.systemPackages = with pkgs; [
+    # Graphical text editor
+    kate
+  ];
 
   system.activationScripts.installerDesktop = let
-    desktopFile = pkgs.writeText "nixos-manual.desktop" ''
+
+    manualDesktopFile = pkgs.writeScript "nixos-manual.desktop" ''
       [Desktop Entry]
       Version=1.0
       Type=Application
       Name=NixOS Manual
-      Exec=firefox ${config.system.build.manual.manual}/share/doc/nixos/index.html
+      Exec=firefox ${config.system.build.manual.manualHTMLIndex}
       Icon=text-html
     '';
 
   in ''
     mkdir -p /root/Desktop
-    ln -sfT ${desktopFile} /root/Desktop/nixos-manual.desktop
+    ln -sfT ${manualDesktopFile} /root/Desktop/nixos-manual.desktop
     ln -sfT ${pkgs.konsole}/share/applications/org.kde.konsole.desktop /root/Desktop/org.kde.konsole.desktop
     ln -sfT ${pkgs.gparted}/share/applications/gparted.desktop /root/Desktop/gparted.desktop
   '';
diff --git a/nixos/modules/installer/cd-dvd/installation-cd-minimal.nix b/nixos/modules/installer/cd-dvd/installation-cd-minimal.nix
index 3dc0f606bf60..bcdbffdc20b7 100644
--- a/nixos/modules/installer/cd-dvd/installation-cd-minimal.nix
+++ b/nixos/modules/installer/cd-dvd/installation-cd-minimal.nix
@@ -7,4 +7,6 @@
   imports =
     [ ./installation-cd-base.nix
     ];
+
+  fonts.fontconfig.enable = false;
 }
diff --git a/nixos/modules/installer/cd-dvd/iso-image.nix b/nixos/modules/installer/cd-dvd/iso-image.nix
index 98712f0759a9..96fdb997b2c0 100644
--- a/nixos/modules/installer/cd-dvd/iso-image.nix
+++ b/nixos/modules/installer/cd-dvd/iso-image.nix
@@ -233,7 +233,7 @@ let
             "
     # Make our own efi program, we can't rely on "grub-install" since it seems to
     # probe for devices, even with --skip-fs-probe.
-    ${pkgs.grub2_efi}/bin/grub-mkimage -o $out/EFI/boot/${if targetArch == "x64" then "bootx64" else "bootx32"}.efi -p /EFI/boot -O ${if targetArch == "x64" then "x86_64" else "i386"}-efi \
+    ${pkgs.grub2_efi}/bin/grub-mkimage -o $out/EFI/boot/${if targetArch == "x64" then "bootx64" else "bootia32"}.efi -p /EFI/boot -O ${if targetArch == "x64" then "x86_64" else "i386"}-efi \
       $MODULES
     cp ${pkgs.grub2_efi}/share/grub/unicode.pf2 $out/EFI/boot/
 
diff --git a/nixos/modules/installer/cd-dvd/system-tarball-sheevaplug.nix b/nixos/modules/installer/cd-dvd/system-tarball-sheevaplug.nix
index 7ec09acd5919..90a5128c02a5 100644
--- a/nixos/modules/installer/cd-dvd/system-tarball-sheevaplug.nix
+++ b/nixos/modules/installer/cd-dvd/system-tarball-sheevaplug.nix
@@ -137,7 +137,7 @@ in
   # Setting vesa, we don't get the nvidia driver, which can't work in arm.
   services.xserver.videoDrivers = [ "vesa" ];
 
-  services.nixosManual.enable = false;
+  documentation.nixos.enable = false;
 
   # Include the firmware for various wireless cards.
   networking.enableRalinkFirmware = true;
diff --git a/nixos/modules/installer/tools/nix-fallback-paths.nix b/nixos/modules/installer/tools/nix-fallback-paths.nix
index 7c5414257b46..1cfc8ff8612e 100644
--- a/nixos/modules/installer/tools/nix-fallback-paths.nix
+++ b/nixos/modules/installer/tools/nix-fallback-paths.nix
@@ -1,6 +1,6 @@
 {
-  x86_64-linux = "/nix/store/0d60i73mcv8z1m8d2m74yfn84980gfsa-nix-2.0.4";
-  i686-linux = "/nix/store/6ssafj2s5a2g9x28yld7b70vwd6vw6lb-nix-2.0.4";
-  aarch64-linux = "/nix/store/3wwch7bp7n7xsl8apgy2a4b16yzyij1z-nix-2.0.4";
-  x86_64-darwin = "/nix/store/771l8i0mz4c8kry8cz3sz8rr3alalckg-nix-2.0.4";
+  x86_64-linux = "/nix/store/cdcia67siabmj6li7vyffgv2cry86fq8-nix-2.1.3";
+  i686-linux = "/nix/store/6q3xi6y5qnsv7d62b8n00hqfxi8rs2xs-nix-2.1.3";
+  aarch64-linux = "/nix/store/2v93d0vimlm28jg0ms6v1i6lc0fq13pn-nix-2.1.3";
+  x86_64-darwin = "/nix/store/dkjlfkrknmxbjmpfk3dg4q3nmb7m3zvk-nix-2.1.3";
 }
diff --git a/nixos/modules/installer/tools/nixos-generate-config.pl b/nixos/modules/installer/tools/nixos-generate-config.pl
index 359caad89a72..b70faa380e54 100644
--- a/nixos/modules/installer/tools/nixos-generate-config.pl
+++ b/nixos/modules/installer/tools/nixos-generate-config.pl
@@ -277,8 +277,7 @@ if ($virt eq "qemu" || $virt eq "kvm" || $virt eq "bochs") {
 
 # Also for Hyper-V.
 if ($virt eq "microsoft") {
-    push @initrdAvailableKernelModules, "hv_storvsc";
-    $videoDriver = "fbdev";
+    push @attrs, "virtualisation.hypervGuest.enable = true;"
 }
 
 
diff --git a/nixos/modules/installer/tools/nixos-install.sh b/nixos/modules/installer/tools/nixos-install.sh
index 22c1e0fe9a34..defc46ad2a72 100644
--- a/nixos/modules/installer/tools/nixos-install.sh
+++ b/nixos/modules/installer/tools/nixos-install.sh
@@ -13,6 +13,7 @@ extraBuildFlags=()
 
 mountPoint=/mnt
 channelPath=
+system=
 
 while [ "$#" -gt 0 ]; do
     i="$1"; shift 1
diff --git a/nixos/modules/installer/tools/nixos-option.sh b/nixos/modules/installer/tools/nixos-option.sh
index 3f1e591b97b0..327e3e6989f7 100644
--- a/nixos/modules/installer/tools/nixos-option.sh
+++ b/nixos/modules/installer/tools/nixos-option.sh
@@ -82,7 +82,7 @@ evalNix(){
   set -e
 
   if test $exit_code -eq 0; then
-      cat <<EOF
+      sed '/^warning: Nix search path/d' <<EOF
 $result
 EOF
       return 0;
@@ -90,7 +90,7 @@ EOF
       sed -n '
   /^error/ { s/, at (string):[0-9]*:[0-9]*//; p; };
   /^warning: Nix search path/ { p; };
-' <<EOF
+' >&2 <<EOF
 $result
 EOF
     exit_code=1
diff --git a/nixos/modules/installer/virtualbox-demo.nix b/nixos/modules/installer/virtualbox-demo.nix
index 8ca3592f3800..2e1b4b3998b5 100644
--- a/nixos/modules/installer/virtualbox-demo.nix
+++ b/nixos/modules/installer/virtualbox-demo.nix
@@ -22,4 +22,42 @@ with lib;
 
   powerManagement.enable = false;
   system.stateVersion = mkDefault "18.03";
+
+  installer.cloneConfigExtra = ''
+  # Let demo build as a trusted user.
+  # nix.trustedUsers = [ "demo" ];
+
+  # Mount a VirtualBox shared folder.
+  # This is configurable in the VirtualBox menu at
+  # Machine / Settings / Shared Folders.
+  # fileSystems."/mnt" = {
+  #   fsType = "vboxsf";
+  #   device = "nameofdevicetomount";
+  #   options = [ "rw" ];
+  # };
+
+  # By default, the NixOS VirtualBox demo image includes SDDM and Plasma.
+  # If you prefer another desktop manager or display manager, you may want
+  # to disable the default.
+  # services.xserver.desktopManager.plasma5.enable = lib.mkForce false;
+  # services.xserver.displayManager.sddm.enable = lib.mkForce false;
+
+  # Enable GDM/GNOME by uncommenting above two lines and two lines below.
+  # services.xserver.displayManager.gdm.enable = true;
+  # services.xserver.desktopManager.gnome3.enable = true;
+
+  # Set your time zone.
+  # time.timeZone = "Europe/Amsterdam";
+
+  # List packages installed in system profile. To search, run:
+  # \$ nix search wget
+  # environment.systemPackages = with pkgs; [
+  #   wget vim
+  # ];
+
+  # Enable the OpenSSH daemon.
+  # services.openssh.enable = true;
+
+  system.stateVersion = mkDefault "18.03";
+  '';
 }
diff --git a/nixos/modules/misc/documentation.nix b/nixos/modules/misc/documentation.nix
index e6ccda5d7f40..09d53c322fb3 100644
--- a/nixos/modules/misc/documentation.nix
+++ b/nixos/modules/misc/documentation.nix
@@ -1,8 +1,72 @@
-{ config, lib, pkgs, ... }:
+{ config, lib, pkgs, baseModules, ... }:
 
 with lib;
 
-let cfg = config.documentation; in
+let
+
+  cfg = config.documentation;
+
+  /* For the purpose of generating docs, evaluate options with each derivation
+    in `pkgs` (recursively) replaced by a fake with path "\${pkgs.attribute.path}".
+    It isn't perfect, but it seems to cover a vast majority of use cases.
+    Caveat: even if the package is reached by a different means,
+    the path above will be shown and not e.g. `${config.services.foo.package}`. */
+  manual = import ../../doc/manual rec {
+    inherit pkgs config;
+    version = config.system.nixos.release;
+    revision = "release-${version}";
+    options =
+      let
+        scrubbedEval = evalModules {
+          modules = [ { nixpkgs.localSystem = config.nixpkgs.localSystem; } ] ++ baseModules;
+          args = (config._module.args) // { modules = [ ]; };
+          specialArgs = { pkgs = scrubDerivations "pkgs" pkgs; };
+        };
+        scrubDerivations = namePrefix: pkgSet: mapAttrs
+          (name: value:
+            let wholeName = "${namePrefix}.${name}"; in
+            if isAttrs value then
+              scrubDerivations wholeName value
+              // (optionalAttrs (isDerivation value) { outPath = "\${${wholeName}}"; })
+            else value
+          )
+          pkgSet;
+      in scrubbedEval.options;
+  };
+
+  helpScript = pkgs.writeScriptBin "nixos-help"
+    ''
+      #! ${pkgs.runtimeShell} -e
+      # Finds first executable browser in a colon-separated list.
+      # (see how xdg-open defines BROWSER)
+      browser="$(
+        IFS=: ; for b in $BROWSER; do
+          [ -n "$(type -P "$b" || true)" ] && echo "$b" && break
+        done
+      )"
+      if [ -z "$browser" ]; then
+        browser="$(type -P xdg-open || true)"
+        if [ -z "$browser" ]; then
+          browser="$(type -P w3m || true)"
+          if [ -z "$browser" ]; then
+            echo "$0: unable to start a web browser; please set \$BROWSER"
+            exit 1
+          fi
+        fi
+      fi
+      exec "$browser" ${manual.manualHTMLIndex}
+    '';
+
+  desktopItem = pkgs.makeDesktopItem {
+    name = "nixos-manual";
+    desktopName = "NixOS Manual";
+    genericName = "View NixOS documentation in a web browser";
+    icon = "nix-snowflake";
+    exec = "${helpScript}/bin/nixos-help";
+    categories = "System";
+  };
+
+in
 
 {
 
@@ -66,6 +130,22 @@ let cfg = config.documentation; in
         '';
       };
 
+      nixos.enable = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Whether to install NixOS's own documentation.
+          <itemizedlist>
+          <listitem><para>This includes man pages like
+                    <citerefentry><refentrytitle>configuration.nix</refentrytitle>
+                    <manvolnum>5</manvolnum></citerefentry> if <option>man.enable</option> is
+                    set.</para></listitem>
+          <listitem><para>This includes the HTML manual and the <command>nixos-help</command> command if
+                    <option>doc.enable</option> is set.</para></listitem>
+          </itemizedlist>
+        '';
+      };
+
     };
 
   };
@@ -86,7 +166,7 @@ let cfg = config.documentation; in
         if [ -w $out/share/info ]; then
           shopt -s nullglob
           for i in $out/share/info/*.info $out/share/info/*.info.gz; do
-              ${pkgs.texinfo}/bin/install-info $i $out/share/info/dir
+              ${pkgs.buildPackages.texinfo}/bin/install-info $i $out/share/info/dir
           done
         fi
       '';
@@ -99,6 +179,21 @@ let cfg = config.documentation; in
       environment.extraOutputsToInstall = [ "doc" ] ++ optional cfg.dev.enable "devdoc";
     })
 
+    (mkIf cfg.nixos.enable {
+      system.build.manual = manual;
+
+      environment.systemPackages = []
+        ++ optional cfg.man.enable manual.manpages
+        ++ optionals cfg.doc.enable ([ manual.manualHTML helpScript ]
+           ++ optionals config.services.xserver.enable [ desktopItem pkgs.nixos-icons ]);
+
+      services.mingetty.helpLine = mkIf cfg.doc.enable (
+          "\nRun `nixos-help` "
+        + optionalString config.services.nixosManual.showManual "or press <Alt-F${toString config.services.nixosManual.ttyNumber}> "
+        + "for the NixOS manual."
+      );
+    })
+
   ]);
 
 }
diff --git a/nixos/modules/misc/ids.nix b/nixos/modules/misc/ids.nix
index 8292cdc995e0..a32e4fe3f7c8 100644
--- a/nixos/modules/misc/ids.nix
+++ b/nixos/modules/misc/ids.nix
@@ -53,7 +53,7 @@
       tomcat = 16;
       #audio = 17; # unused
       #floppy = 18; # unused
-      #uucp = 19; # unused
+      uucp = 19;
       #lp = 20; # unused
       #proc = 21; # unused
       pulseaudio = 22; # must match `pulseaudio' GID
@@ -289,7 +289,7 @@
       stanchion = 262;
       riak-cs = 263;
       infinoted = 264;
-      # keystone = 265; # unused, removed 2017-12-13
+      sickbeard = 265;
       # glance = 266; # unused, removed 2017-12-13
       couchpotato = 267;
       gogs = 268;
@@ -329,6 +329,9 @@
       # kvm = 302; # unused
       # render = 303; # unused
       zeronet = 304;
+      lirc = 305;
+      lidarr = 306;
+      slurm = 307;
 
       # When adding a uid, make sure it doesn't match an existing gid. And don't use uids above 399!
 
@@ -383,7 +386,7 @@
       virtuoso = 44;
       #rtkit = 45; # unused
       dovecot2 = 46;
-      #dovenull = 47; # unused
+      dovenull2 = 47;
       prayer = 49;
       mpd = 50;
       clamav = 51;
@@ -579,7 +582,7 @@
       stanchion = 262;
       riak-cs = 263;
       infinoted = 264;
-      # keystone = 265; # unused, removed 2017-12-13
+      sickbeard = 265;
       # glance = 266; # unused, removed 2017-12-13
       couchpotato = 267;
       gogs = 268;
@@ -618,6 +621,9 @@
       kvm = 302; # default udev rules from systemd requires these
       render = 303; # default udev rules from systemd requires these
       zeronet = 304;
+      lirc = 305;
+      lidarr = 306;
+      slurm = 307;
 
       # When adding a gid, make sure it doesn't match an existing
       # uid. Users and groups with the same name should have equal
diff --git a/nixos/modules/misc/version.nix b/nixos/modules/misc/version.nix
index 63717e0c6a81..6d78b7c593f8 100644
--- a/nixos/modules/misc/version.nix
+++ b/nixos/modules/misc/version.nix
@@ -5,7 +5,6 @@ with lib;
 let
   cfg = config.system.nixos;
 
-  revisionFile = "${toString pkgs.path}/.git-revision";
   gitRepo      = "${toString pkgs.path}/.git";
   gitCommitId  = lib.substring 0 7 (commitIdFromGitRepo gitRepo);
 in
@@ -37,9 +36,7 @@ in
     nixos.revision = mkOption {
       internal = true;
       type = types.str;
-      default = if pathIsDirectory gitRepo then commitIdFromGitRepo gitRepo
-                else if pathExists revisionFile then fileContents revisionFile
-                else "master";
+      default = lib.trivial.revisionWithDefault "master";
       description = "The Git revision from which this NixOS configuration was built.";
     };
 
@@ -84,7 +81,7 @@ in
       versionSuffix = mkIf (pathIsDirectory gitRepo) (mkDefault (".git." + gitCommitId));
 
       # Note: the first letter is bumped on every release.  It's an animal.
-      codeName = "Jellyfish";
+      codeName = "Koi";
     };
 
     # Generate /etc/os-release.  See
diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix
index 4795922abcfb..660644eade8d 100644
--- a/nixos/modules/module-list.nix
+++ b/nixos/modules/module-list.nix
@@ -46,6 +46,7 @@
   ./hardware/opengl.nix
   ./hardware/pcmcia.nix
   ./hardware/raid/hpsa.nix
+  ./hardware/steam-hardware.nix
   ./hardware/usb-wwan.nix
   ./hardware/onlykey.nix
   ./hardware/video/amdgpu.nix
@@ -107,7 +108,6 @@
   ./programs/oblogout.nix
   ./programs/plotinus.nix
   ./programs/qt5ct.nix
-  ./programs/rootston.nix
   ./programs/screen.nix
   ./programs/sedutil.nix
   ./programs/slock.nix
@@ -120,11 +120,13 @@
   ./programs/sysdig.nix
   ./programs/systemtap.nix
   ./programs/sway.nix
+  ./programs/sway-beta.nix
   ./programs/thefuck.nix
   ./programs/tmux.nix
   ./programs/udevil.nix
   ./programs/venus.nix
   ./programs/vim.nix
+  ./programs/wavemon.nix
   ./programs/way-cooler.nix
   ./programs/wireshark.nix
   ./programs/xfs_quota.nix
@@ -148,6 +150,7 @@
   ./security/duosec.nix
   ./security/hidepid.nix
   ./security/lock-kernel-modules.nix
+  ./security/misc.nix
   ./security/oath.nix
   ./security/pam.nix
   ./security/pam_usb.nix
@@ -232,6 +235,7 @@
   ./services/desktops/dleyna-server.nix
   ./services/desktops/flatpak.nix
   ./services/desktops/geoclue2.nix
+  ./services/desktops/gsignond.nix
   ./services/desktops/pipewire.nix
   ./services/desktops/gnome3/at-spi2-core.nix
   ./services/desktops/gnome3/chrome-gnome-shell.nix
@@ -245,6 +249,7 @@
   ./services/desktops/gnome3/gnome-user-share.nix
   ./services/desktops/gnome3/gpaste.nix
   ./services/desktops/gnome3/gvfs.nix
+  ./services/desktops/gnome3/rygel.nix
   ./services/desktops/gnome3/seahorse.nix
   ./services/desktops/gnome3/sushi.nix
   ./services/desktops/gnome3/tracker.nix
@@ -271,15 +276,18 @@
   ./services/hardware/interception-tools.nix
   ./services/hardware/irqbalance.nix
   ./services/hardware/lcd.nix
+  ./services/hardware/lirc.nix
   ./services/hardware/nvidia-optimus.nix
   ./services/hardware/pcscd.nix
   ./services/hardware/pommed.nix
+  ./services/hardware/ratbagd.nix
   ./services/hardware/sane.nix
   ./services/hardware/sane_extra_backends/brscan4.nix
   ./services/hardware/tcsd.nix
   ./services/hardware/tlp.nix
   ./services/hardware/thinkfan.nix
   ./services/hardware/trezord.nix
+  ./services/hardware/triggerhappy.nix
   ./services/hardware/u2f.nix
   ./services/hardware/udev.nix
   ./services/hardware/udisks2.nix
@@ -362,6 +370,7 @@
   ./services/misc/jackett.nix
   ./services/misc/logkeys.nix
   ./services/misc/leaps.nix
+  ./services/misc/lidarr.nix
   ./services/misc/mantisbt.nix
   ./services/misc/mathics.nix
   ./services/misc/matrix-synapse.nix
@@ -392,6 +401,7 @@
   ./services/misc/rogue.nix
   ./services/misc/serviio.nix
   ./services/misc/safeeyes.nix
+  ./services/misc/sickbeard.nix
   ./services/misc/siproxd.nix
   ./services/misc/snapper.nix
   ./services/misc/sonarr.nix
@@ -406,6 +416,7 @@
   ./services/misc/taskserver
   ./services/misc/tzupdate.nix
   ./services/misc/uhub.nix
+  ./services/misc/weechat.nix
   ./services/misc/xmr-stak.nix
   ./services/misc/zookeeper.nix
   ./services/monitoring/apcupsd.nix
@@ -494,6 +505,8 @@
   ./services/networking/dnsdist.nix
   ./services/networking/dnsmasq.nix
   ./services/networking/ejabberd.nix
+  ./services/networking/epmd.nix
+  ./services/networking/eternal-terminal.nix
   ./services/networking/fakeroute.nix
   ./services/networking/ferm.nix
   ./services/networking/firefox/sync-server.nix
@@ -515,9 +528,11 @@
   ./services/networking/heyefi.nix
   ./services/networking/hostapd.nix
   ./services/networking/htpdate.nix
+  ./services/networking/hylafax/default.nix
   ./services/networking/i2pd.nix
   ./services/networking/i2p.nix
   ./services/networking/iodine.nix
+  ./services/networking/iperf3.nix
   ./services/networking/ircd-hybrid/default.nix
   ./services/networking/iwd.nix
   ./services/networking/keepalived/default.nix
@@ -552,6 +567,7 @@
   ./services/networking/nsd.nix
   ./services/networking/ntopng.nix
   ./services/networking/ntpd.nix
+  ./services/networking/nullidentdmod.nix
   ./services/networking/nylon.nix
   ./services/networking/ocserv.nix
   ./services/networking/oidentd.nix
@@ -622,7 +638,7 @@
   ./services/networking/zerobin.nix
   ./services/networking/zeronet.nix
   ./services/networking/zerotierone.nix
-  ./services/networking/znc.nix
+  ./services/networking/znc/default.nix
   ./services/printing/cupsd.nix
   ./services/scheduling/atd.nix
   ./services/scheduling/chronos.nix
@@ -676,8 +692,10 @@
   ./services/web-apps/atlassian/confluence.nix
   ./services/web-apps/atlassian/crowd.nix
   ./services/web-apps/atlassian/jira.nix
+  ./services/web-apps/codimd.nix
   ./services/web-apps/frab.nix
   ./services/web-apps/mattermost.nix
+  ./services/web-apps/nextcloud.nix
   ./services/web-apps/nexus.nix
   ./services/web-apps/pgpkeyserver-lite.nix
   ./services/web-apps/matomo.nix
@@ -721,12 +739,14 @@
   ./services/x11/display-managers/lightdm.nix
   ./services/x11/display-managers/sddm.nix
   ./services/x11/display-managers/slim.nix
+  ./services/x11/display-managers/startx.nix
   ./services/x11/display-managers/xpra.nix
   ./services/x11/fractalart.nix
   ./services/x11/hardware/libinput.nix
   ./services/x11/hardware/multitouch.nix
   ./services/x11/hardware/synaptics.nix
   ./services/x11/hardware/wacom.nix
+  ./services/x11/gdk-pixbuf.nix
   ./services/x11/redshift.nix
   ./services/x11/urxvtd.nix
   ./services/x11/window-managers/awesome.nix
diff --git a/nixos/modules/profiles/base.nix b/nixos/modules/profiles/base.nix
index 5aaffa4f1f2a..7e14b0e21143 100644
--- a/nixos/modules/profiles/base.nix
+++ b/nixos/modules/profiles/base.nix
@@ -7,7 +7,7 @@
   # Include some utilities that are useful for installing or repairing
   # the system.
   environment.systemPackages = [
-    pkgs.w3m-nox # needed for the manual anyway
+    pkgs.w3m-nographics # needed for the manual anyway
     pkgs.testdisk # useful for repairing boot problems
     pkgs.ms-sys # for writing Microsoft boot sectors / MBRs
     pkgs.efibootmgr
@@ -19,6 +19,9 @@
     pkgs.cryptsetup # needed for dm-crypt volumes
     pkgs.mkpasswd # for generating password files
 
+    # Some text editors.
+    pkgs.vim
+
     # Some networking tools.
     pkgs.fuse
     pkgs.fuse3
diff --git a/nixos/modules/profiles/clone-config.nix b/nixos/modules/profiles/clone-config.nix
index 99d4774584f1..3f669ba7d2e1 100644
--- a/nixos/modules/profiles/clone-config.nix
+++ b/nixos/modules/profiles/clone-config.nix
@@ -48,6 +48,8 @@ let
 
       {
         imports = [ ${toString config.installer.cloneConfigIncludes} ];
+
+        ${config.installer.cloneConfigExtra}
       }
     '';
 
@@ -73,6 +75,13 @@ in
       '';
     };
 
+    installer.cloneConfigExtra = mkOption {
+      default = "";
+      description = ''
+        Extra text to include in the cloned configuration.nix included in this
+        installer.
+      '';
+    };
   };
 
   config = {
diff --git a/nixos/modules/profiles/graphical.nix b/nixos/modules/profiles/graphical.nix
index 332cf58aa538..fba756391b11 100644
--- a/nixos/modules/profiles/graphical.nix
+++ b/nixos/modules/profiles/graphical.nix
@@ -7,9 +7,12 @@
   services.xserver = {
     enable = true;
     displayManager.sddm.enable = true;
-    desktopManager.plasma5.enable = true;
+    desktopManager.plasma5 = {
+      enable = true;
+      enableQt4Support = false;
+    };
     libinput.enable = true; # for touchpad support on many laptops
   };
 
-  environment.systemPackages = [ pkgs.glxinfo ];
+  environment.systemPackages = [ pkgs.glxinfo pkgs.firefox ];
 }
diff --git a/nixos/modules/profiles/hardened.nix b/nixos/modules/profiles/hardened.nix
index 2af8bf1f8e30..d712fb2514b1 100644
--- a/nixos/modules/profiles/hardened.nix
+++ b/nixos/modules/profiles/hardened.nix
@@ -6,12 +6,18 @@
 with lib;
 
 {
+  meta = {
+    maintainers = [ maintainers.joachifm ];
+  };
+
   boot.kernelPackages = mkDefault pkgs.linuxPackages_hardened;
 
   security.hideProcessInformation = mkDefault true;
 
   security.lockKernelModules = mkDefault true;
 
+  security.allowUserNamespaces = mkDefault false;
+
   security.apparmor.enable = mkDefault true;
 
   boot.kernelParams = [
@@ -55,18 +61,6 @@ with lib;
   # ... or at least apply some hardening to it
   boot.kernel.sysctl."net.core.bpf_jit_harden" = mkDefault true;
 
-  # A recurring problem with user namespaces is that there are
-  # still code paths where the kernel's permission checking logic
-  # fails to account for namespacing, instead permitting a
-  # namespaced process to act outside the namespace with the
-  # same privileges as it would have inside it.  This is particularly
-  # bad in the common case of running as root within the namespace.
-  #
-  # Setting the number of allowed user namespaces to 0 effectively disables
-  # the feature at runtime.  Attempting to create a user namespace
-  # with unshare will then fail with "no space left on device".
-  boot.kernel.sysctl."user.max_user_namespaces" = mkDefault 0;
-
   # Raise ASLR entropy for 64bit & 32bit, respectively.
   #
   # Note: mmap_rnd_compat_bits may not exist on 64bit.
diff --git a/nixos/modules/profiles/installation-device.nix b/nixos/modules/profiles/installation-device.nix
index ff4a23a18d06..580ea4a58e5b 100644
--- a/nixos/modules/profiles/installation-device.nix
+++ b/nixos/modules/profiles/installation-device.nix
@@ -22,9 +22,10 @@ with lib;
   config = {
 
     # Enable in installer, even if the minimal profile disables it.
-    services.nixosManual.enable = mkForce true;
+    documentation.enable = mkForce true;
 
     # Show the manual.
+    documentation.nixos.enable = mkForce true;
     services.nixosManual.showManual = true;
 
     # Let the user play Rogue on TTY 8 during the installation.
@@ -62,7 +63,7 @@ with lib;
     # Tell the Nix evaluator to garbage collect more aggressively.
     # This is desirable in memory-constrained environments that don't
     # (yet) have swap set up.
-    environment.variables.GC_INITIAL_HEAP_SIZE = "100000";
+    environment.variables.GC_INITIAL_HEAP_SIZE = "1M";
 
     # Make the installer more likely to succeed in low memory
     # environments.  The kernel's overcommit heustistics bite us
@@ -86,9 +87,6 @@ with lib;
     # console less cumbersome if the machine has a public IP.
     networking.firewall.logRefusedConnections = mkDefault false;
 
-    environment.systemPackages = [ pkgs.vim ];
-
-
     # Allow the user to log in as root without a password.
     users.users.root.initialHashedPassword = "";
   };
diff --git a/nixos/modules/profiles/minimal.nix b/nixos/modules/profiles/minimal.nix
index ed04e46c77d1..138eda117c74 100644
--- a/nixos/modules/profiles/minimal.nix
+++ b/nixos/modules/profiles/minimal.nix
@@ -12,7 +12,6 @@ with lib;
   i18n.supportedLocales = [ (config.i18n.defaultLocale + "/UTF-8") ];
 
   documentation.enable = mkDefault false;
-  services.nixosManual.enable = mkDefault false;
 
   sound.enable = mkDefault false;
 }
diff --git a/nixos/modules/programs/bash/bash.nix b/nixos/modules/programs/bash/bash.nix
index 69a1a482d074..0fbc77ea44cf 100644
--- a/nixos/modules/programs/bash/bash.nix
+++ b/nixos/modules/programs/bash/bash.nix
@@ -33,7 +33,8 @@ let
   '';
 
   bashAliases = concatStringsSep "\n" (
-    mapAttrsFlatten (k: v: "alias ${k}='${v}'") cfg.shellAliases
+    mapAttrsFlatten (k: v: "alias ${k}=${escapeShellArg v}")
+      (filterAttrs (k: v: !isNull v) cfg.shellAliases)
   );
 
 in
@@ -59,12 +60,12 @@ in
       */
 
       shellAliases = mkOption {
-        default = config.environment.shellAliases;
+        default = {};
         description = ''
-          Set of aliases for bash shell. See <option>environment.shellAliases</option>
-          for an option format description.
+          Set of aliases for bash shell, which overrides <option>environment.shellAliases</option>.
+          See <option>environment.shellAliases</option> for an option format description.
         '';
-        type = types.attrs; # types.attrsOf types.stringOrPath;
+        type = with types; attrsOf (nullOr (either str path));
       };
 
       shellInit = mkOption {
@@ -125,8 +126,12 @@ in
 
     programs.bash = {
 
+      shellAliases = mapAttrs (name: mkDefault) cfge.shellAliases;
+
       shellInit = ''
-        ${config.system.build.setEnvironment.text}
+        if [ -z "$__NIXOS_SET_ENVIRONMENT_DONE" ]; then
+            . ${config.system.build.setEnvironment}
+        fi
 
         ${cfge.shellInit}
       '';
@@ -166,11 +171,11 @@ in
 
         # Read system-wide modifications.
         if test -f /etc/profile.local; then
-          . /etc/profile.local
+            . /etc/profile.local
         fi
 
         if [ -n "''${BASH_VERSION:-}" ]; then
-          . /etc/bashrc
+            . /etc/bashrc
         fi
       '';
 
@@ -191,12 +196,12 @@ in
 
         # We are not always an interactive shell.
         if [ -n "$PS1" ]; then
-          ${cfg.interactiveShellInit}
+            ${cfg.interactiveShellInit}
         fi
 
         # Read system-wide modifications.
         if test -f /etc/bashrc.local; then
-          . /etc/bashrc.local
+            . /etc/bashrc.local
         fi
       '';
 
diff --git a/nixos/modules/programs/dconf.nix b/nixos/modules/programs/dconf.nix
index b7d8a345e65c..9c9765b06b6f 100644
--- a/nixos/modules/programs/dconf.nix
+++ b/nixos/modules/programs/dconf.nix
@@ -32,6 +32,8 @@ in
     environment.etc = optionals (cfg.profiles != {})
       (mapAttrsToList mkDconfProfile cfg.profiles);
 
+    services.dbus.packages = [ pkgs.gnome3.dconf ];
+
     environment.variables.GIO_EXTRA_MODULES = optional cfg.enable
       "${pkgs.gnome3.dconf.lib}/lib/gio/modules";
     # https://github.com/NixOS/nixpkgs/pull/31891
diff --git a/nixos/modules/programs/digitalbitbox/doc.xml b/nixos/modules/programs/digitalbitbox/doc.xml
index a26653dda535..c63201628dbd 100644
--- a/nixos/modules/programs/digitalbitbox/doc.xml
+++ b/nixos/modules/programs/digitalbitbox/doc.xml
@@ -3,75 +3,64 @@
          xmlns:xi="http://www.w3.org/2001/XInclude"
          version="5.0"
          xml:id="module-programs-digitalbitbox">
-
-  <title>Digital Bitbox</title>
-
-  <para>
-    Digital Bitbox is a hardware wallet and second-factor authenticator.
-  </para>
-
-  <para>
-    The <literal>digitalbitbox</literal> programs module may be
-    installed by setting <literal>programs.digitalbitbox</literal>
-    to <literal>true</literal> in a manner similar to
-
+ <title>Digital Bitbox</title>
+ <para>
+  Digital Bitbox is a hardware wallet and second-factor authenticator.
+ </para>
+ <para>
+  The <literal>digitalbitbox</literal> programs module may be installed by
+  setting <literal>programs.digitalbitbox</literal> to <literal>true</literal>
+  in a manner similar to
 <programlisting>
 <xref linkend="opt-programs.digitalbitbox.enable"/> = true;
 </programlisting>
-
-    and bundles the <literal>digitalbitbox</literal> package (see <xref
+  and bundles the <literal>digitalbitbox</literal> package (see
+  <xref
       linkend="sec-digitalbitbox-package" />), which contains the
-    <literal>dbb-app</literal> and <literal>dbb-cli</literal> binaries,
-    along with the hardware module (see <xref
+  <literal>dbb-app</literal> and <literal>dbb-cli</literal> binaries, along
+  with the hardware module (see
+  <xref
       linkend="sec-digitalbitbox-hardware-module" />) which sets up the
-    necessary udev rules to access the device.
-  </para>
-
-  <para>
-    Enabling the digitalbitbox module is pretty much the easiest way to
-    get a Digital Bitbox device working on your system.
-  </para>
+  necessary udev rules to access the device.
+ </para>
+ <para>
+  Enabling the digitalbitbox module is pretty much the easiest way to get a
+  Digital Bitbox device working on your system.
+ </para>
+ <para>
+  For more information, see
+  <link xlink:href="https://digitalbitbox.com/start_linux" />.
+ </para>
+ <section xml:id="sec-digitalbitbox-package">
+  <title>Package</title>
 
   <para>
-    For more information, see
-    <link xlink:href="https://digitalbitbox.com/start_linux" />.
-  </para>
-
-  <section xml:id="sec-digitalbitbox-package">
-    <title>Package</title>
-
-    <para>
-      The binaries, <literal>dbb-app</literal> (a GUI tool) and
-      <literal>dbb-cli</literal> (a CLI tool), are available through the
-      <literal>digitalbitbox</literal> package which could be installed
-      as follows:
-
+   The binaries, <literal>dbb-app</literal> (a GUI tool) and
+   <literal>dbb-cli</literal> (a CLI tool), are available through the
+   <literal>digitalbitbox</literal> package which could be installed as
+   follows:
 <programlisting>
 <xref linkend="opt-environment.systemPackages"/> = [
   pkgs.digitalbitbox
 ];
 </programlisting>
-    </para>
-  </section>
-
-
-  <section xml:id="sec-digitalbitbox-hardware-module">
-    <title>Hardware</title>
-
-    <para>
-      The digitalbitbox hardware package enables the udev rules for
-      Digital Bitbox devices and may be installed as follows:
+  </para>
+ </section>
+ <section xml:id="sec-digitalbitbox-hardware-module">
+  <title>Hardware</title>
 
+  <para>
+   The digitalbitbox hardware package enables the udev rules for Digital Bitbox
+   devices and may be installed as follows:
 <programlisting>
 <xref linkend="opt-hardware.digitalbitbox.enable"/> = true;
 </programlisting>
-    </para>
-
-    <para>
-      In order to alter the udev rules, one may provide different values for
-      the <literal>udevRule51</literal> and <literal>udevRule52</literal>
-      attributes by means of overriding as follows:
+  </para>
 
+  <para>
+   In order to alter the udev rules, one may provide different values for the
+   <literal>udevRule51</literal> and <literal>udevRule52</literal> attributes
+   by means of overriding as follows:
 <programlisting>
 programs.digitalbitbox = {
   <link linkend="opt-programs.digitalbitbox.enable">enable</link> = true;
@@ -80,6 +69,6 @@ programs.digitalbitbox = {
   };
 };
 </programlisting>
-    </para>
-  </section>
+  </para>
+ </section>
 </chapter>
diff --git a/nixos/modules/programs/fish.nix b/nixos/modules/programs/fish.nix
index c8d94a47be28..b38af07b92c3 100644
--- a/nixos/modules/programs/fish.nix
+++ b/nixos/modules/programs/fish.nix
@@ -9,7 +9,8 @@ let
   cfg = config.programs.fish;
 
   fishAliases = concatStringsSep "\n" (
-    mapAttrsFlatten (k: v: "alias ${k} '${v}'") cfg.shellAliases
+    mapAttrsFlatten (k: v: "alias ${k} ${escapeShellArg v}")
+      (filterAttrs (k: v: !isNull v) cfg.shellAliases)
   );
 
 in
@@ -27,7 +28,7 @@ in
         '';
         type = types.bool;
       };
-      
+
       vendor.config.enable = mkOption {
         type = types.bool;
         default = true;
@@ -43,7 +44,7 @@ in
           Whether fish should use completion files provided by other packages.
         '';
       };
-      
+
       vendor.functions.enable = mkOption {
         type = types.bool;
         default = true;
@@ -53,12 +54,12 @@ in
       };
 
       shellAliases = mkOption {
-        default = config.environment.shellAliases;
+        default = {};
         description = ''
-          Set of aliases for fish shell. See <option>environment.shellAliases</option>
-          for an option format description.
+          Set of aliases for fish shell, which overrides <option>environment.shellAliases</option>.
+          See <option>environment.shellAliases</option> for an option format description.
         '';
-        type = types.attrs;
+        type = with types; attrsOf (nullOr (either str path));
       };
 
       shellInit = mkOption {
@@ -99,6 +100,8 @@ in
 
   config = mkIf cfg.enable {
 
+    programs.fish.shellAliases = mapAttrs (name: mkDefault) cfge.shellAliases;
+
     environment.etc."fish/foreign-env/shellInit".text = cfge.shellInit;
     environment.etc."fish/foreign-env/loginShellInit".text = cfge.loginShellInit;
     environment.etc."fish/foreign-env/interactiveShellInit".text = cfge.interactiveShellInit;
@@ -107,9 +110,11 @@ in
       # This happens before $__fish_datadir/config.fish sets fish_function_path, so it is currently
       # unset. We set it and then completely erase it, leaving its configuration to $__fish_datadir/config.fish
       set fish_function_path ${pkgs.fish-foreign-env}/share/fish-foreign-env/functions $__fish_datadir/functions
-      
+
       # source the NixOS environment config
-      fenv source ${config.system.build.setEnvironment}
+      if [ -z "$__NIXOS_SET_ENVIRONMENT_DONE" ]
+          fenv source ${config.system.build.setEnvironment}
+      end
 
       # clear fish_function_path so that it will be correctly set when we return to $__fish_datadir/config.fish
       set -e fish_function_path
@@ -123,7 +128,7 @@ in
         set fish_function_path ${pkgs.fish-foreign-env}/share/fish-foreign-env/functions $fish_function_path
         fenv source /etc/fish/foreign-env/shellInit > /dev/null
         set -e fish_function_path[1]
-        
+
         ${cfg.shellInit}
 
         # and leave a note so we don't source this config section again from
@@ -137,7 +142,7 @@ in
         set fish_function_path ${pkgs.fish-foreign-env}/share/fish-foreign-env/functions $fish_function_path
         fenv source /etc/fish/foreign-env/loginShellInit > /dev/null
         set -e fish_function_path[1]
-        
+
         ${cfg.loginShellInit}
 
         # and leave a note so we don't source this config section again from
@@ -149,12 +154,11 @@ in
       status --is-interactive; and not set -q __fish_nixos_interactive_config_sourced
       and begin
         ${fishAliases}
-        
 
         set fish_function_path ${pkgs.fish-foreign-env}/share/fish-foreign-env/functions $fish_function_path
         fenv source /etc/fish/foreign-env/interactiveShellInit > /dev/null
         set -e fish_function_path[1]
-        
+
         ${cfg.promptInit}
         ${cfg.interactiveShellInit}
 
@@ -170,7 +174,7 @@ in
       ++ optional cfg.vendor.config.enable "/share/fish/vendor_conf.d"
       ++ optional cfg.vendor.completions.enable "/share/fish/vendor_completions.d"
       ++ optional cfg.vendor.functions.enable "/share/fish/vendor_functions.d";
-    
+
     environment.systemPackages = [ pkgs.fish ];
 
     environment.shells = [
diff --git a/nixos/modules/programs/light.nix b/nixos/modules/programs/light.nix
index 6f8c389acc97..9f2a03e7e763 100644
--- a/nixos/modules/programs/light.nix
+++ b/nixos/modules/programs/light.nix
@@ -13,7 +13,8 @@ in
         default = false;
         type = types.bool;
         description = ''
-          Whether to install Light backlight control with setuid wrapper.
+          Whether to install Light backlight control command
+          and udev rules granting access to members of the "video" group.
         '';
       };
     };
@@ -21,6 +22,6 @@ in
 
   config = mkIf cfg.enable {
     environment.systemPackages = [ pkgs.light ];
-    security.wrappers.light.source = "${pkgs.light.out}/bin/light";
+    services.udev.packages = [ pkgs.light ];
   };
 }
diff --git a/nixos/modules/programs/plotinus.xml b/nixos/modules/programs/plotinus.xml
index 91740ee16ec2..902cd89e0c49 100644
--- a/nixos/modules/programs/plotinus.xml
+++ b/nixos/modules/programs/plotinus.xml
@@ -3,23 +3,28 @@
          xmlns:xi="http://www.w3.org/2001/XInclude"
          version="5.0"
          xml:id="module-program-plotinus">
-
-<title>Plotinus</title>
-
-<para><emphasis>Source:</emphasis> <filename>modules/programs/plotinus.nix</filename></para>
-
-<para><emphasis>Upstream documentation:</emphasis> <link xlink:href="https://github.com/p-e-w/plotinus"/></para>
-
-<para>Plotinus is a searchable command palette in every modern GTK+ application.</para>
-
-<para>When in a GTK+3 application and Plotinus is enabled, you can press <literal>Ctrl+Shift+P</literal> to open the command palette.  The command palette provides a searchable list of of all menu items in the application.</para>
-
-<para>To enable Plotinus, add the following to your <filename>configuration.nix</filename>:
-
+ <title>Plotinus</title>
+ <para>
+  <emphasis>Source:</emphasis>
+  <filename>modules/programs/plotinus.nix</filename>
+ </para>
+ <para>
+  <emphasis>Upstream documentation:</emphasis>
+  <link xlink:href="https://github.com/p-e-w/plotinus"/>
+ </para>
+ <para>
+  Plotinus is a searchable command palette in every modern GTK+ application.
+ </para>
+ <para>
+  When in a GTK+3 application and Plotinus is enabled, you can press
+  <literal>Ctrl+Shift+P</literal> to open the command palette. The command
+  palette provides a searchable list of of all menu items in the application.
+ </para>
+ <para>
+  To enable Plotinus, add the following to your
+  <filename>configuration.nix</filename>:
 <programlisting>
 <xref linkend="opt-programs.plotinus.enable"/> = true;
 </programlisting>
-
-</para>
-
+ </para>
 </chapter>
diff --git a/nixos/modules/programs/rootston.nix b/nixos/modules/programs/rootston.nix
deleted file mode 100644
index 842d9e6cfb48..000000000000
--- a/nixos/modules/programs/rootston.nix
+++ /dev/null
@@ -1,103 +0,0 @@
-{ config, pkgs, lib, ... }:
-
-with lib;
-
-let
-  cfg = config.programs.rootston;
-
-  rootstonWrapped = pkgs.writeScriptBin "rootston" ''
-    #! ${pkgs.runtimeShell}
-    if [[ "$#" -ge 1 ]]; then
-      exec ${pkgs.rootston}/bin/rootston "$@"
-    else
-      ${cfg.extraSessionCommands}
-      exec ${pkgs.rootston}/bin/rootston -C ${cfg.configFile}
-    fi
-  '';
-in {
-  options.programs.rootston = {
-    enable = mkEnableOption ''
-      rootston, the reference compositor for wlroots. The purpose of rootston
-      is to test and demonstrate the features of wlroots (if you want a real
-      Wayland compositor you should e.g. use Sway instead). You can manually
-      start the compositor by running "rootston" from a terminal'';
-
-    extraSessionCommands = mkOption {
-      type = types.lines;
-      default = "";
-      example = ''
-        # Define a keymap (US QWERTY is the default)
-        export XKB_DEFAULT_LAYOUT=de,us
-        export XKB_DEFAULT_VARIANT=nodeadkeys
-        export XKB_DEFAULT_OPTIONS=grp:alt_shift_toggle,caps:escape
-      '';
-      description = ''
-        Shell commands executed just before rootston is started.
-      '';
-    };
-
-    extraPackages = mkOption {
-      type = with types; listOf package;
-      default = with pkgs; [
-        westonLite xwayland rofi
-      ];
-      defaultText = literalExample ''
-        with pkgs; [
-          westonLite xwayland rofi
-        ]
-      '';
-      example = literalExample "[ ]";
-      description = ''
-        Extra packages to be installed system wide.
-      '';
-    };
-
-    config = mkOption {
-      type = types.str;
-      default = ''
-        [keyboard]
-        meta-key = Logo
-
-        # Sway/i3 like Keybindings
-        # Maps key combinations with commands to execute
-        # Commands include:
-        # - "exit" to stop the compositor
-        # - "exec" to execute a shell command
-        # - "close" to close the current view
-        # - "next_window" to cycle through windows
-        [bindings]
-        Logo+Shift+e = exit
-        Logo+q = close
-        Logo+m = maximize
-        Alt+Tab = next_window
-        Logo+Return = exec weston-terminal
-        Logo+d = exec rofi -show run
-      '';
-      description = ''
-        Default configuration for rootston (used when called without any
-        parameters).
-      '';
-    };
-
-    configFile = mkOption {
-      type = types.path;
-      default = "/etc/rootston.ini";
-      example = literalExample "${pkgs.rootston}/etc/rootston.ini";
-      description = ''
-        Path to the default rootston configuration file (the "config" option
-        will have no effect if you change the path).
-      '';
-    };
-  };
-
-  config = mkIf cfg.enable {
-    environment.etc."rootston.ini".text = cfg.config;
-    environment.systemPackages = [ rootstonWrapped ] ++ cfg.extraPackages;
-
-    hardware.opengl.enable = mkDefault true;
-    fonts.enableDefaultFonts = mkDefault true;
-    programs.dconf.enable = mkDefault true;
-  };
-
-  meta.maintainers = with lib.maintainers; [ primeos gnidorah ];
-}
diff --git a/nixos/modules/programs/shell.nix b/nixos/modules/programs/shell.nix
index 944a8bdf16fc..9842e2bef643 100644
--- a/nixos/modules/programs/shell.nix
+++ b/nixos/modules/programs/shell.nix
@@ -8,18 +8,12 @@ with lib;
 
   config = {
 
-    environment.shellAliases =
-      { ls = "ls --color=tty";
-        ll = "ls -l";
-        l  = "ls -alh";
-      };
-
     environment.shellInit =
       ''
         # Set up the per-user profile.
         mkdir -m 0755 -p "$NIX_USER_PROFILE_DIR"
         if [ "$(stat --printf '%u' "$NIX_USER_PROFILE_DIR")" != "$(id -u)" ]; then
-            echo "WARNING: bad ownership on $NIX_USER_PROFILE_DIR, should be $(id -u)" >&2
+            echo "WARNING: the per-user profile dir $NIX_USER_PROFILE_DIR should belong to user id $(id -u)" >&2
         fi
 
         if [ -w "$HOME" ]; then
@@ -41,7 +35,7 @@ with lib;
           NIX_USER_GCROOTS_DIR="/nix/var/nix/gcroots/per-user/$USER"
           mkdir -m 0755 -p "$NIX_USER_GCROOTS_DIR"
           if [ "$(stat --printf '%u' "$NIX_USER_GCROOTS_DIR")" != "$(id -u)" ]; then
-              echo "WARNING: bad ownership on $NIX_USER_GCROOTS_DIR, should be $(id -u)" >&2
+              echo "WARNING: the per-user gcroots dir $NIX_USER_GCROOTS_DIR should belong to user id $(id -u)" >&2
           fi
 
           # Set up a default Nix expression from which to install stuff.
diff --git a/nixos/modules/programs/sway-beta.nix b/nixos/modules/programs/sway-beta.nix
new file mode 100644
index 000000000000..04f2e0662b86
--- /dev/null
+++ b/nixos/modules/programs/sway-beta.nix
@@ -0,0 +1,54 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+  cfg = config.programs.sway-beta;
+  swayPackage = cfg.package;
+in {
+  options.programs.sway-beta = {
+    enable = mkEnableOption ''
+      Sway, the i3-compatible tiling Wayland compositor. This module will be removed after the final release of Sway 1.0
+    '';
+
+    package = mkOption {
+      type = types.package;
+      default = pkgs.sway-beta;
+      defaultText = "pkgs.sway-beta";
+      description = ''
+        The package to be used for `sway`.
+      '';
+    };
+
+    extraPackages = mkOption {
+      type = with types; listOf package;
+      default = with pkgs; [
+        xwayland dmenu
+      ];
+      defaultText = literalExample ''
+        with pkgs; [ xwayland dmenu ];
+      '';
+      example = literalExample ''
+        with pkgs; [
+          xwayland
+          i3status i3status-rust
+          termite rofi light
+        ]
+      '';
+      description = ''
+        Extra packages to be installed system wide.
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+    environment.systemPackages = [ swayPackage ] ++ cfg.extraPackages;
+    security.pam.services.swaylock = {};
+    hardware.opengl.enable = mkDefault true;
+    fonts.enableDefaultFonts = mkDefault true;
+    programs.dconf.enable = mkDefault true;
+  };
+
+  meta.maintainers = with lib.maintainers; [ gnidorah primeos colemickens ];
+}
+
diff --git a/nixos/modules/programs/thefuck.nix b/nixos/modules/programs/thefuck.nix
index f4ae52934760..21ed6603c1bd 100644
--- a/nixos/modules/programs/thefuck.nix
+++ b/nixos/modules/programs/thefuck.nix
@@ -29,8 +29,8 @@ in
 
     config = mkIf cfg.enable {
       environment.systemPackages = with pkgs; [ thefuck ];
-      environment.shellInit = initScript;
 
+      programs.bash.interactiveShellInit = initScript;
       programs.zsh.interactiveShellInit = mkIf prg.zsh.enable initScript;
       programs.fish.interactiveShellInit = mkIf prg.fish.enable ''
         ${pkgs.thefuck}/bin/thefuck --alias | source
diff --git a/nixos/modules/programs/wavemon.nix b/nixos/modules/programs/wavemon.nix
new file mode 100644
index 000000000000..ac665fe4a023
--- /dev/null
+++ b/nixos/modules/programs/wavemon.nix
@@ -0,0 +1,28 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.programs.wavemon;
+in {
+  options = {
+    programs.wavemon = {
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to add wavemon to the global environment and configure a
+          setcap wrapper for it.
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    environment.systemPackages = with pkgs; [ wavemon ];
+    security.wrappers.wavemon = {
+      source = "${pkgs.wavemon}/bin/wavemon";
+      capabilities = "cap_net_admin+ep";
+    };
+  };
+}
diff --git a/nixos/modules/programs/yabar.nix b/nixos/modules/programs/yabar.nix
index a01083c3ace9..db085211366e 100644
--- a/nixos/modules/programs/yabar.nix
+++ b/nixos/modules/programs/yabar.nix
@@ -44,10 +44,23 @@ in
       enable = mkEnableOption "yabar";
 
       package = mkOption {
-        default = pkgs.yabar;
-        example = literalExample "pkgs.yabar-unstable";
+        default = pkgs.yabar-unstable;
+        example = literalExample "pkgs.yabar";
         type = types.package;
 
+        # `yabar-stable` segfaults under certain conditions.
+        apply = x: if x == pkgs.yabar-unstable then x else flip warn x ''
+          It's not recommended to use `yabar' with `programs.yabar', the (old) stable release
+          tends to segfault under certain circumstances:
+
+          * https://github.com/geommer/yabar/issues/86
+          * https://github.com/geommer/yabar/issues/68
+          * https://github.com/geommer/yabar/issues/143
+
+          Most of them don't occur on master anymore, until a new release is published, it's recommended
+          to use `yabar-unstable'.
+        '';
+
         description = ''
           The package which contains the `yabar` binary.
 
diff --git a/nixos/modules/programs/zsh/oh-my-zsh.xml b/nixos/modules/programs/zsh/oh-my-zsh.xml
index 6567d4a42fac..5cf690c1a560 100644
--- a/nixos/modules/programs/zsh/oh-my-zsh.xml
+++ b/nixos/modules/programs/zsh/oh-my-zsh.xml
@@ -3,18 +3,20 @@
          xmlns:xi="http://www.w3.org/2001/XInclude"
          version="5.0"
          xml:id="module-programs-zsh-ohmyzsh">
-
-<title>Oh my ZSH</title>
-
-<para><literal><link xlink:href="https://ohmyz.sh/">oh-my-zsh</link></literal> is a framework
-to manage your <link xlink:href="https://www.zsh.org/">ZSH</link> configuration
-including completion scripts for several CLI tools or custom prompt themes.</para>
-
-<section xml:id="module-programs-oh-my-zsh-usage"><title>Basic usage</title>
-<para>The module uses the <literal>oh-my-zsh</literal> package with all available features.  The
-initial setup using Nix expressions is fairly similar to the configuration format
-of <literal>oh-my-zsh</literal>.
-
+ <title>Oh my ZSH</title>
+ <para>
+  <literal><link xlink:href="https://ohmyz.sh/">oh-my-zsh</link></literal> is a
+  framework to manage your <link xlink:href="https://www.zsh.org/">ZSH</link>
+  configuration including completion scripts for several CLI tools or custom
+  prompt themes.
+ </para>
+ <section xml:id="module-programs-oh-my-zsh-usage">
+  <title>Basic usage</title>
+
+  <para>
+   The module uses the <literal>oh-my-zsh</literal> package with all available
+   features. The initial setup using Nix expressions is fairly similar to the
+   configuration format of <literal>oh-my-zsh</literal>.
 <programlisting>
 {
   programs.ohMyZsh = {
@@ -24,39 +26,50 @@ of <literal>oh-my-zsh</literal>.
   };
 }
 </programlisting>
-
-For a detailed explanation of these arguments please refer to the
-<link xlink:href="https://github.com/robbyrussell/oh-my-zsh/wiki"><literal>oh-my-zsh</literal> docs</link>.
-</para>
-<para>The expression generates the needed
-configuration and writes it into your <literal>/etc/zshrc</literal>.
-</para></section>
-
-<section xml:id="module-programs-oh-my-zsh-additions"><title>Custom additions</title>
-
-<para>Sometimes third-party or custom scripts such as a modified theme may be needed.
-<literal>oh-my-zsh</literal> provides the
-<link xlink:href="https://github.com/robbyrussell/oh-my-zsh/wiki/Customization#overriding-internals"><literal>ZSH_CUSTOM</literal></link> 
-environment variable for this which points to a directory with additional scripts.</para>
-
-<para>The module can do this as well:
-
+   For a detailed explanation of these arguments please refer to the
+   <link xlink:href="https://github.com/robbyrussell/oh-my-zsh/wiki"><literal>oh-my-zsh</literal>
+   docs</link>.
+  </para>
+
+  <para>
+   The expression generates the needed configuration and writes it into your
+   <literal>/etc/zshrc</literal>.
+  </para>
+ </section>
+ <section xml:id="module-programs-oh-my-zsh-additions">
+  <title>Custom additions</title>
+
+  <para>
+   Sometimes third-party or custom scripts such as a modified theme may be
+   needed. <literal>oh-my-zsh</literal> provides the
+   <link xlink:href="https://github.com/robbyrussell/oh-my-zsh/wiki/Customization#overriding-internals"><literal>ZSH_CUSTOM</literal></link>
+   environment variable for this which points to a directory with additional
+   scripts.
+  </para>
+
+  <para>
+   The module can do this as well:
 <programlisting>
 {
   programs.ohMyZsh.custom = "~/path/to/custom/scripts";
 }
 </programlisting>
-</para></section>
-
-<section xml:id="module-programs-oh-my-zsh-environments"><title>Custom environments</title>
-
-<para>There are several extensions for <literal>oh-my-zsh</literal> packaged in <literal>nixpkgs</literal>.
-One of them is <link xlink:href="https://github.com/spwhitt/nix-zsh-completions">nix-zsh-completions</link>
-which bundles completion scripts and a plugin for <literal>oh-my-zsh</literal>.</para>
-
-<para>Rather than using a single mutable path for <literal>ZSH_CUSTOM</literal>, it's also possible to
-generate this path from a list of Nix packages:
-
+  </para>
+ </section>
+ <section xml:id="module-programs-oh-my-zsh-environments">
+  <title>Custom environments</title>
+
+  <para>
+   There are several extensions for <literal>oh-my-zsh</literal> packaged in
+   <literal>nixpkgs</literal>. One of them is
+   <link xlink:href="https://github.com/spwhitt/nix-zsh-completions">nix-zsh-completions</link>
+   which bundles completion scripts and a plugin for
+   <literal>oh-my-zsh</literal>.
+  </para>
+
+  <para>
+   Rather than using a single mutable path for <literal>ZSH_CUSTOM</literal>,
+   it's also possible to generate this path from a list of Nix packages:
 <programlisting>
 { pkgs, ... }:
 {
@@ -66,42 +79,59 @@ generate this path from a list of Nix packages:
   ];
 }
 </programlisting>
-
-Internally a single store path will be created using <literal>buildEnv</literal>.
-Please refer to the docs of
-<link xlink:href="https://nixos.org/nixpkgs/manual/#sec-building-environment"><literal>buildEnv</literal></link>
-for further reference.</para>
-
-<para><emphasis>Please keep in mind that this is not compatible with <literal>programs.ohMyZsh.custom</literal>
-as it requires an immutable store path while <literal>custom</literal> shall remain mutable! An evaluation failure
-will be thrown if both <literal>custom</literal> and <literal>customPkgs</literal> are set.</emphasis>
-</para></section>
-
-<section xml:id="module-programs-oh-my-zsh-packaging-customizations"><title>Package your own customizations</title>
-
-<para>If third-party customizations (e.g. new themes) are supposed to be added to <literal>oh-my-zsh</literal>
-there are several pitfalls to keep in mind:</para>
-
-<itemizedlist>
-  <listitem>
-    <para>To comply with the default structure of <literal>ZSH</literal> the entire output needs to be written to
-    <literal>$out/share/zsh.</literal></para>
-  </listitem>
-  <listitem>
-    <para>Completion scripts are supposed to be stored at <literal>$out/share/zsh/site-functions</literal>. This directory
-    is part of the <literal><link xlink:href="http://zsh.sourceforge.net/Doc/Release/Functions.html">fpath</link></literal>
-    and the package should be compatible with pure <literal>ZSH</literal> setups. The module will automatically link
-    the contents of <literal>site-functions</literal> to completions directory in the proper store path.</para>
-  </listitem>
-  <listitem>
-    <para>The <literal>plugins</literal> directory needs the structure <literal>pluginname/pluginname.plugin.zsh</literal>
-    as structured in the <link xlink:href="https://github.com/robbyrussell/oh-my-zsh/tree/91b771914bc7c43dd7c7a43b586c5de2c225ceb7/plugins">upstream repo.</link>
+   Internally a single store path will be created using
+   <literal>buildEnv</literal>. Please refer to the docs of
+   <link xlink:href="https://nixos.org/nixpkgs/manual/#sec-building-environment"><literal>buildEnv</literal></link>
+   for further reference.
+  </para>
+
+  <para>
+   <emphasis>Please keep in mind that this is not compatible with
+   <literal>programs.ohMyZsh.custom</literal> as it requires an immutable store
+   path while <literal>custom</literal> shall remain mutable! An evaluation
+   failure will be thrown if both <literal>custom</literal> and
+   <literal>customPkgs</literal> are set.</emphasis>
+  </para>
+ </section>
+ <section xml:id="module-programs-oh-my-zsh-packaging-customizations">
+  <title>Package your own customizations</title>
+
+  <para>
+   If third-party customizations (e.g. new themes) are supposed to be added to
+   <literal>oh-my-zsh</literal> there are several pitfalls to keep in mind:
+  </para>
+
+  <itemizedlist>
+   <listitem>
+    <para>
+     To comply with the default structure of <literal>ZSH</literal> the entire
+     output needs to be written to <literal>$out/share/zsh.</literal>
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     Completion scripts are supposed to be stored at
+     <literal>$out/share/zsh/site-functions</literal>. This directory is part
+     of the
+     <literal><link xlink:href="http://zsh.sourceforge.net/Doc/Release/Functions.html">fpath</link></literal>
+     and the package should be compatible with pure <literal>ZSH</literal>
+     setups. The module will automatically link the contents of
+     <literal>site-functions</literal> to completions directory in the proper
+     store path.
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     The <literal>plugins</literal> directory needs the structure
+     <literal>pluginname/pluginname.plugin.zsh</literal> as structured in the
+     <link xlink:href="https://github.com/robbyrussell/oh-my-zsh/tree/91b771914bc7c43dd7c7a43b586c5de2c225ceb7/plugins">upstream
+     repo.</link>
     </para>
-  </listitem>
-</itemizedlist>
+   </listitem>
+  </itemizedlist>
 
-<para>
-A derivation for <literal>oh-my-zsh</literal> may look like this:
+  <para>
+   A derivation for <literal>oh-my-zsh</literal> may look like this:
 <programlisting>
 { stdenv, fetchFromGitHub }:
 
@@ -120,6 +150,6 @@ stdenv.mkDerivation rec {
   '';
 }
 </programlisting>
-</para>
-</section>
+  </para>
+ </section>
 </chapter>
diff --git a/nixos/modules/programs/zsh/zsh.nix b/nixos/modules/programs/zsh/zsh.nix
index d30b3415411f..164d8db5859a 100644
--- a/nixos/modules/programs/zsh/zsh.nix
+++ b/nixos/modules/programs/zsh/zsh.nix
@@ -11,7 +11,8 @@ let
   cfg = config.programs.zsh;
 
   zshAliases = concatStringsSep "\n" (
-    mapAttrsFlatten (k: v: "alias ${k}='${v}'") cfg.shellAliases
+    mapAttrsFlatten (k: v: "alias ${k}=${escapeShellArg v}")
+      (filterAttrs (k: v: !isNull v) cfg.shellAliases)
   );
 
 in
@@ -34,13 +35,12 @@ in
       };
 
       shellAliases = mkOption {
-        default = config.environment.shellAliases;
+        default = {};
         description = ''
-          Set of aliases for zsh shell. Overrides the default value taken from
-           <option>environment.shellAliases</option>.
+          Set of aliases for zsh shell, which overrides <option>environment.shellAliases</option>.
           See <option>environment.shellAliases</option> for an option format description.
         '';
-        type = types.attrs; # types.attrsOf types.stringOrPath;
+        type = with types; attrsOf (nullOr (either str path));
       };
 
       shellInit = mkOption {
@@ -70,7 +70,7 @@ in
       promptInit = mkOption {
         default = ''
           if [ "$TERM" != dumb ]; then
-            autoload -U promptinit && promptinit && prompt walters
+              autoload -U promptinit && promptinit && prompt walters
           fi
         '';
         description = ''
@@ -106,6 +106,8 @@ in
 
   config = mkIf cfg.enable {
 
+    programs.zsh.shellAliases = mapAttrs (name: mkDefault) cfge.shellAliases;
+
     environment.etc."zshenv".text =
       ''
         # /etc/zshenv: DO NOT EDIT -- this file has been generated automatically.
@@ -116,7 +118,9 @@ in
         if [ -n "$__ETC_ZSHENV_SOURCED" ]; then return; fi
         export __ETC_ZSHENV_SOURCED=1
 
-        ${config.system.build.setEnvironment.text}
+        if [ -z "$__NIXOS_SET_ENVIRONMENT_DONE" ]; then
+            . ${config.system.build.setEnvironment}
+        fi
 
         ${cfge.shellInit}
 
@@ -124,7 +128,7 @@ in
 
         # Read system-wide modifications.
         if test -f /etc/zshenv.local; then
-          . /etc/zshenv.local
+            . /etc/zshenv.local
         fi
       '';
 
@@ -143,7 +147,7 @@ in
 
         # Read system-wide modifications.
         if test -f /etc/zprofile.local; then
-          . /etc/zprofile.local
+            . /etc/zprofile.local
         fi
       '';
 
@@ -169,7 +173,7 @@ in
 
         # Tell zsh how to find installed completions
         for p in ''${(z)NIX_PROFILES}; do
-          fpath+=($p/share/zsh/site-functions $p/share/zsh/$ZSH_VERSION/functions $p/share/zsh/vendor-completions)
+            fpath+=($p/share/zsh/site-functions $p/share/zsh/$ZSH_VERSION/functions $p/share/zsh/vendor-completions)
         done
 
         ${optionalString cfg.enableGlobalCompInit "autoload -U compinit && compinit"}
@@ -184,7 +188,7 @@ in
 
         # Read system-wide modifications.
         if test -f /etc/zshrc.local; then
-          . /etc/zshrc.local
+            . /etc/zshrc.local
         fi
       '';
 
diff --git a/nixos/modules/rename.nix b/nixos/modules/rename.nix
index 4a6bdfe83dd2..aa2b5c0b2dfb 100644
--- a/nixos/modules/rename.nix
+++ b/nixos/modules/rename.nix
@@ -28,7 +28,10 @@ with lib;
       (config:
         let enabled = getAttrFromPath [ "services" "printing" "gutenprint" ] config;
         in if enabled then [ pkgs.gutenprint ] else [ ]))
-    (mkRenamedOptionModule [ "services" "ddclient" "domain" ] [ "services" "ddclient" "domains" ])
+    (mkChangedOptionModule [ "services" "ddclient" "domain" ] [ "services" "ddclient" "domains" ]
+      (config:
+        let value = getAttrFromPath [ "services" "ddclient" "domain" ] config;
+        in if value != "" then [ value ] else []))
     (mkRemovedOptionModule [ "services" "ddclient" "homeDir" ] "")
     (mkRenamedOptionModule [ "services" "elasticsearch" "host" ] [ "services" "elasticsearch" "listenAddress" ])
     (mkRenamedOptionModule [ "services" "graphite" "api" "host" ] [ "services" "graphite" "api" "listenAddress" ])
@@ -39,6 +42,7 @@ with lib;
     (mkRenamedOptionModule [ "services" "kubernetes" "apiserver" "address" ] ["services" "kubernetes" "apiserver" "bindAddress"])
     (mkRemovedOptionModule [ "services" "kubernetes" "apiserver" "publicAddress" ] "")
     (mkRenamedOptionModule [ "services" "kubernetes" "addons" "dashboard" "enableRBAC" ] [ "services" "kubernetes" "addons" "dashboard" "rbac" "enable" ])
+    (mkRemovedOptionModule [ "services" "kubernetes" "kubelet" "cadvisorPort" ] "")
     (mkRenamedOptionModule [ "services" "logstash" "address" ] [ "services" "logstash" "listenAddress" ])
     (mkRenamedOptionModule [ "services" "mpd" "network" "host" ] [ "services" "mpd" "network" "listenAddress" ])
     (mkRenamedOptionModule [ "services" "neo4j" "host" ] [ "services" "neo4j" "defaultListenAddress" ])
@@ -276,6 +280,7 @@ with lib;
 
     (mkRenamedOptionModule [ "programs" "info" "enable" ] [ "documentation" "info" "enable" ])
     (mkRenamedOptionModule [ "programs" "man"  "enable" ] [ "documentation" "man"  "enable" ])
+    (mkRenamedOptionModule [ "services" "nixosManual" "enable" ] [ "documentation" "nixos" "enable" ])
 
   ] ++ (flip map [ "blackboxExporter" "collectdExporter" "fritzboxExporter"
                    "jsonExporter" "minioExporter" "nginxExporter" "nodeExporter"
diff --git a/nixos/modules/security/acme.nix b/nixos/modules/security/acme.nix
index 946da92d80e7..092704c6fc3f 100644
--- a/nixos/modules/security/acme.nix
+++ b/nixos/modules/security/acme.nix
@@ -302,15 +302,15 @@ in
                       workdir="$(mktemp -d)"
 
                       # Create CA
-                      openssl genrsa -des3 -passout pass:x -out $workdir/ca.pass.key 2048
-                      openssl rsa -passin pass:x -in $workdir/ca.pass.key -out $workdir/ca.key
+                      openssl genrsa -des3 -passout pass:xxxx -out $workdir/ca.pass.key 2048
+                      openssl rsa -passin pass:xxxx -in $workdir/ca.pass.key -out $workdir/ca.key
                       openssl req -new -key $workdir/ca.key -out $workdir/ca.csr \
                         -subj "/C=UK/ST=Warwickshire/L=Leamington/O=OrgName/OU=Security Department/CN=example.com"
                       openssl x509 -req -days 1 -in $workdir/ca.csr -signkey $workdir/ca.key -out $workdir/ca.crt
 
                       # Create key
-                      openssl genrsa -des3 -passout pass:x -out $workdir/server.pass.key 2048
-                      openssl rsa -passin pass:x -in $workdir/server.pass.key -out $workdir/server.key
+                      openssl genrsa -des3 -passout pass:xxxx -out $workdir/server.pass.key 2048
+                      openssl rsa -passin pass:xxxx -in $workdir/server.pass.key -out $workdir/server.key
                       openssl req -new -key $workdir/server.key -out $workdir/server.csr \
                         -subj "/C=UK/ST=Warwickshire/L=Leamington/O=OrgName/OU=IT Department/CN=example.com"
                       openssl x509 -req -days 1 -in $workdir/server.csr -CA $workdir/ca.crt \
diff --git a/nixos/modules/security/acme.xml b/nixos/modules/security/acme.xml
index b4cd83f6632c..ef71fe53d0c7 100644
--- a/nixos/modules/security/acme.xml
+++ b/nixos/modules/security/acme.xml
@@ -3,23 +3,25 @@
          xmlns:xi="http://www.w3.org/2001/XInclude"
          version="5.0"
          xml:id="module-security-acme">
-
-<title>SSL/TLS Certificates with ACME</title>
-
-<para>NixOS supports automatic domain validation &amp; certificate
-retrieval and renewal using the ACME protocol. This is currently only
-implemented by and for Let's Encrypt. The alternative ACME client
-<literal>simp_le</literal> is used under the hood.</para>
-
-<section xml:id="module-security-acme-prerequisites"><title>Prerequisites</title>
-
-<para>You need to have a running HTTP server for verification. The server must
-have a webroot defined that can serve
-<filename>.well-known/acme-challenge</filename>. This directory must be
-writeable by the user that will run the ACME client.</para>
-
-<para>For instance, this generic snippet could be used for Nginx:
-
+ <title>SSL/TLS Certificates with ACME</title>
+ <para>
+  NixOS supports automatic domain validation &amp; certificate retrieval and
+  renewal using the ACME protocol. This is currently only implemented by and
+  for Let's Encrypt. The alternative ACME client <literal>simp_le</literal> is
+  used under the hood.
+ </para>
+ <section xml:id="module-security-acme-prerequisites">
+  <title>Prerequisites</title>
+
+  <para>
+   You need to have a running HTTP server for verification. The server must
+   have a webroot defined that can serve
+   <filename>.well-known/acme-challenge</filename>. This directory must be
+   writeable by the user that will run the ACME client.
+  </para>
+
+  <para>
+   For instance, this generic snippet could be used for Nginx:
 <programlisting>
 http {
   server {
@@ -37,43 +39,47 @@ http {
   }
 }
 </programlisting>
-</para>
-
-</section>
-
-<section xml:id="module-security-acme-configuring"><title>Configuring</title>
-
-<para>To enable ACME certificate retrieval &amp; renewal for a certificate for
-<literal>foo.example.com</literal>, add the following in your
-<filename>configuration.nix</filename>:
-
+  </para>
+ </section>
+ <section xml:id="module-security-acme-configuring">
+  <title>Configuring</title>
+
+  <para>
+   To enable ACME certificate retrieval &amp; renewal for a certificate for
+   <literal>foo.example.com</literal>, add the following in your
+   <filename>configuration.nix</filename>:
 <programlisting>
 <xref linkend="opt-security.acme.certs"/>."foo.example.com" = {
   <link linkend="opt-security.acme.certs._name_.webroot">webroot</link> = "/var/www/challenges";
   <link linkend="opt-security.acme.certs._name_.email">email</link> = "foo@example.com";
 };
 </programlisting>
-</para>
-
-<para>The private key <filename>key.pem</filename> and certificate
-<filename>fullchain.pem</filename> will be put into
-<filename>/var/lib/acme/foo.example.com</filename>. The target directory can
-be configured with the option <xref linkend="opt-security.acme.directory"/>.
-</para>
-
-<para>Refer to <xref linkend="ch-options" /> for all available configuration
-options for the <link linkend="opt-security.acme.certs">security.acme</link> module.</para>
-
-</section>
-
-<section xml:id="module-security-acme-nginx"><title>Using ACME certificates in Nginx</title>
-<para>NixOS supports fetching ACME certificates for you by setting
-  <literal><link linkend="opt-services.nginx.virtualHosts._name_.enableACME">enableACME</link> = true;</literal> in a virtualHost config. We
-first create self-signed placeholder certificates in place of the
-real ACME certs. The placeholder certs are overwritten when the ACME
-certs arrive. For <literal>foo.example.com</literal> the config would
-look like.
-</para>
+  </para>
+
+  <para>
+   The private key <filename>key.pem</filename> and certificate
+   <filename>fullchain.pem</filename> will be put into
+   <filename>/var/lib/acme/foo.example.com</filename>. The target directory can
+   be configured with the option <xref linkend="opt-security.acme.directory"/>.
+  </para>
+
+  <para>
+   Refer to <xref linkend="ch-options" /> for all available configuration
+   options for the <link linkend="opt-security.acme.certs">security.acme</link>
+   module.
+  </para>
+ </section>
+ <section xml:id="module-security-acme-nginx">
+  <title>Using ACME certificates in Nginx</title>
+
+  <para>
+   NixOS supports fetching ACME certificates for you by setting
+   <literal><link linkend="opt-services.nginx.virtualHosts._name_.enableACME">enableACME</link>
+   = true;</literal> in a virtualHost config. We first create self-signed
+   placeholder certificates in place of the real ACME certs. The placeholder
+   certs are overwritten when the ACME certs arrive. For
+   <literal>foo.example.com</literal> the config would look like.
+  </para>
 
 <programlisting>
 services.nginx = {
@@ -89,5 +95,5 @@ services.nginx = {
   };
 }
 </programlisting>
-</section>
+ </section>
 </chapter>
diff --git a/nixos/modules/security/apparmor-suid.nix b/nixos/modules/security/apparmor-suid.nix
index dfbf5d859ba9..498c2f25d1c0 100644
--- a/nixos/modules/security/apparmor-suid.nix
+++ b/nixos/modules/security/apparmor-suid.nix
@@ -28,7 +28,7 @@ with lib;
         capability setuid,
         network inet raw,
 
-        ${pkgs.glibc.out}/lib/*.so mr,
+        ${pkgs.stdenv.cc.libc.out}/lib/*.so mr,
         ${pkgs.libcap.lib}/lib/libcap.so* mr,
         ${pkgs.attr.out}/lib/libattr.so* mr,
 
diff --git a/nixos/modules/security/dhparams.nix b/nixos/modules/security/dhparams.nix
index e2b84c3e3b38..62a499ea624d 100644
--- a/nixos/modules/security/dhparams.nix
+++ b/nixos/modules/security/dhparams.nix
@@ -170,4 +170,6 @@ in {
       '';
     }) cfg.params;
   };
+
+  meta.maintainers = with lib.maintainers; [ ekleog ];
 }
diff --git a/nixos/modules/security/hidepid.xml b/nixos/modules/security/hidepid.xml
index d69341eb3cde..5a17cb1da412 100644
--- a/nixos/modules/security/hidepid.xml
+++ b/nixos/modules/security/hidepid.xml
@@ -3,31 +3,26 @@
          xmlns:xi="http://www.w3.org/2001/XInclude"
          version="5.0"
          xml:id="sec-hidepid">
-
-  <title>Hiding process information</title>
-
-  <para>
-    Setting
+ <title>Hiding process information</title>
+ <para>
+  Setting
 <programlisting>
 <xref linkend="opt-security.hideProcessInformation"/> = true;
 </programlisting>
-    ensures that access to process information is restricted to the
-    owning user.  This implies, among other things, that command-line
-    arguments remain private.  Unless your deployment relies on unprivileged
-    users being able to inspect the process information of other users, this
-    option should be safe to enable.
-  </para>
-
-  <para>
-    Members of the <literal>proc</literal> group are exempt from process
-    information hiding.
-  </para>
-
-  <para>
-    To allow a service <replaceable>foo</replaceable> to run without process information hiding, set
+  ensures that access to process information is restricted to the owning user.
+  This implies, among other things, that command-line arguments remain private.
+  Unless your deployment relies on unprivileged users being able to inspect the
+  process information of other users, this option should be safe to enable.
+ </para>
+ <para>
+  Members of the <literal>proc</literal> group are exempt from process
+  information hiding.
+ </para>
+ <para>
+  To allow a service <replaceable>foo</replaceable> to run without process
+  information hiding, set
 <programlisting>
 <link linkend="opt-systemd.services._name_.serviceConfig">systemd.services.<replaceable>foo</replaceable>.serviceConfig</link>.SupplementaryGroups = [ "proc" ];
 </programlisting>
-  </para>
-
+ </para>
 </chapter>
diff --git a/nixos/modules/security/lock-kernel-modules.nix b/nixos/modules/security/lock-kernel-modules.nix
index c81521ed9b08..fc9e7939d814 100644
--- a/nixos/modules/security/lock-kernel-modules.nix
+++ b/nixos/modules/security/lock-kernel-modules.nix
@@ -3,6 +3,10 @@
 with lib;
 
 {
+  meta = {
+    maintainers = [ maintainers.joachifm ];
+  };
+
   options = {
     security.lockKernelModules = mkOption {
       type = types.bool;
diff --git a/nixos/modules/security/misc.nix b/nixos/modules/security/misc.nix
new file mode 100644
index 000000000000..42f872b7b088
--- /dev/null
+++ b/nixos/modules/security/misc.nix
@@ -0,0 +1,39 @@
+{ config, lib, ... }:
+
+with lib;
+
+{
+  meta = {
+    maintainers = [ maintainers.joachifm ];
+  };
+
+  options = {
+    security.allowUserNamespaces = mkOption {
+      type = types.bool;
+      default = true;
+      description = ''
+        Whether to allow creation of user namespaces.  A recurring problem
+        with user namespaces is the presence of code paths where the kernel's
+        permission checking logic fails to account for namespacing, instead
+        permitting a namespaced process to act outside the namespace with the
+        same privileges as it would have inside it.  This is particularly
+        damaging in the common case of running as root within the namespace.
+        When user namespace creation is disallowed, attempting to create
+        a user namespace fails with "no space left on device" (ENOSPC).
+      '';
+    };
+  };
+
+  config = mkIf (!config.security.allowUserNamespaces) {
+    # Setting the number of allowed user namespaces to 0 effectively disables
+    # the feature at runtime.  Note that root may raise the limit again
+    # at any time.
+    boot.kernel.sysctl."user.max_user_namespaces" = 0;
+
+    assertions = [
+      { assertion = config.nix.useSandbox -> config.security.allowUserNamespaces;
+        message = "`nix.useSandbox = true` conflicts with `!security.allowUserNamespaces`.";
+      }
+    ];
+  };
+}
diff --git a/nixos/modules/security/pam.nix b/nixos/modules/security/pam.nix
index bef10b4fe614..926c6d77d3bb 100644
--- a/nixos/modules/security/pam.nix
+++ b/nixos/modules/security/pam.nix
@@ -548,6 +548,13 @@ in
     environment.etc =
       mapAttrsToList (n: v: makePAMService v) config.security.pam.services;
 
+    systemd.tmpfiles.rules = optionals
+      (any (s: s.updateWtmp) (attrValues config.security.pam.services))
+      [
+        "f /var/log/wtmp"
+        "f /var/log/lastlog"
+      ];
+
     security.pam.services =
       { other.text =
           ''
diff --git a/nixos/modules/security/polkit.nix b/nixos/modules/security/polkit.nix
index 04685f2c9ea1..7f1de81d5b70 100644
--- a/nixos/modules/security/polkit.nix
+++ b/nixos/modules/security/polkit.nix
@@ -88,11 +88,11 @@ in
       "polkit-agent-helper-1".source = "${pkgs.polkit.out}/lib/polkit-1/polkit-agent-helper-1";
     };
 
-    system.activationScripts.polkit =
-      ''
-        # Probably no more needed, clean up
-        rm -rf /var/lib/{polkit-1,PolicyKit}
-      '';
+    systemd.tmpfiles.rules = [
+      # Probably no more needed, clean up
+      "R /var/lib/polkit-1"
+      "R /var/lib/PolicyKit"
+    ];
 
     users.users.polkituser = {
       description = "PolKit daemon";
diff --git a/nixos/modules/security/rngd.nix b/nixos/modules/security/rngd.nix
index 3a1ffc55e5fe..63e00b548120 100644
--- a/nixos/modules/security/rngd.nix
+++ b/nixos/modules/security/rngd.nix
@@ -20,7 +20,6 @@ with lib;
       KERNEL=="random", TAG+="systemd"
       SUBSYSTEM=="cpu", ENV{MODALIAS}=="cpu:type:x86,*feature:*009E*", TAG+="systemd", ENV{SYSTEMD_WANTS}+="rngd.service"
       KERNEL=="hw_random", TAG+="systemd", ENV{SYSTEMD_WANTS}+="rngd.service"
-      ${if config.services.tcsd.enable then "" else ''KERNEL=="tpm0", TAG+="systemd", ENV{SYSTEMD_WANTS}+="rngd.service"''}
     '';
 
     systemd.services.rngd = {
@@ -30,8 +29,7 @@ with lib;
 
       description = "Hardware RNG Entropy Gatherer Daemon";
 
-      serviceConfig.ExecStart = "${pkgs.rng_tools}/sbin/rngd -f -v" +
-        (if config.services.tcsd.enable then " --no-tpm=1" else "");
+      serviceConfig.ExecStart = "${pkgs.rng-tools}/sbin/rngd -f -v";
     };
   };
 }
diff --git a/nixos/modules/security/wrappers/default.nix b/nixos/modules/security/wrappers/default.nix
index 77e4b2a616d8..dcb9c8d4ed5f 100644
--- a/nixos/modules/security/wrappers/default.nix
+++ b/nixos/modules/security/wrappers/default.nix
@@ -180,35 +180,6 @@ in
           # programs to be wrapped.
           WRAPPER_PATH=${config.system.path}/bin:${config.system.path}/sbin
 
-          # Remove the old /var/setuid-wrappers path from the system...
-          #
-          # TODO: this is only necessary for upgrades 16.09 => 17.x;
-          # this conditional removal block needs to be removed after
-          # the release.
-          if [ -d /var/setuid-wrappers ]; then
-            rm -rf /var/setuid-wrappers
-            ln -s /run/wrappers/bin /var/setuid-wrappers
-          fi
-
-          # Remove the old /run/setuid-wrappers-dir path from the
-          # system as well...
-          #
-          # TODO: this is only necessary for upgrades 16.09 => 17.x;
-          # this conditional removal block needs to be removed after
-          # the release.
-          if [ -d /run/setuid-wrapper-dirs ]; then
-            rm -rf /run/setuid-wrapper-dirs
-            ln -s /run/wrappers/bin /run/setuid-wrapper-dirs
-          fi
-
-          # TODO: this is only necessary for upgrades 16.09 => 17.x;
-          # this conditional removal block needs to be removed after
-          # the release.
-          if readlink -f /run/booted-system | grep nixos-17 > /dev/null; then
-            rm -rf /run/setuid-wrapper-dirs
-            rm -rf /var/setuid-wrappers
-          fi
-
           # We want to place the tmpdirs for the wrappers to the parent dir.
           wrapperDir=$(mktemp --directory --tmpdir="${parentWrapperDir}" wrappers.XXXXXXXXXX)
           chmod a+rx $wrapperDir
diff --git a/nixos/modules/services/admin/salt/master.nix b/nixos/modules/services/admin/salt/master.nix
index 165580b97837..c6b1b0cc0bd8 100644
--- a/nixos/modules/services/admin/salt/master.nix
+++ b/nixos/modules/services/admin/salt/master.nix
@@ -53,6 +53,9 @@ in
         Type = "notify";
         NotifyAccess = "all";
       };
+      restartTriggers = [
+        config.environment.etc."salt/master".source
+      ];
     };
   };
 
diff --git a/nixos/modules/services/admin/salt/minion.nix b/nixos/modules/services/admin/salt/minion.nix
index 9ecefb32cfa8..c8fa9461a209 100644
--- a/nixos/modules/services/admin/salt/minion.nix
+++ b/nixos/modules/services/admin/salt/minion.nix
@@ -15,7 +15,6 @@ let
     # Default is in /etc/salt/pki/minion
     pki_dir = "/var/lib/salt/pki/minion";
   } cfg.configuration;
-  configDir = pkgs.writeTextDir "minion" (builtins.toJSON fullConfig);
 
 in
 
@@ -28,15 +27,24 @@ in
         default = {};
         description = ''
           Salt minion configuration as Nix attribute set.
-          See <link xlink:href="https://docs.saltstack.com/en/latest/ref/configuration/minion.html"/>                                                                                                 
-          for details.          
+          See <link xlink:href="https://docs.saltstack.com/en/latest/ref/configuration/minion.html"/>
+          for details.
         '';
       };
     };
   };
 
   config = mkIf cfg.enable {
-    environment.systemPackages = with pkgs; [ salt ];
+    environment = {
+      # Set this up in /etc/salt/minion so `salt-call`, etc. work.
+      # The alternatives are
+      # - passing --config-dir to all salt commands, not just the minion unit,
+      # - setting aglobal environment variable.
+      etc."salt/minion".source = pkgs.writeText "minion" (
+        builtins.toJSON fullConfig
+      );
+      systemPackages = with pkgs; [ salt ];
+    };
     systemd.services.salt-minion = {
       description = "Salt Minion";
       wantedBy = [ "multi-user.target" ];
@@ -45,11 +53,14 @@ in
         utillinux
       ];
       serviceConfig = {
-        ExecStart = "${pkgs.salt}/bin/salt-minion --config-dir=${configDir}";
+        ExecStart = "${pkgs.salt}/bin/salt-minion";
         LimitNOFILE = 8192;
         Type = "notify";
         NotifyAccess = "all";
       };
+      restartTriggers = [
+        config.environment.etc."salt/minion".source
+      ];
     };
   };
 }
diff --git a/nixos/modules/services/amqp/rabbitmq.nix b/nixos/modules/services/amqp/rabbitmq.nix
index bb6fc0a104df..c6878dd67dbf 100644
--- a/nixos/modules/services/amqp/rabbitmq.nix
+++ b/nixos/modules/services/amqp/rabbitmq.nix
@@ -4,14 +4,18 @@ with lib;
 
 let
   cfg = config.services.rabbitmq;
-  config_file = pkgs.writeText "rabbitmq.config" cfg.config;
-  config_file_wo_suffix = builtins.substring 0 ((builtins.stringLength config_file) - 7) config_file;
+
+  inherit (builtins) concatStringsSep;
+
+  config_file_content = lib.generators.toKeyValue {} cfg.configItems;
+  config_file = pkgs.writeText "rabbitmq.conf" config_file_content;
+
+  advanced_config_file = pkgs.writeText "advanced.config" cfg.config;
 
 in {
   ###### interface
   options = {
     services.rabbitmq = {
-
       enable = mkOption {
         default = false;
         description = ''
@@ -20,6 +24,15 @@ in {
         '';
       };
 
+      package = mkOption {
+        default = pkgs.rabbitmq-server;
+        type = types.package;
+        defaultText = "pkgs.rabbitmq-server";
+        description = ''
+          Which rabbitmq package to use.
+        '';
+      };
+
       listenAddress = mkOption {
         default = "127.0.0.1";
         example = "";
@@ -30,6 +43,10 @@ in {
           <literal>guest</literal> with password
           <literal>guest</literal> by default, so you should delete
           this user if you intend to allow external access.
+
+          Together with 'port' setting it's mostly an alias for
+          configItems."listeners.tcp.1" and it's left for backwards
+          compatibility with previous version of this module.
         '';
         type = types.str;
       };
@@ -60,11 +77,29 @@ in {
         '';
       };
 
+      configItems = mkOption {
+        default = {};
+        type = types.attrsOf types.str;
+        example = ''
+          {
+            "auth_backends.1.authn" = "rabbit_auth_backend_ldap";
+            "auth_backends.1.authz" = "rabbit_auth_backend_internal";
+          }
+        '';
+        description = ''
+          New style config options.
+
+          See http://www.rabbitmq.com/configure.html
+        '';
+      };
+
       config = mkOption {
         default = "";
         type = types.str;
         description = ''
-          Verbatim configuration file contents.
+          Verbatim advanced configuration file contents.
+          Prefered way is to use configItems.
+
           See http://www.rabbitmq.com/configure.html
         '';
       };
@@ -74,6 +109,12 @@ in {
         type = types.listOf types.str;
         description = "The names of plugins to enable";
       };
+
+      pluginDirs = mkOption {
+        default = [];
+        type = types.listOf types.path;
+        description = "The list of directories containing external plugins";
+      };
     };
   };
 
@@ -81,7 +122,10 @@ in {
   ###### implementation
   config = mkIf cfg.enable {
 
-    environment.systemPackages = [ pkgs.rabbitmq_server ];
+    # This is needed so we will have 'rabbitmqctl' in our PATH
+    environment.systemPackages = [ cfg.package ];
+
+    services.epmd.enable = true;
 
     users.users.rabbitmq = {
       description = "RabbitMQ server user";
@@ -93,44 +137,54 @@ in {
 
     users.groups.rabbitmq.gid = config.ids.gids.rabbitmq;
 
+    services.rabbitmq.configItems = {
+      "listeners.tcp.1" = mkDefault "${cfg.listenAddress}:${toString cfg.port}";
+    };
+
     systemd.services.rabbitmq = {
       description = "RabbitMQ Server";
 
       wantedBy = [ "multi-user.target" ];
-      after = [ "network.target" ];
+      after = [ "network.target" "epmd.socket" ];
+      wants = [ "network.target" "epmd.socket" ];
 
-      path = [ pkgs.rabbitmq_server pkgs.procps ];
+      path = [ cfg.package pkgs.procps ];
 
       environment = {
         RABBITMQ_MNESIA_BASE = "${cfg.dataDir}/mnesia";
-        RABBITMQ_NODE_IP_ADDRESS = cfg.listenAddress;
-        RABBITMQ_NODE_PORT = toString cfg.port;
         RABBITMQ_LOGS = "-";
-        RABBITMQ_SASL_LOGS = "-";
-        RABBITMQ_PID_FILE = "${cfg.dataDir}/pid";
         SYS_PREFIX = "";
+        RABBITMQ_CONFIG_FILE = config_file;
+        RABBITMQ_PLUGINS_DIR = concatStringsSep ":" cfg.pluginDirs;
         RABBITMQ_ENABLED_PLUGINS_FILE = pkgs.writeText "enabled_plugins" ''
           [ ${concatStringsSep "," cfg.plugins} ].
         '';
-      } //  optionalAttrs (cfg.config != "") { RABBITMQ_CONFIG_FILE = config_file_wo_suffix; };
+      } //  optionalAttrs (cfg.config != "") { RABBITMQ_ADVANCED_CONFIG_FILE = advanced_config_file; };
 
       serviceConfig = {
-        ExecStart = "${pkgs.rabbitmq_server}/sbin/rabbitmq-server";
-        ExecStop = "${pkgs.rabbitmq_server}/sbin/rabbitmqctl stop";
+        PermissionsStartOnly = true; # preStart must be run as root
+        ExecStart = "${cfg.package}/sbin/rabbitmq-server";
+        ExecStop = "${cfg.package}/sbin/rabbitmqctl shutdown";
         User = "rabbitmq";
         Group = "rabbitmq";
         WorkingDirectory = cfg.dataDir;
+        Type = "notify";
+        NotifyAccess = "all";
+        UMask = "0027";
+        LimitNOFILE = "100000";
+        Restart = "on-failure";
+        RestartSec = "10";
+        TimeoutStartSec = "3600";
       };
 
-      postStart = ''
-        rabbitmqctl wait ${cfg.dataDir}/pid
-      '';
-
       preStart = ''
         ${optionalString (cfg.cookie != "") ''
             echo -n ${cfg.cookie} > ${cfg.dataDir}/.erlang.cookie
+            chown rabbitmq:rabbitmq ${cfg.dataDir}/.erlang.cookie
             chmod 600 ${cfg.dataDir}/.erlang.cookie
         ''}
+        mkdir -p /var/log/rabbitmq
+        chown rabbitmq:rabbitmq /var/log/rabbitmq
       '';
     };
 
diff --git a/nixos/modules/services/cluster/kubernetes/default.nix b/nixos/modules/services/cluster/kubernetes/default.nix
index aeaa6bd66c99..e63d91eb9aca 100644
--- a/nixos/modules/services/cluster/kubernetes/default.nix
+++ b/nixos/modules/services/cluster/kubernetes/default.nix
@@ -622,13 +622,6 @@ in {
         type = types.bool;
       };
 
-      # TODO: remove this deprecated flag
-      cadvisorPort = mkOption {
-        description = "Kubernetes kubelet local cadvisor port.";
-        default = 4194;
-        type = types.int;
-      };
-
       clusterDns = mkOption {
         description = "Use alternative DNS.";
         default = "10.1.0.1";
@@ -862,7 +855,6 @@ in {
             --hostname-override=${cfg.kubelet.hostname} \
             --allow-privileged=${boolToString cfg.kubelet.allowPrivileged} \
             --root-dir=${cfg.dataDir} \
-            --cadvisor_port=${toString cfg.kubelet.cadvisorPort} \
             ${optionalString (cfg.kubelet.clusterDns != "")
               "--cluster-dns=${cfg.kubelet.clusterDns}"} \
             ${optionalString (cfg.kubelet.clusterDomain != "")
diff --git a/nixos/modules/services/computing/slurm/slurm.nix b/nixos/modules/services/computing/slurm/slurm.nix
index 1e1c5bc9f035..cd481212db2d 100644
--- a/nixos/modules/services/computing/slurm/slurm.nix
+++ b/nixos/modules/services/computing/slurm/slurm.nix
@@ -6,12 +6,18 @@ let
 
   cfg = config.services.slurm;
   # configuration file can be generated by http://slurm.schedmd.com/configurator.html
+
+  defaultUser = "slurm";
+
   configFile = pkgs.writeTextDir "slurm.conf"
     ''
+      ClusterName=${cfg.clusterName}
+      StateSaveLocation=${cfg.stateSaveLocation}
+      SlurmUser=${cfg.user}
       ${optionalString (cfg.controlMachine != null) ''controlMachine=${cfg.controlMachine}''}
       ${optionalString (cfg.controlAddr != null) ''controlAddr=${cfg.controlAddr}''}
-      ${optionalString (cfg.nodeName != null) ''nodeName=${cfg.nodeName}''}
-      ${optionalString (cfg.partitionName != null) ''partitionName=${cfg.partitionName}''}
+      ${toString (map (x: "NodeName=${x}\n") cfg.nodeName)}
+      ${toString (map (x: "PartitionName=${x}\n") cfg.partitionName)}
       PlugStackConfig=${plugStackConfig}
       ProctrackType=${cfg.procTrackType}
       ${cfg.extraConfig}
@@ -23,12 +29,19 @@ let
       ${cfg.extraPlugstackConfig}
     '';
 
-
   cgroupConfig = pkgs.writeTextDir "cgroup.conf"
    ''
      ${cfg.extraCgroupConfig}
    '';
 
+  slurmdbdConf = pkgs.writeTextDir "slurmdbd.conf"
+   ''
+     DbdHost=${cfg.dbdserver.dbdHost}
+     SlurmUser=${cfg.user}
+     StorageType=accounting_storage/mysql
+     ${cfg.dbdserver.extraConfig}
+   '';
+
   # slurm expects some additional config files to be
   # in the same directory as slurm.conf
   etcSlurm = pkgs.symlinkJoin {
@@ -42,6 +55,8 @@ in
 
   ###### interface
 
+  meta.maintainers = [ maintainers.markuskowa ];
+
   options = {
 
     services.slurm = {
@@ -59,6 +74,27 @@ in
         };
       };
 
+      dbdserver = {
+        enable = mkEnableOption "SlurmDBD service";
+
+        dbdHost = mkOption {
+          type = types.str;
+          default = config.networking.hostName;
+          description = ''
+            Hostname of the machine where <literal>slurmdbd</literal>
+            is running (i.e. name returned by <literal>hostname -s</literal>).
+          '';
+        };
+
+        extraConfig = mkOption {
+          type = types.lines;
+          default = "";
+          description = ''
+            Extra configuration for <literal>slurmdbd.conf</literal>
+          '';
+        };
+      };
+
       client = {
         enable = mkEnableOption "slurm client daemon";
       };
@@ -105,10 +141,19 @@ in
         '';
       };
 
+      clusterName = mkOption {
+        type = types.str;
+        default = "default";
+        example = "myCluster";
+        description = ''
+          Necessary to distinguish accounting records in a multi-cluster environment.
+        '';
+      };
+
       nodeName = mkOption {
-        type = types.nullOr types.str;
-        default = null;
-        example = "linux[1-32] CPUs=1 State=UNKNOWN";
+        type = types.listOf types.str;
+        default = [];
+        example = literalExample ''[ "linux[1-32] CPUs=1 State=UNKNOWN" ];'';
         description = ''
           Name that SLURM uses to refer to a node (or base partition for BlueGene
           systems). Typically this would be the string that "/bin/hostname -s"
@@ -117,9 +162,9 @@ in
       };
 
       partitionName = mkOption {
-        type = types.nullOr types.str;
-        default = null;
-        example = "debug Nodes=linux[1-32] Default=YES MaxTime=INFINITE State=UP";
+        type = types.listOf types.str;
+        default = [];
+        example = literalExample ''[ "debug Nodes=linux[1-32] Default=YES MaxTime=INFINITE State=UP" ];'';
         description = ''
           Name by which the partition may be referenced. Note that now you have
           to write the partition's parameters after the name.
@@ -140,7 +185,7 @@ in
       };
 
       procTrackType = mkOption {
-        type = types.string;
+        type = types.str;
         default = "proctrack/linuxproc";
         description = ''
           Plugin to be used for process tracking on a job step basis.
@@ -149,6 +194,25 @@ in
         '';
       };
 
+      stateSaveLocation = mkOption {
+        type = types.str;
+        default = "/var/spool/slurmctld";
+        description = ''
+          Directory into which the Slurm controller, slurmctld, saves its state.
+        '';
+      };
+
+      user = mkOption {
+        type = types.str;
+        default = defaultUser;
+        description = ''
+          Set this option when you want to run the slurmctld daemon
+          as something else than the default slurm user "slurm".
+          Note that the UID of this user needs to be the same
+          on all nodes.
+        '';
+      };
+
       extraConfig = mkOption {
         default = "";
         type = types.lines;
@@ -174,6 +238,8 @@ in
           used when <literal>procTrackType=proctrack/cgroup</literal>.
         '';
       };
+
+
     };
 
   };
@@ -210,12 +276,24 @@ in
         '';
       };
 
-  in mkIf (cfg.enableStools || cfg.client.enable || cfg.server.enable) {
+  in mkIf ( cfg.enableStools ||
+            cfg.client.enable ||
+            cfg.server.enable ||
+            cfg.dbdserver.enable ) {
 
     environment.systemPackages = [ wrappedSlurm ];
 
     services.munge.enable = mkDefault true;
 
+    # use a static uid as default to ensure it is the same on all nodes
+    users.users.slurm = mkIf (cfg.user == defaultUser) {
+      name = defaultUser;
+      group = "slurm";
+      uid = config.ids.uids.slurm;
+    };
+
+    users.groups.slurm.gid = config.ids.uids.slurm;
+
     systemd.services.slurmd = mkIf (cfg.client.enable) {
       path = with pkgs; [ wrappedSlurm coreutils ]
         ++ lib.optional cfg.enableSrunX11 slurm-spank-x11;
@@ -251,6 +329,29 @@ in
         PIDFile = "/run/slurmctld.pid";
         ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
       };
+
+      preStart = ''
+        mkdir -p ${cfg.stateSaveLocation}
+        chown -R ${cfg.user}:slurm ${cfg.stateSaveLocation}
+      '';
+    };
+
+    systemd.services.slurmdbd = mkIf (cfg.dbdserver.enable) {
+      path = with pkgs; [ wrappedSlurm munge coreutils ];
+
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" "munged.service" "mysql.service" ];
+      requires = [ "munged.service" "mysql.service" ];
+
+      # slurm strips the last component off the path
+      environment.SLURM_CONF = "${slurmdbdConf}/slurm.conf";
+
+      serviceConfig = {
+        Type = "forking";
+        ExecStart = "${cfg.package}/bin/slurmdbd";
+        PIDFile = "/run/slurmdbd.pid";
+        ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+      };
     };
 
   };
diff --git a/nixos/modules/services/continuous-integration/buildbot/master.nix b/nixos/modules/services/continuous-integration/buildbot/master.nix
index 8d767de37f00..0f07e6133bb5 100644
--- a/nixos/modules/services/continuous-integration/buildbot/master.nix
+++ b/nixos/modules/services/continuous-integration/buildbot/master.nix
@@ -6,8 +6,12 @@ with lib;
 
 let
   cfg = config.services.buildbot-master;
+
+  python = cfg.package.pythonModule;
+
   escapeStr = s: escape ["'"] s;
-  masterCfg = if cfg.masterCfg == null then pkgs.writeText "master.cfg" ''
+
+  defaultMasterCfg = pkgs.writeText "master.cfg" ''
     from buildbot.plugins import *
     factory = util.BuildFactory()
     c = BuildmasterConfig = dict(
@@ -27,8 +31,28 @@ let
       factory.addStep(step)
 
     ${cfg.extraConfig}
-  ''
-  else cfg.masterCfg;
+  '';
+
+  tacFile = pkgs.writeText "buildbot-master.tac" ''
+    import os
+
+    from twisted.application import service
+    from buildbot.master import BuildMaster
+
+    basedir = '${cfg.buildbotDir}'
+
+    configfile = '${cfg.masterCfg}'
+
+    # Default umask for server
+    umask = None
+
+    # note: this line is matched against to check that this is a buildmaster
+    # directory; do not edit it.
+    application = service.Application('buildmaster')
+
+    m = BuildMaster(basedir, configfile, umask)
+    m.setServiceParent(application)
+  '';
 
 in {
   options = {
@@ -66,9 +90,9 @@ in {
       };
 
       masterCfg = mkOption {
-        type = types.nullOr types.path;
+        type = types.path;
         description = "Optionally pass master.cfg path. Other options in this configuration will be ignored.";
-        default = null;
+        default = defaultMasterCfg;
         example = "/etc/nixos/buildbot/master.cfg";
       };
 
@@ -175,18 +199,25 @@ in {
 
       package = mkOption {
         type = types.package;
-        default = pkgs.buildbot-full;
-        defaultText = "pkgs.buildbot-full";
+        default = pkgs.pythonPackages.buildbot-full;
+        defaultText = "pkgs.pythonPackages.buildbot-full";
         description = "Package to use for buildbot.";
-        example = literalExample "pkgs.buildbot-full";
+        example = literalExample "pkgs.python3Packages.buildbot-full";
       };
 
       packages = mkOption {
-        default = with pkgs; [ python27Packages.twisted git ];
+        default = [ pkgs.git ];
         example = literalExample "[ pkgs.git ]";
         type = types.listOf types.package;
         description = "Packages to add to PATH for the buildbot process.";
       };
+
+      pythonPackages = mkOption {
+        default = pythonPackages: with pythonPackages; [ ];
+        defaultText = "pythonPackages: with pythonPackages; [ ]";
+        description = "Packages to add the to the PYTHONPATH of the buildbot process.";
+        example = literalExample "pythonPackages: with pythonPackages; [ requests ]";
+      };
     };
   };
 
@@ -210,14 +241,15 @@ in {
       description = "Buildbot Continuous Integration Server.";
       after = [ "network-online.target" ];
       wantedBy = [ "multi-user.target" ];
-      path = cfg.packages;
+      path = cfg.packages ++ cfg.pythonPackages python.pkgs;
+      environment.PYTHONPATH = "${python.withPackages (self: cfg.pythonPackages self ++ [ cfg.package ])}/${python.sitePackages}";
 
       preStart = ''
-        env > envvars
-        mkdir -vp ${cfg.buildbotDir}
-        ln -sfv ${masterCfg} ${cfg.buildbotDir}/master.cfg
-        rm -fv $cfg.buildbotDir}/buildbot.tac
-        ${cfg.package}/bin/buildbot create-master ${cfg.buildbotDir}
+        mkdir -vp "${cfg.buildbotDir}"
+        # Link the tac file so buildbot command line tools recognize the directory
+        ln -sf "${tacFile}" "${cfg.buildbotDir}/buildbot.tac"
+        ${cfg.package}/bin/buildbot create-master --db "${cfg.dbUrl}" "${cfg.buildbotDir}"
+        rm -f buildbot.tac.new master.cfg.sample
       '';
 
       serviceConfig = {
@@ -225,12 +257,11 @@ in {
         User = cfg.user;
         Group = cfg.group;
         WorkingDirectory = cfg.home;
-        ExecStart = "${cfg.package}/bin/buildbot start --nodaemon ${cfg.buildbotDir}";
+        # NOTE: call twistd directly with stdout logging for systemd
+        ExecStart = "${python.pkgs.twisted}/bin/twistd -o --nodaemon --pidfile= --logfile - --python ${tacFile}";
       };
-
     };
   };
 
   meta.maintainers = with lib.maintainers; [ nand0p mic92 ];
-
 }
diff --git a/nixos/modules/services/continuous-integration/buildbot/worker.nix b/nixos/modules/services/continuous-integration/buildbot/worker.nix
index 67c541570b97..4130ec918a70 100644
--- a/nixos/modules/services/continuous-integration/buildbot/worker.nix
+++ b/nixos/modules/services/continuous-integration/buildbot/worker.nix
@@ -7,6 +7,40 @@ with lib;
 let
   cfg = config.services.buildbot-worker;
 
+  python = cfg.package.pythonModule;
+
+  tacFile = pkgs.writeText "aur-buildbot-worker.tac" ''
+    import os
+    from io import open
+
+    from buildbot_worker.bot import Worker
+    from twisted.application import service
+
+    basedir = '${cfg.buildbotDir}'
+
+    # note: this line is matched against to check that this is a worker
+    # directory; do not edit it.
+    application = service.Application('buildbot-worker')
+
+    master_url_split = '${cfg.masterUrl}'.split(':')
+    buildmaster_host = master_url_split[0]
+    port = int(master_url_split[1])
+    workername = '${cfg.workerUser}'
+
+    with open('${cfg.workerPassFile}', 'r', encoding='utf-8') as passwd_file:
+        passwd = passwd_file.read().strip('\r\n')
+    keepalive = 600
+    umask = None
+    maxdelay = 300
+    numcpus = None
+    allow_shutdown = None
+
+    s = Worker(buildmaster_host, port, workername, passwd, basedir,
+               keepalive, umask=umask, maxdelay=maxdelay,
+               numcpus=numcpus, allow_shutdown=allow_shutdown)
+    s.setServiceParent(application)
+  '';
+
 in {
   options = {
     services.buildbot-worker = {
@@ -59,6 +93,23 @@ in {
         description = "Specifies the Buildbot Worker password.";
       };
 
+      workerPassFile = mkOption {
+        type = types.path;
+        description = "File used to store the Buildbot Worker password";
+      };
+
+      hostMessage = mkOption {
+        default = null;
+        type = types.nullOr types.str;
+        description = "Description of this worker";
+      };
+
+      adminMessage = mkOption {
+        default = null;
+        type = types.nullOr types.str;
+        description = "Name of the administrator of this worker";
+      };
+
       masterUrl = mkOption {
         default = "localhost:9989";
         type = types.str;
@@ -67,23 +118,24 @@ in {
 
       package = mkOption {
         type = types.package;
-        default = pkgs.buildbot-worker;
-        defaultText = "pkgs.buildbot-worker";
+        default = pkgs.pythonPackages.buildbot-worker;
+        defaultText = "pkgs.pythonPackages.buildbot-worker";
         description = "Package to use for buildbot worker.";
-        example = literalExample "pkgs.buildbot-worker";
+        example = literalExample "pkgs.python3Packages.buildbot-worker";
       };
 
       packages = mkOption {
-        default = with pkgs; [ python27Packages.twisted git ];
+        default = with pkgs; [ git ];
         example = literalExample "[ pkgs.git ]";
         type = types.listOf types.package;
         description = "Packages to add to PATH for the buildbot process.";
       };
-
     };
   };
 
   config = mkIf cfg.enable {
+    services.buildbot-worker.workerPassFile = mkDefault (pkgs.writeText "buildbot-worker-password" cfg.workerPass);
+
     users.groups = optional (cfg.group == "bbworker") {
       name = "bbworker";
     };
@@ -104,11 +156,16 @@ in {
       after = [ "network.target" "buildbot-master.service" ];
       wantedBy = [ "multi-user.target" ];
       path = cfg.packages;
+      environment.PYTHONPATH = "${python.withPackages (p: [ cfg.package ])}/${python.sitePackages}";
 
       preStart = ''
-        mkdir -vp ${cfg.buildbotDir}
-        rm -fv $cfg.buildbotDir}/buildbot.tac
-        ${cfg.package}/bin/buildbot-worker create-worker ${cfg.buildbotDir} ${cfg.masterUrl} ${cfg.workerUser} ${cfg.workerPass}
+        mkdir -vp "${cfg.buildbotDir}/info"
+        ${optionalString (cfg.hostMessage != null) ''
+          ln -sf "${pkgs.writeText "buildbot-worker-host" cfg.hostMessage}" "${cfg.buildbotDir}/info/host"
+        ''}
+        ${optionalString (cfg.adminMessage != null) ''
+          ln -sf "${pkgs.writeText "buildbot-worker-admin" cfg.adminMessage}" "${cfg.buildbotDir}/info/admin"
+        ''}
       '';
 
       serviceConfig = {
@@ -116,11 +173,9 @@ in {
         User = cfg.user;
         Group = cfg.group;
         WorkingDirectory = cfg.home;
-        Environment = "PYTHONPATH=${cfg.package}/lib/python2.7/site-packages:${pkgs.python27Packages.future}/lib/python2.7/site-packages";
 
         # NOTE: call twistd directly with stdout logging for systemd
-        #ExecStart = "${cfg.package}/bin/buildbot-worker start --nodaemon ${cfg.buildbotDir}";
-        ExecStart = "${pkgs.python27Packages.twisted}/bin/twistd -n -l - -y ${cfg.buildbotDir}/buildbot.tac";
+        ExecStart = "${python.pkgs.twisted}/bin/twistd --nodaemon --pidfile= --logfile - --python ${tacFile}";
       };
 
     };
diff --git a/nixos/modules/services/databases/foundationdb.xml b/nixos/modules/services/databases/foundationdb.xml
index 7883680d46cc..bf4b644c9b86 100644
--- a/nixos/modules/services/databases/foundationdb.xml
+++ b/nixos/modules/services/databases/foundationdb.xml
@@ -3,42 +3,50 @@
          xmlns:xi="http://www.w3.org/2001/XInclude"
          version="5.0"
          xml:id="module-services-foundationdb">
-
-<title>FoundationDB</title>
-
-<para><emphasis>Source:</emphasis> <filename>modules/services/databases/foundationdb.nix</filename></para>
-
-<para><emphasis>Upstream documentation:</emphasis> <link xlink:href="https://apple.github.io/foundationdb/"/></para>
-
-<para><emphasis>Maintainer:</emphasis> Austin Seipp</para>
-
-<para><emphasis>Available version(s):</emphasis> 5.1.x, 5.2.x, 6.0.x</para>
-
-<para>FoundationDB (or "FDB") is an open source, distributed, transactional
-key-value store.</para>
-
-<section xml:id="module-services-foundationdb-configuring"><title>Configuring and basic setup</title>
-
-<para>To enable FoundationDB, add the following to your
-<filename>configuration.nix</filename>:
-
+ <title>FoundationDB</title>
+ <para>
+  <emphasis>Source:</emphasis>
+  <filename>modules/services/databases/foundationdb.nix</filename>
+ </para>
+ <para>
+  <emphasis>Upstream documentation:</emphasis>
+  <link xlink:href="https://apple.github.io/foundationdb/"/>
+ </para>
+ <para>
+  <emphasis>Maintainer:</emphasis> Austin Seipp
+ </para>
+ <para>
+  <emphasis>Available version(s):</emphasis> 5.1.x, 5.2.x, 6.0.x
+ </para>
+ <para>
+  FoundationDB (or "FDB") is an open source, distributed, transactional
+  key-value store.
+ </para>
+ <section xml:id="module-services-foundationdb-configuring">
+  <title>Configuring and basic setup</title>
+
+  <para>
+   To enable FoundationDB, add the following to your
+   <filename>configuration.nix</filename>:
 <programlisting>
 services.foundationdb.enable = true;
 services.foundationdb.package = pkgs.foundationdb52; # FoundationDB 5.2.x
 </programlisting>
-</para>
-
-<para>The <option>services.foundationdb.package</option> option is required,
-and must always be specified. Due to the fact FoundationDB network protocols and
-on-disk storage formats may change between (major) versions, and upgrades must
-be explicitly handled by the user, you must always manually specify this
-yourself so that the NixOS module will use the proper version. Note that minor,
-bugfix releases are always compatible.</para>
-
-<para>After running <command>nixos-rebuild</command>, you can verify whether
-FoundationDB is running by executing <command>fdbcli</command> (which is added
-to <option>environment.systemPackages</option>):
-
+  </para>
+
+  <para>
+   The <option>services.foundationdb.package</option> option is required, and
+   must always be specified. Due to the fact FoundationDB network protocols and
+   on-disk storage formats may change between (major) versions, and upgrades
+   must be explicitly handled by the user, you must always manually specify
+   this yourself so that the NixOS module will use the proper version. Note
+   that minor, bugfix releases are always compatible.
+  </para>
+
+  <para>
+   After running <command>nixos-rebuild</command>, you can verify whether
+   FoundationDB is running by executing <command>fdbcli</command> (which is
+   added to <option>environment.systemPackages</option>):
 <programlisting>
 $ sudo -u foundationdb fdbcli
 Using cluster file `/etc/foundationdb/fdb.cluster'.
@@ -66,14 +74,14 @@ Cluster:
 
 fdb>
 </programlisting>
-</para>
-
-<para>You can also write programs using the available client libraries.
-For example, the following Python program can be run in order to grab the
-cluster status, as a quick example. (This example uses
-<command>nix-shell</command> shebang support to automatically supply the
-necessary Python modules).
-
+  </para>
+
+  <para>
+   You can also write programs using the available client libraries. For
+   example, the following Python program can be run in order to grab the
+   cluster status, as a quick example. (This example uses
+   <command>nix-shell</command> shebang support to automatically supply the
+   necessary Python modules).
 <programlisting>
 a@link> cat fdb-status.py
 #! /usr/bin/env nix-shell
@@ -100,255 +108,336 @@ a@link> ./fdb-status.py
 FoundationDB available: True
 a@link>
 </programlisting>
-</para>
-
-<para>FoundationDB is run under the <command>foundationdb</command> user and
-group by default, but this may be changed in the NixOS configuration. The
-systemd unit <command>foundationdb.service</command> controls the
-<command>fdbmonitor</command> process.</para>
-
-<para>By default, the NixOS module for FoundationDB creates a single
-SSD-storage based database for development and basic usage. This storage engine
-is designed for SSDs and will perform poorly on HDDs; however it can handle far
-more data than the alternative "memory" engine and is a better default choice
-for most deployments. (Note that you can change the storage backend on-the-fly
-for a given FoundationDB cluster using <command>fdbcli</command>.)</para>
-
-<para>Furthermore, only 1 server process and 1 backup agent are started in the
-default configuration. See below for more on scaling to increase this.</para>
-
-<para>FoundationDB stores all data for all server processes under
-<filename>/var/lib/foundationdb</filename>. You can override this using
-<option>services.foundationdb.dataDir</option>, e.g.
-
+  </para>
+
+  <para>
+   FoundationDB is run under the <command>foundationdb</command> user and group
+   by default, but this may be changed in the NixOS configuration. The systemd
+   unit <command>foundationdb.service</command> controls the
+   <command>fdbmonitor</command> process.
+  </para>
+
+  <para>
+   By default, the NixOS module for FoundationDB creates a single SSD-storage
+   based database for development and basic usage. This storage engine is
+   designed for SSDs and will perform poorly on HDDs; however it can handle far
+   more data than the alternative "memory" engine and is a better default
+   choice for most deployments. (Note that you can change the storage backend
+   on-the-fly for a given FoundationDB cluster using
+   <command>fdbcli</command>.)
+  </para>
+
+  <para>
+   Furthermore, only 1 server process and 1 backup agent are started in the
+   default configuration. See below for more on scaling to increase this.
+  </para>
+
+  <para>
+   FoundationDB stores all data for all server processes under
+   <filename>/var/lib/foundationdb</filename>. You can override this using
+   <option>services.foundationdb.dataDir</option>, e.g.
 <programlisting>
 services.foundationdb.dataDir = "/data/fdb";
 </programlisting>
-
-</para>
-
-<para>Similarly, logs are stored under
-<filename>/var/log/foundationdb</filename> by default, and there is a
-corresponding <option>services.foundationdb.logDir</option> as well.</para>
-
-</section>
-
-<section xml:id="module-services-foundationdb-scaling"><title>Scaling processes and backup agents</title>
-
-<para>Scaling the number of server processes is quite easy; simply specify
-<option>services.foundationdb.serverProcesses</option> to be the number of
-FoundationDB worker processes that should be started on the machine.</para>
-
-<para>FoundationDB worker processes typically require 4GB of RAM per-process at
-minimum for good performance, so this option is set to 1 by default since the
-maximum amount of RAM is unknown. You're advised to abide by this restriction,
-so pick a number of processes so that each has 4GB or more.</para>
-
-<para>A similar option exists in order to scale backup agent processes,
-<option>services.foundationdb.backupProcesses</option>. Backup agents are not
-as performance/RAM sensitive, so feel free to experiment with the number of
-available backup processes.</para>
-
-</section>
-
-<section xml:id="module-services-foundationdb-clustering"><title>Clustering</title>
-
-<para>FoundationDB on NixOS works similarly to other Linux systems, so this
-section will be brief. Please refer to the full FoundationDB documentation for
-more on clustering.</para>
-
-<para>FoundationDB organizes clusters using a set of
-<emphasis>coordinators</emphasis>, which are just specially-designated worker
-processes. By default, every installation of FoundationDB on NixOS will start
-as its own individual cluster, with a single coordinator: the first worker
-process on <command>localhost</command>.</para>
-
-<para>Coordinators are specified globally using the
-<command>/etc/foundationdb/fdb.cluster</command> file, which all servers and
-client applications will use to find and join coordinators. Note that this file
-<emphasis>can not</emphasis> be managed by NixOS so easily: FoundationDB is
-designed so that it will rewrite the file at runtime for all clients and nodes
-when cluster coordinators change, with clients transparently handling this
-without intervention. It is fundamentally a mutable file, and you should not
-try to manage it in any way in NixOS.</para>
-
-<para>When dealing with a cluster, there are two main things you want to
-do:</para>
-
-<itemizedlist>
-  <listitem><para>Add a node to the cluster for storage/compute.</para></listitem>
-  <listitem><para>Promote an ordinary worker to a coordinator.</para></listitem>
-</itemizedlist>
-
-<para>A node must already be a member of the cluster in order to properly be
-promoted to a coordinator, so you must always add it first if you wish to
-promote it.</para>
-
-<para>To add a machine to a FoundationDB cluster:</para>
-
-<itemizedlist>
-  <listitem><para>Choose one of the servers to start as the initial coordinator.
-      </para></listitem>
-  <listitem><para>Copy the <command>/etc/foundationdb/fdb.cluster</command> file
-      from this server to all the other servers. Restart FoundationDB on all of
-      these other servers, so they join the cluster.</para></listitem>
-  <listitem><para>All of these servers are now connected and working together
-      in the cluster, under the chosen coordinator.</para></listitem>
-</itemizedlist>
-
-<para>At this point, you can add as many nodes as you want by just repeating
-the above steps. By default there will still be a single coordinator: you can
-use <command>fdbcli</command> to change this and add new coordinators.</para>
-
-<para>As a convenience, FoundationDB can automatically assign coordinators
-based on the redundancy mode you wish to achieve for the cluster. Once all the
-nodes have been joined, simply set the replication policy, and then issue the
-<command>coordinators auto</command> command</para>
-
-<para>For example, assuming we have 3 nodes available, we can enable double
-redundancy mode, then auto-select coordinators. For double redundancy, 3
-coordinators is ideal: therefore FoundationDB will make
-<emphasis>every</emphasis> node a coordinator automatically:</para>
+  </para>
+
+  <para>
+   Similarly, logs are stored under <filename>/var/log/foundationdb</filename>
+   by default, and there is a corresponding
+   <option>services.foundationdb.logDir</option> as well.
+  </para>
+ </section>
+ <section xml:id="module-services-foundationdb-scaling">
+  <title>Scaling processes and backup agents</title>
+
+  <para>
+   Scaling the number of server processes is quite easy; simply specify
+   <option>services.foundationdb.serverProcesses</option> to be the number of
+   FoundationDB worker processes that should be started on the machine.
+  </para>
+
+  <para>
+   FoundationDB worker processes typically require 4GB of RAM per-process at
+   minimum for good performance, so this option is set to 1 by default since
+   the maximum amount of RAM is unknown. You're advised to abide by this
+   restriction, so pick a number of processes so that each has 4GB or more.
+  </para>
+
+  <para>
+   A similar option exists in order to scale backup agent processes,
+   <option>services.foundationdb.backupProcesses</option>. Backup agents are
+   not as performance/RAM sensitive, so feel free to experiment with the number
+   of available backup processes.
+  </para>
+ </section>
+ <section xml:id="module-services-foundationdb-clustering">
+  <title>Clustering</title>
+
+  <para>
+   FoundationDB on NixOS works similarly to other Linux systems, so this
+   section will be brief. Please refer to the full FoundationDB documentation
+   for more on clustering.
+  </para>
+
+  <para>
+   FoundationDB organizes clusters using a set of
+   <emphasis>coordinators</emphasis>, which are just specially-designated
+   worker processes. By default, every installation of FoundationDB on NixOS
+   will start as its own individual cluster, with a single coordinator: the
+   first worker process on <command>localhost</command>.
+  </para>
+
+  <para>
+   Coordinators are specified globally using the
+   <command>/etc/foundationdb/fdb.cluster</command> file, which all servers and
+   client applications will use to find and join coordinators. Note that this
+   file <emphasis>can not</emphasis> be managed by NixOS so easily:
+   FoundationDB is designed so that it will rewrite the file at runtime for all
+   clients and nodes when cluster coordinators change, with clients
+   transparently handling this without intervention. It is fundamentally a
+   mutable file, and you should not try to manage it in any way in NixOS.
+  </para>
+
+  <para>
+   When dealing with a cluster, there are two main things you want to do:
+  </para>
+
+  <itemizedlist>
+   <listitem>
+    <para>
+     Add a node to the cluster for storage/compute.
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     Promote an ordinary worker to a coordinator.
+    </para>
+   </listitem>
+  </itemizedlist>
+
+  <para>
+   A node must already be a member of the cluster in order to properly be
+   promoted to a coordinator, so you must always add it first if you wish to
+   promote it.
+  </para>
+
+  <para>
+   To add a machine to a FoundationDB cluster:
+  </para>
+
+  <itemizedlist>
+   <listitem>
+    <para>
+     Choose one of the servers to start as the initial coordinator.
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     Copy the <command>/etc/foundationdb/fdb.cluster</command> file from this
+     server to all the other servers. Restart FoundationDB on all of these
+     other servers, so they join the cluster.
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     All of these servers are now connected and working together in the
+     cluster, under the chosen coordinator.
+    </para>
+   </listitem>
+  </itemizedlist>
+
+  <para>
+   At this point, you can add as many nodes as you want by just repeating the
+   above steps. By default there will still be a single coordinator: you can
+   use <command>fdbcli</command> to change this and add new coordinators.
+  </para>
+
+  <para>
+   As a convenience, FoundationDB can automatically assign coordinators based
+   on the redundancy mode you wish to achieve for the cluster. Once all the
+   nodes have been joined, simply set the replication policy, and then issue
+   the <command>coordinators auto</command> command
+  </para>
+
+  <para>
+   For example, assuming we have 3 nodes available, we can enable double
+   redundancy mode, then auto-select coordinators. For double redundancy, 3
+   coordinators is ideal: therefore FoundationDB will make
+   <emphasis>every</emphasis> node a coordinator automatically:
+  </para>
 
 <programlisting>
 fdbcli> configure double ssd
 fdbcli> coordinators auto
 </programlisting>
 
-<para>This will transparently update all the servers within seconds, and
-appropriately rewrite the <command>fdb.cluster</command> file, as well as
-informing all client processes to do the same.</para>
-
-</section>
-
-<section xml:id="module-services-foundationdb-connectivity"><title>Client connectivity</title>
-
-<para>By default, all clients must use the current
-<command>fdb.cluster</command> file to access a given FoundationDB cluster.
-This file is located by default in
-<command>/etc/foundationdb/fdb.cluster</command> on all machines with the
-FoundationDB service enabled, so you may copy the active one from your cluster
-to a new node in order to connect, if it is not part of the cluster.</para>
-
-</section>
-
-<section xml:id="module-services-foundationdb-authorization"><title>Client authorization and TLS</title>
-
-<para>By default, any user who can connect to a FoundationDB process with the
-correct cluster configuration can access anything. FoundationDB uses a
-pluggable design to transport security, and out of the box it supports a
-LibreSSL-based plugin for TLS support. This plugin not only does in-flight
-encryption, but also performs client authorization based on the given
-endpoint's certificate chain. For example, a FoundationDB server may be
-configured to only accept client connections over TLS, where the client TLS
-certificate is from organization <emphasis>Acme Co</emphasis> in the
-<emphasis>Research and Development</emphasis> unit.</para>
-
-<para>Configuring TLS with FoundationDB is done using the
-<option>services.foundationdb.tls</option> options in order to control the peer
-verification string, as well as the certificate and its private key.</para>
-
-<para>Note that the certificate and its private key must be accessible to the
-FoundationDB user account that the server runs under. These files are also NOT
-managed by NixOS, as putting them into the store may reveal private
-information.</para>
-
-<para>After you have a key and certificate file in place, it is not enough to
-simply set the NixOS module options -- you must also configure the
-<command>fdb.cluster</command> file to specify that a given set of coordinators
-use TLS. This is as simple as adding the suffix <command>:tls</command> to your
-cluster coordinator configuration, after the port number. For example, assuming
-you have a coordinator on localhost with the default configuration, simply
-specifying:</para>
+  <para>
+   This will transparently update all the servers within seconds, and
+   appropriately rewrite the <command>fdb.cluster</command> file, as well as
+   informing all client processes to do the same.
+  </para>
+ </section>
+ <section xml:id="module-services-foundationdb-connectivity">
+  <title>Client connectivity</title>
+
+  <para>
+   By default, all clients must use the current <command>fdb.cluster</command>
+   file to access a given FoundationDB cluster. This file is located by default
+   in <command>/etc/foundationdb/fdb.cluster</command> on all machines with the
+   FoundationDB service enabled, so you may copy the active one from your
+   cluster to a new node in order to connect, if it is not part of the cluster.
+  </para>
+ </section>
+ <section xml:id="module-services-foundationdb-authorization">
+  <title>Client authorization and TLS</title>
+
+  <para>
+   By default, any user who can connect to a FoundationDB process with the
+   correct cluster configuration can access anything. FoundationDB uses a
+   pluggable design to transport security, and out of the box it supports a
+   LibreSSL-based plugin for TLS support. This plugin not only does in-flight
+   encryption, but also performs client authorization based on the given
+   endpoint's certificate chain. For example, a FoundationDB server may be
+   configured to only accept client connections over TLS, where the client TLS
+   certificate is from organization <emphasis>Acme Co</emphasis> in the
+   <emphasis>Research and Development</emphasis> unit.
+  </para>
+
+  <para>
+   Configuring TLS with FoundationDB is done using the
+   <option>services.foundationdb.tls</option> options in order to control the
+   peer verification string, as well as the certificate and its private key.
+  </para>
+
+  <para>
+   Note that the certificate and its private key must be accessible to the
+   FoundationDB user account that the server runs under. These files are also
+   NOT managed by NixOS, as putting them into the store may reveal private
+   information.
+  </para>
+
+  <para>
+   After you have a key and certificate file in place, it is not enough to
+   simply set the NixOS module options -- you must also configure the
+   <command>fdb.cluster</command> file to specify that a given set of
+   coordinators use TLS. This is as simple as adding the suffix
+   <command>:tls</command> to your cluster coordinator configuration, after the
+   port number. For example, assuming you have a coordinator on localhost with
+   the default configuration, simply specifying:
+  </para>
 
 <programlisting>
 XXXXXX:XXXXXX@127.0.0.1:4500:tls
 </programlisting>
 
-<para>will configure all clients and server processes to use TLS from now
-on.</para>
-
-</section>
-
-<section xml:id="module-services-foundationdb-disaster-recovery"><title>Backups and Disaster Recovery</title>
-
-<para>The usual rules for doing FoundationDB backups apply on NixOS as written
-in the FoundationDB manual. However, one important difference is the security
-profile for NixOS: by default, the <command>foundationdb</command> systemd unit
-uses <emphasis>Linux namespaces</emphasis> to restrict write access to the
-system, except for the log directory, data directory, and the
-<command>/etc/foundationdb/</command> directory. This is enforced by default
-and cannot be disabled.</para>
-
-<para>However, a side effect of this is that the <command>fdbbackup</command>
-command doesn't work properly for local filesystem backups: FoundationDB uses a
-server process alongside the database processes to perform backups and copy the
-backups to the filesystem. As a result, this process is put under the
-restricted namespaces above: the backup process can only write to a limited
-number of paths.</para>
-
-<para>In order to allow flexible backup locations on local disks, the
-FoundationDB NixOS module supports a
-<option>services.foundationdb.extraReadWritePaths</option> option. This option
-takes a list of paths, and adds them to the systemd unit, allowing the
-processes inside the service to write (and read) the specified
-directories.</para>
-
-<para>For example, to create backups in <command>/opt/fdb-backups</command>,
-first set up the paths in the module options:</para>
+  <para>
+   will configure all clients and server processes to use TLS from now on.
+  </para>
+ </section>
+ <section xml:id="module-services-foundationdb-disaster-recovery">
+  <title>Backups and Disaster Recovery</title>
+
+  <para>
+   The usual rules for doing FoundationDB backups apply on NixOS as written in
+   the FoundationDB manual. However, one important difference is the security
+   profile for NixOS: by default, the <command>foundationdb</command> systemd
+   unit uses <emphasis>Linux namespaces</emphasis> to restrict write access to
+   the system, except for the log directory, data directory, and the
+   <command>/etc/foundationdb/</command> directory. This is enforced by default
+   and cannot be disabled.
+  </para>
+
+  <para>
+   However, a side effect of this is that the <command>fdbbackup</command>
+   command doesn't work properly for local filesystem backups: FoundationDB
+   uses a server process alongside the database processes to perform backups
+   and copy the backups to the filesystem. As a result, this process is put
+   under the restricted namespaces above: the backup process can only write to
+   a limited number of paths.
+  </para>
+
+  <para>
+   In order to allow flexible backup locations on local disks, the FoundationDB
+   NixOS module supports a
+   <option>services.foundationdb.extraReadWritePaths</option> option. This
+   option takes a list of paths, and adds them to the systemd unit, allowing
+   the processes inside the service to write (and read) the specified
+   directories.
+  </para>
+
+  <para>
+   For example, to create backups in <command>/opt/fdb-backups</command>, first
+   set up the paths in the module options:
+  </para>
 
 <programlisting>
 services.foundationdb.extraReadWritePaths = [ "/opt/fdb-backups" ];
 </programlisting>
 
-<para>Restart the FoundationDB service, and it will now be able to write to
-this directory (even if it does not yet exist.) Note: this path
-<emphasis>must</emphasis> exist before restarting the unit. Otherwise, systemd
-will not include it in the private FoundationDB namespace (and it will not add
-it dynamically at runtime).</para>
+  <para>
+   Restart the FoundationDB service, and it will now be able to write to this
+   directory (even if it does not yet exist.) Note: this path
+   <emphasis>must</emphasis> exist before restarting the unit. Otherwise,
+   systemd will not include it in the private FoundationDB namespace (and it
+   will not add it dynamically at runtime).
+  </para>
 
-<para>You can now perform a backup:</para>
+  <para>
+   You can now perform a backup:
+  </para>
 
 <programlisting>
 $ sudo -u foundationdb fdbbackup start  -t default -d file:///opt/fdb-backups
 $ sudo -u foundationdb fdbbackup status -t default
 </programlisting>
-
-</section>
-
-<section xml:id="module-services-foundationdb-limitations"><title>Known limitations</title>
-
-<para>The FoundationDB setup for NixOS should currently be considered beta.
-FoundationDB is not new software, but the NixOS compilation and integration has
-only undergone fairly basic testing of all the available functionality.</para>
-
-<itemizedlist>
-  <listitem><para>There is no way to specify individual parameters for
-      individual <command>fdbserver</command> processes. Currently, all server
-      processes inherit all the global <command>fdbmonitor</command> settings.
-      </para></listitem>
-  <listitem><para>Ruby bindings are not currently installed.</para></listitem>
-  <listitem><para>Go bindings are not currently installed.</para></listitem>
-</itemizedlist>
-
-</section>
-
-<section xml:id="module-services-foundationdb-options"><title>Options</title>
-
-<para>NixOS's FoundationDB module allows you to configure all of the most
-relevant configuration options for <command>fdbmonitor</command>, matching it
-quite closely. A complete list of options for the FoundationDB module may be
-found <link linkend="opt-services.foundationdb.enable">here</link>. You should
-also read the FoundationDB documentation as well.</para>
-
-</section>
-
-<section xml:id="module-services-foundationdb-full-docs"><title>Full documentation</title>
-
-<para>FoundationDB is a complex piece of software, and requires careful
-administration to properly use. Full documentation for administration can be
-found here: <link xlink:href="https://apple.github.io/foundationdb/"/>.</para>
-
-</section>
-
+ </section>
+ <section xml:id="module-services-foundationdb-limitations">
+  <title>Known limitations</title>
+
+  <para>
+   The FoundationDB setup for NixOS should currently be considered beta.
+   FoundationDB is not new software, but the NixOS compilation and integration
+   has only undergone fairly basic testing of all the available functionality.
+  </para>
+
+  <itemizedlist>
+   <listitem>
+    <para>
+     There is no way to specify individual parameters for individual
+     <command>fdbserver</command> processes. Currently, all server processes
+     inherit all the global <command>fdbmonitor</command> settings.
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     Ruby bindings are not currently installed.
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     Go bindings are not currently installed.
+    </para>
+   </listitem>
+  </itemizedlist>
+ </section>
+ <section xml:id="module-services-foundationdb-options">
+  <title>Options</title>
+
+  <para>
+   NixOS's FoundationDB module allows you to configure all of the most relevant
+   configuration options for <command>fdbmonitor</command>, matching it quite
+   closely. A complete list of options for the FoundationDB module may be found
+   <link linkend="opt-services.foundationdb.enable">here</link>. You should
+   also read the FoundationDB documentation as well.
+  </para>
+ </section>
+ <section xml:id="module-services-foundationdb-full-docs">
+  <title>Full documentation</title>
+
+  <para>
+   FoundationDB is a complex piece of software, and requires careful
+   administration to properly use. Full documentation for administration can be
+   found here: <link xlink:href="https://apple.github.io/foundationdb/"/>.
+  </para>
+ </section>
 </chapter>
diff --git a/nixos/modules/services/databases/postgresql.nix b/nixos/modules/services/databases/postgresql.nix
index f59fb1c81772..6edb1503c233 100644
--- a/nixos/modules/services/databases/postgresql.nix
+++ b/nixos/modules/services/databases/postgresql.nix
@@ -55,7 +55,7 @@ in
 
       package = mkOption {
         type = types.package;
-        example = literalExample "pkgs.postgresql96";
+        example = literalExample "pkgs.postgresql_9_6";
         description = ''
           PostgreSQL package to use.
         '';
@@ -118,7 +118,7 @@ in
       extraPlugins = mkOption {
         type = types.listOf types.path;
         default = [];
-        example = literalExample "[ (pkgs.postgis.override { postgresql = pkgs.postgresql94; }) ]";
+        example = literalExample "[ (pkgs.postgis.override { postgresql = pkgs.postgresql_9_4; }) ]";
         description = ''
           When this list contains elements a new store path is created.
           PostgreSQL and the elements are symlinked into it. Then pg_config,
@@ -167,9 +167,9 @@ in
       # Note: when changing the default, make it conditional on
       # ‘system.stateVersion’ to maintain compatibility with existing
       # systems!
-      mkDefault (if versionAtLeast config.system.stateVersion "17.09" then pkgs.postgresql96
-            else if versionAtLeast config.system.stateVersion "16.03" then pkgs.postgresql95
-            else pkgs.postgresql94);
+      mkDefault (if versionAtLeast config.system.stateVersion "17.09" then pkgs.postgresql_9_6
+            else if versionAtLeast config.system.stateVersion "16.03" then pkgs.postgresql_9_5
+            else pkgs.postgresql_9_4);
 
     services.postgresql.dataDir =
       mkDefault (if versionAtLeast config.system.stateVersion "17.09" then "/var/lib/postgresql/${config.services.postgresql.package.psqlSchema}"
@@ -188,6 +188,8 @@ in
         uid = config.ids.uids.postgres;
         group = "postgres";
         description = "PostgreSQL server user";
+        home = "${cfg.dataDir}";
+        useDefaultShell = true;
       };
 
     users.groups.postgres.gid = config.ids.gids.postgres;
diff --git a/nixos/modules/services/databases/postgresql.xml b/nixos/modules/services/databases/postgresql.xml
index 1aaf33963245..14f4d4909bc0 100644
--- a/nixos/modules/services/databases/postgresql.xml
+++ b/nixos/modules/services/databases/postgresql.xml
@@ -3,36 +3,39 @@
          xmlns:xi="http://www.w3.org/2001/XInclude"
          version="5.0"
          xml:id="module-postgresql">
-
-<title>PostgreSQL</title>
-
+ <title>PostgreSQL</title>
 <!-- FIXME: render nicely -->
-
 <!-- FIXME: source can be added automatically -->
-<para><emphasis>Source:</emphasis> <filename>modules/services/databases/postgresql.nix</filename></para>
-
-<para><emphasis>Upstream documentation:</emphasis> <link xlink:href="http://www.postgresql.org/docs/"/></para>
-
+ <para>
+  <emphasis>Source:</emphasis>
+  <filename>modules/services/databases/postgresql.nix</filename>
+ </para>
+ <para>
+  <emphasis>Upstream documentation:</emphasis>
+  <link xlink:href="http://www.postgresql.org/docs/"/>
+ </para>
 <!-- FIXME: more stuff, like maintainer? -->
-
-<para>PostgreSQL is an advanced, free relational database.<!-- MORE --></para>
-
-<section xml:id="module-services-postgres-configuring"><title>Configuring</title>
-
-<para>To enable PostgreSQL, add the following to your
-<filename>configuration.nix</filename>:
-
+ <para>
+  PostgreSQL is an advanced, free relational database.
+<!-- MORE -->
+ </para>
+ <section xml:id="module-services-postgres-configuring">
+  <title>Configuring</title>
+
+  <para>
+   To enable PostgreSQL, add the following to your
+   <filename>configuration.nix</filename>:
 <programlisting>
 <xref linkend="opt-services.postgresql.enable"/> = true;
-<xref linkend="opt-services.postgresql.package"/> = pkgs.postgresql94;
+<xref linkend="opt-services.postgresql.package"/> = pkgs.postgresql_9_4;
 </programlisting>
-
-Note that you are required to specify the desired version of
-PostgreSQL (e.g. <literal>pkgs.postgresql94</literal>). Since
-upgrading your PostgreSQL version requires a database dump and reload
-(see below), NixOS cannot provide a default value for
-<xref linkend="opt-services.postgresql.package"/> such as the most recent
-release of PostgreSQL.</para>
+   Note that you are required to specify the desired version of PostgreSQL
+   (e.g. <literal>pkgs.postgresql_9_4</literal>). Since upgrading your
+   PostgreSQL version requires a database dump and reload (see below), NixOS
+   cannot provide a default value for
+   <xref linkend="opt-services.postgresql.package"/> such as the most recent
+   release of PostgreSQL.
+  </para>
 
 <!--
 <para>After running <command>nixos-rebuild</command>, you can verify
@@ -47,31 +50,28 @@ alice=>
 </screen>
 -->
 
-<para>By default, PostgreSQL stores its databases in
-<filename>/var/db/postgresql</filename>. You can override this using
-<xref linkend="opt-services.postgresql.dataDir"/>, e.g.
-
+  <para>
+   By default, PostgreSQL stores its databases in
+   <filename>/var/db/postgresql</filename>. You can override this using
+   <xref linkend="opt-services.postgresql.dataDir"/>, e.g.
 <programlisting>
 <xref linkend="opt-services.postgresql.dataDir"/> = "/data/postgresql";
 </programlisting>
-
-</para>
-
-</section>
-
-
-<section xml:id="module-services-postgres-upgrading"><title>Upgrading</title>
-
-<para>FIXME: document dump/upgrade/load cycle.</para>
-
-</section>
-
-
-<section xml:id="module-services-postgres-options"><title>Options</title>
-
-  <para>A complete list of options for the PostgreSQL module may be found <link linkend="opt-services.postgresql.enable">here</link>.</para>
-
-</section>
-
-
+  </para>
+ </section>
+ <section xml:id="module-services-postgres-upgrading">
+  <title>Upgrading</title>
+
+  <para>
+   FIXME: document dump/upgrade/load cycle.
+  </para>
+ </section>
+ <section xml:id="module-services-postgres-options">
+  <title>Options</title>
+
+  <para>
+   A complete list of options for the PostgreSQL module may be found
+   <link linkend="opt-services.postgresql.enable">here</link>.
+  </para>
+ </section>
 </chapter>
diff --git a/nixos/modules/services/desktops/flatpak.xml b/nixos/modules/services/desktops/flatpak.xml
index d9c8b711c450..8045d5fa14f8 100644
--- a/nixos/modules/services/desktops/flatpak.xml
+++ b/nixos/modules/services/desktops/flatpak.xml
@@ -3,51 +3,54 @@
          xmlns:xi="http://www.w3.org/2001/XInclude"
          version="5.0"
          xml:id="module-services-flatpak">
-
-<title>Flatpak</title>
-
-<para><emphasis>Source:</emphasis> <filename>modules/services/desktop/flatpak.nix</filename></para>
-
-<para><emphasis>Upstream documentation:</emphasis> <link xlink:href="https://github.com/flatpak/flatpak/wiki"/></para>
-
-<para>Flatpak is a system for building, distributing, and running sandboxed desktop applications on Linux.</para>
-
-<para>
-  To enable Flatpak, add the following to your <filename>configuration.nix</filename>:
-
-  <programlisting>
+ <title>Flatpak</title>
+ <para>
+  <emphasis>Source:</emphasis>
+  <filename>modules/services/desktop/flatpak.nix</filename>
+ </para>
+ <para>
+  <emphasis>Upstream documentation:</emphasis>
+  <link xlink:href="https://github.com/flatpak/flatpak/wiki"/>
+ </para>
+ <para>
+  Flatpak is a system for building, distributing, and running sandboxed desktop
+  applications on Linux.
+ </para>
+ <para>
+  To enable Flatpak, add the following to your
+  <filename>configuration.nix</filename>:
+<programlisting>
   <xref linkend="opt-services.flatpak.enable"/> = true;
   </programlisting>
-</para>
-
-<para>
-  For the sandboxed apps to work correctly, desktop integration portals need to be installed. If you run GNOME, this will be handled automatically for you; in other cases, you will need to add something like the following to your <filename>configuration.nix</filename>:
-
-  <programlisting>
+ </para>
+ <para>
+  For the sandboxed apps to work correctly, desktop integration portals need to
+  be installed. If you run GNOME, this will be handled automatically for you;
+  in other cases, you will need to add something like the following to your
+  <filename>configuration.nix</filename>:
+<programlisting>
   <xref linkend="opt-services.flatpak.extraPortals"/> = [ pkgs.xdg-desktop-portal-gtk ];
   </programlisting>
-</para>
-
-<para>
-  Then, you will need to add a repository, for example, <link xlink:href="https://github.com/flatpak/flatpak/wiki">Flathub</link>, either using the following commands:
-
-  <programlisting>
+ </para>
+ <para>
+  Then, you will need to add a repository, for example,
+  <link xlink:href="https://github.com/flatpak/flatpak/wiki">Flathub</link>,
+  either using the following commands:
+<programlisting>
   flatpak remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo
   flatpak update
   </programlisting>
-
-  or by opening the <link xlink:href="https://flathub.org/repo/flathub.flatpakrepo">repository file</link> in GNOME Software.
-</para>
-
-<para>
+  or by opening the
+  <link xlink:href="https://flathub.org/repo/flathub.flatpakrepo">repository
+  file</link> in GNOME Software.
+ </para>
+ <para>
   Finally, you can search and install programs:
-
-  <programlisting>
+<programlisting>
   flatpak search bustle
   flatpak install flathub org.freedesktop.Bustle
   flatpak run org.freedesktop.Bustle
   </programlisting>
-
   Again, GNOME Software offers graphical interface for these tasks.
-</para>
+ </para>
 </chapter>
diff --git a/nixos/modules/services/desktops/gnome3/rygel.nix b/nixos/modules/services/desktops/gnome3/rygel.nix
new file mode 100644
index 000000000000..55d5e703aa19
--- /dev/null
+++ b/nixos/modules/services/desktops/gnome3/rygel.nix
@@ -0,0 +1,30 @@
+# rygel service.
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+  ###### interface
+  options = {
+    services.gnome3.rygel = {
+      enable = mkOption {
+        default = false;
+        description = ''
+          Whether to enable Rygel UPnP Mediaserver.
+
+          You will need to also allow UPnP connections in firewall, see the following <link xlink:href="https://github.com/NixOS/nixpkgs/pull/45045#issuecomment-416030795">comment</link>.
+        '';
+        type = types.bool;
+      };
+    };
+  };
+
+  ###### implementation
+  config = mkIf config.services.gnome3.rygel.enable {
+    environment.systemPackages = [ pkgs.gnome3.rygel ];
+
+    services.dbus.packages = [ pkgs.gnome3.rygel ];
+
+    systemd.packages = [ pkgs.gnome3.rygel ];
+  };
+}
diff --git a/nixos/modules/services/desktops/gsignond.nix b/nixos/modules/services/desktops/gsignond.nix
new file mode 100644
index 000000000000..cf26e05d5c18
--- /dev/null
+++ b/nixos/modules/services/desktops/gsignond.nix
@@ -0,0 +1,43 @@
+# Accounts-SSO gSignOn daemon
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  package = pkgs.gsignond.override { plugins = config.services.gsignond.plugins; };
+in
+{
+
+  ###### interface
+
+  options = {
+
+    services.gsignond = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to enable gSignOn daemon, a DBus service
+          which performs user authentication on behalf of its clients.
+        '';
+      };
+
+      plugins = mkOption {
+        type = types.listOf types.package;
+        default = [];
+        description = ''
+          What plugins to use with the gSignOn daemon.
+        '';
+      };
+    };
+  };
+
+  ###### implementation
+  config = mkIf config.services.gsignond.enable {
+    environment.etc."gsignond.conf".source = "${package}/etc/gsignond.conf";
+    services.dbus.packages = [ package ];
+  };
+
+}
diff --git a/nixos/modules/services/desktops/profile-sync-daemon.nix b/nixos/modules/services/desktops/profile-sync-daemon.nix
index e3f74df3e573..e4e47cfbd438 100644
--- a/nixos/modules/services/desktops/profile-sync-daemon.nix
+++ b/nixos/modules/services/desktops/profile-sync-daemon.nix
@@ -4,22 +4,7 @@ with lib;
 
 let
   cfg = config.services.psd;
-
-  configFile = ''
-    ${optionalString (cfg.users != [ ]) ''
-      USERS="${concatStringsSep " " cfg.users}"
-    ''}
-
-    ${optionalString (cfg.browsers != [ ]) ''
-      BROWSERS="${concatStringsSep " " cfg.browsers}"
-    ''}
-
-    ${optionalString (cfg.volatile != "") "VOLATILE=${cfg.volatile}"}
-    ${optionalString (cfg.daemonFile != "") "DAEMON_FILE=${cfg.daemonFile}"}
-  '';
-
 in {
-
   options.services.psd = with types; {
     enable = mkOption {
       type = bool;
@@ -28,32 +13,6 @@ in {
         Whether to enable the Profile Sync daemon.
       '';
     };
-
-    users = mkOption {
-      type = listOf str;
-      default = [ ];
-      example = [ "demo" ];
-      description = ''
-        A list of users whose browser profiles should be sync'd to tmpfs.
-      '';
-    };
-
-    browsers = mkOption {
-      type = listOf str;
-      default = [ ];
-      example = [ "chromium" "firefox" ];
-      description = ''
-        A list of browsers to sync. Available choices are:
-
-        chromium chromium-dev conkeror.mozdev.org epiphany firefox
-        firefox-trunk google-chrome google-chrome-beta google-chrome-unstable
-        heftig-aurora icecat luakit midori opera opera-developer opera-beta
-        qupzilla palemoon rekonq seamonkey
-
-        An empty list will enable all browsers.
-      '';
-    };
-
     resyncTimer = mkOption {
       type = str;
       default = "1h";
@@ -66,80 +25,53 @@ in {
         omitted.
       '';
     };
-
-    volatile = mkOption {
-      type = str;
-      default = "/run/psd-profiles";
-      description = ''
-        The directory where browser profiles should reside(this should be
-        mounted as a tmpfs). Do not include a trailing backslash.
-      '';
-    };
-
-    daemonFile = mkOption {
-      type = str;
-      default = "/run/psd";
-      description = ''
-        Where the pid and backup configuration files will be stored.
-      '';
-    };
   };
 
   config = mkIf cfg.enable {
-    assertions = [
-      { assertion = cfg.users != [];
-        message = "services.psd.users must contain at least one user";
-      }
-    ];
-
     systemd = {
-      services = {
-        psd = {
-          description = "Profile Sync daemon";
-          wants = [ "psd-resync.service" "local-fs.target" ];
-          wantedBy = [ "multi-user.target" ];
-          preStart = "mkdir -p ${cfg.volatile}";
-
-          path = with pkgs; [ glibc rsync gawk ];
-
-          unitConfig = {
-            RequiresMountsFor = [ "/home/" ];
+      user = {
+        services = {
+          psd = {
+            enable = true;
+            description = "Profile Sync daemon";
+            wants = [ "psd-resync.service" "local-fs.target" ];
+            wantedBy = [ "default.target" ];
+            path = with pkgs; [ rsync kmod gawk nettools utillinux profile-sync-daemon ];
+            unitConfig = {
+              RequiresMountsFor = [ "/home/" ];
+            };
+            serviceConfig = {
+              Type = "oneshot";
+              RemainAfterExit = "yes";
+              ExecStart = "${pkgs.profile-sync-daemon}/bin/profile-sync-daemon sync";
+              ExecStop = "${pkgs.profile-sync-daemon}/bin/profile-sync-daemon unsync";
+            };
           };
 
-          serviceConfig = {
-            Type = "oneshot";
-            RemainAfterExit = "yes";
-            ExecStart = "${pkgs.profile-sync-daemon}/bin/profile-sync-daemon sync";
-            ExecStop = "${pkgs.profile-sync-daemon}/bin/profile-sync-daemon unsync";
+          psd-resync = {
+            enable = true;
+            description = "Timed profile resync";
+            after = [ "psd.service" ];
+            wants = [ "psd-resync.timer" ];
+            partOf = [ "psd.service" ];
+            wantedBy = [ "default.target" ];
+            path = with pkgs; [ rsync kmod gawk nettools utillinux profile-sync-daemon ];
+            serviceConfig = {
+              Type = "oneshot";
+              ExecStart = "${pkgs.profile-sync-daemon}/bin/profile-sync-daemon resync";
+            };
           };
         };
 
-        psd-resync = {
-          description = "Timed profile resync";
-          after = [ "psd.service" ];
-          wants = [ "psd-resync.timer" ];
-          partOf = [ "psd.service" ];
-
-          path = with pkgs; [ glibc rsync gawk ];
+        timers.psd-resync = {
+          description = "Timer for profile sync daemon - ${cfg.resyncTimer}";
+          partOf = [ "psd-resync.service" "psd.service" ];
 
-          serviceConfig = {
-            Type = "oneshot";
-            ExecStart = "${pkgs.profile-sync-daemon}/bin/profile-sync-daemon resync";
+          timerConfig = {
+            OnUnitActiveSec = "${cfg.resyncTimer}";
           };
         };
       };
-
-      timers.psd-resync = {
-        description = "Timer for profile sync daemon - ${cfg.resyncTimer}";
-        partOf = [ "psd-resync.service" "psd.service" ];
-
-        timerConfig = {
-          OnUnitActiveSec = "${cfg.resyncTimer}";
-        };
-      };
     };
-
-    environment.etc."psd.conf".text = configFile;
-
   };
 }
diff --git a/nixos/modules/services/editors/emacs.xml b/nixos/modules/services/editors/emacs.xml
index 94eb2e6a77bf..6cf20cf4aa7e 100644
--- a/nixos/modules/services/editors/emacs.xml
+++ b/nixos/modules/services/editors/emacs.xml
@@ -3,150 +3,148 @@
          xmlns:xi="http://www.w3.org/2001/XInclude"
          version="5.0"
          xml:id="module-services-emacs">
-
-  <title>Emacs</title>
-
-  <!--
+ <title>Emacs</title>
+<!--
     Documentation contributors:
       Damien Cassou @DamienCassou
       Thomas Tuegel @ttuegel
       Rodney Lorrimar @rvl
   -->
+ <para>
+  <link xlink:href="http://www.gnu.org/software/emacs/">Emacs</link> is an
+  extensible, customizable, self-documenting real-time display editor — and
+  more. At its core is an interpreter for Emacs Lisp, a dialect of the Lisp
+  programming language with extensions to support text editing.
+ </para>
+ <para>
+  Emacs runs within a graphical desktop environment using the X Window System,
+  but works equally well on a text terminal. Under
+  <productname>macOS</productname>, a "Mac port" edition is available, which
+  uses Apple's native GUI frameworks.
+ </para>
+ <para>
+  <productname>Nixpkgs</productname> provides a superior environment for
+  running <application>Emacs</application>. It's simple to create custom builds
+  by overriding the default packages. Chaotic collections of Emacs Lisp code
+  and extensions can be brought under control using declarative package
+  management. <productname>NixOS</productname> even provides a
+  <command>systemd</command> user service for automatically starting the Emacs
+  daemon.
+ </para>
+ <section xml:id="module-services-emacs-installing">
+  <title>Installing <application>Emacs</application></title>
 
   <para>
-    <link xlink:href="http://www.gnu.org/software/emacs/">Emacs</link>
-    is an extensible, customizable, self-documenting real-time display
-    editor — and more. At its core is an interpreter for Emacs Lisp, a
-    dialect of the Lisp programming language with extensions to
-    support text editing.
+   Emacs can be installed in the normal way for Nix (see
+   <xref linkend="sec-package-management" />). In addition, a NixOS
+   <emphasis>service</emphasis> can be enabled.
   </para>
 
-  <para>
-    Emacs runs within a graphical desktop environment using the X
-    Window System, but works equally well on a text terminal. Under
-    <productname>macOS</productname>, a "Mac port" edition is
-    available, which uses Apple's native GUI frameworks.
-  </para>
+  <section xml:id="module-services-emacs-releases">
+   <title>The Different Releases of Emacs</title>
+
+   <para>
+    <productname>Nixpkgs</productname> defines several basic Emacs packages.
+    The following are attributes belonging to the <varname>pkgs</varname> set:
+    <variablelist>
+     <varlistentry>
+      <term>
+       <varname>emacs</varname>
+      </term>
+      <term>
+       <varname>emacs25</varname>
+      </term>
+      <listitem>
+       <para>
+        The latest stable version of Emacs 25 using the
+        <link
+                xlink:href="http://www.gtk.org">GTK+ 2</link>
+        widget toolkit.
+       </para>
+      </listitem>
+     </varlistentry>
+     <varlistentry>
+      <term>
+       <varname>emacs25-nox</varname>
+      </term>
+      <listitem>
+       <para>
+        Emacs 25 built without any dependency on X11 libraries.
+       </para>
+      </listitem>
+     </varlistentry>
+     <varlistentry>
+      <term>
+       <varname>emacsMacport</varname>
+      </term>
+      <term>
+       <varname>emacs25Macport</varname>
+      </term>
+      <listitem>
+       <para>
+        Emacs 25 with the "Mac port" patches, providing a more native look and
+        feel under macOS.
+       </para>
+      </listitem>
+     </varlistentry>
+    </variablelist>
+   </para>
+
+   <para>
+    If those aren't suitable, then the following imitation Emacs editors are
+    also available in Nixpkgs:
+    <link xlink:href="https://www.gnu.org/software/zile/">Zile</link>,
+    <link xlink:href="http://homepage.boetes.org/software/mg/">mg</link>,
+    <link xlink:href="http://yi-editor.github.io/">Yi</link>.
+   </para>
+  </section>
 
-  <para>
-    <productname>Nixpkgs</productname> provides a superior environment
-    for running <application>Emacs</application>. It's simple to
-    create custom builds by overriding the default packages. Chaotic
-    collections of Emacs Lisp code and extensions can be brought under
-    control using declarative package
-    management. <productname>NixOS</productname> even provides a
-    <command>systemd</command> user service for automatically
-    starting the Emacs daemon.
-  </para>
+  <section xml:id="module-services-emacs-adding-packages">
+   <title>Adding Packages to Emacs</title>
 
-  <section xml:id="module-services-emacs-installing">
-    <title>Installing <application>Emacs</application></title>
+   <para>
+    Emacs includes an entire ecosystem of functionality beyond text editing,
+    including a project planner, mail and news reader, debugger interface,
+    calendar, and more.
+   </para>
 
+   <para>
+    Most extensions are gotten with the Emacs packaging system
+    (<filename>package.el</filename>) from
+    <link
+        xlink:href="https://elpa.gnu.org/">Emacs Lisp Package Archive
+    (<acronym>ELPA</acronym>)</link>,
+    <link xlink:href="https://melpa.org/"><acronym>MELPA</acronym></link>,
+    <link xlink:href="https://stable.melpa.org/">MELPA Stable</link>, and
+    <link xlink:href="http://orgmode.org/elpa.html">Org ELPA</link>. Nixpkgs is
+    regularly updated to mirror all these archives.
+   </para>
+
+   <para>
+    Under NixOS, you can continue to use
+    <function>package-list-packages</function> and
+    <function>package-install</function> to install packages. You can also
+    declare the set of Emacs packages you need using the derivations from
+    Nixpkgs. The rest of this section discusses declarative installation of
+    Emacs packages through nixpkgs.
+   </para>
+
+   <note>
     <para>
-      Emacs can be installed in the normal way for Nix (see
-      <xref linkend="sec-package-management" />).
-      In addition, a NixOS <emphasis>service</emphasis>
-      can be enabled.
+     This documentation describes the new Emacs packages framework in NixOS
+     16.03 (<varname>emacsPackagesNg</varname>) which should not be confused
+     with the previous and deprecated framework
+     (<varname>emacs24Packages</varname>).
     </para>
-
-    <section xml:id="module-services-emacs-releases">
-      <title>The Different Releases of Emacs</title>
-
-      <para>
-        <productname>Nixpkgs</productname> defines several basic Emacs
-        packages. The following are attributes belonging to the
-        <varname>pkgs</varname> set:
-
-        <variablelist>
-          <varlistentry>
-            <term><varname>emacs</varname></term>
-            <term><varname>emacs25</varname></term>
-            <listitem>
-              <para>
-                The latest stable version of Emacs 25 using the <link
-                xlink:href="http://www.gtk.org">GTK+ 2</link> widget
-                toolkit.
-              </para>
-            </listitem>
-          </varlistentry>
-          <varlistentry>
-            <term><varname>emacs25-nox</varname></term>
-            <listitem>
-              <para>
-                Emacs 25 built without any dependency on X11
-                libraries.
-              </para>
-            </listitem>
-          </varlistentry>
-          <varlistentry>
-            <term><varname>emacsMacport</varname></term>
-            <term><varname>emacs25Macport</varname></term>
-            <listitem>
-              <para>
-                Emacs 25 with the "Mac port" patches, providing a more
-                native look and feel under macOS.
-              </para>
-            </listitem>
-          </varlistentry>
-        </variablelist>
-      </para>
-
-      <para>
-        If those aren't suitable, then the following imitation Emacs
-        editors are also available in Nixpkgs:
-        <link xlink:href="https://www.gnu.org/software/zile/">Zile</link>,
-        <link xlink:href="http://homepage.boetes.org/software/mg/">mg</link>,
-        <link xlink:href="http://yi-editor.github.io/">Yi</link>.
-      </para>
-
-    </section>
-    <section xml:id="module-services-emacs-adding-packages">
-      <title>Adding Packages to Emacs</title>
-      <para>
-        Emacs includes an entire ecosystem of functionality beyond
-        text editing, including a project planner, mail and news
-        reader, debugger interface, calendar, and more.
-      </para>
-
-      <para>
-        Most extensions are gotten with the Emacs packaging system
-        (<filename>package.el</filename>) from <link
-        xlink:href="https://elpa.gnu.org/">Emacs Lisp Package Archive
-        (<acronym>ELPA</acronym>)</link>,
-        <link xlink:href="https://melpa.org/"><acronym>MELPA</acronym></link>,
-        <link xlink:href="https://stable.melpa.org/">MELPA Stable</link>,
-        and <link xlink:href="http://orgmode.org/elpa.html">Org ELPA</link>.
-        Nixpkgs is regularly updated to mirror all these archives.
-      </para>
-
-      <para>
-        Under NixOS, you can continue to use
-        <function>package-list-packages</function> and
-        <function>package-install</function> to install packages. You
-        can also declare the set of Emacs packages you need using the
-        derivations from Nixpkgs. The rest of this section discusses
-        declarative installation of Emacs packages through nixpkgs.
-      </para>
-
-      <note>
-        <para>
-          This documentation describes the new Emacs packages
-          framework in NixOS 16.03
-          (<varname>emacsPackagesNg</varname>) which should not be
-          confused with the previous and deprecated framework
-          (<varname>emacs24Packages</varname>).
-        </para>
-      </note>
-
-      <para>
-        The first step to declare the list of packages you want in
-        your Emacs installation is to create a dedicated
-        derivation. This can be done in a dedicated
-        <filename>emacs.nix</filename> file such as:
-
-      <example xml:id="ex-emacsNix">
-        <title>Nix expression to build Emacs with packages (<filename>emacs.nix</filename>)</title>
-        <programlisting language="nix">
+   </note>
+
+   <para>
+    The first step to declare the list of packages you want in your Emacs
+    installation is to create a dedicated derivation. This can be done in a
+    dedicated <filename>emacs.nix</filename> file such as:
+    <example xml:id="ex-emacsNix">
+     <title>Nix expression to build Emacs with packages (<filename>emacs.nix</filename>)</title>
+<programlisting language="nix">
 /*
 This is a nix expression to build Emacs and some Emacs packages I like
 from source on any distribution where Nix is installed. This will install
@@ -181,119 +179,104 @@ in
     pkgs.notmuch   # From main packages set <co xml:id="ex-emacsNix-7" />
   ])
 </programlisting>
-      </example>
-
-      <calloutlist>
-        <callout arearefs="ex-emacsNix-1">
-          <para>
-            The first non-comment line in this file
-            (<literal>{ pkgs ? ... }</literal>)
-            indicates that the whole file represents a function.
-          </para>
-        </callout>
-
-        <callout arearefs="ex-emacsNix-2">
-          <para>
-            The <varname>let</varname> expression below defines a
-            <varname>myEmacs</varname> binding pointing to the current
-            stable version of Emacs. This binding is here to separate the
-            choice of the Emacs binary from the specification of the
-            required packages.
-          </para>
-        </callout>
-
-        <callout arearefs="ex-emacsNix-3">
-          <para>
-            This generates an <varname>emacsWithPackages</varname>
-            function. It takes a single argument: a function from a
-            package set to a list of packages (the packages that will
-            be available in Emacs).
-          </para>
-        </callout>
-
-        <callout arearefs="ex-emacsNix-4">
-          <para>
-            The rest of the file specifies the list of packages to
-            install. In the example, two packages
-            (<varname>magit</varname> and
-            <varname>zerodark-theme</varname>) are taken from MELPA
-            stable.
-          </para>
-        </callout>
-
-        <callout arearefs="ex-emacsNix-5">
-          <para>
-            Two packages (<varname>undo-tree</varname> and
-            <varname>zoom-frm</varname>) are taken from MELPA.
-          </para>
-        </callout>
-
-        <callout arearefs="ex-emacsNix-6">
-          <para>Three packages are taken from GNU ELPA.</para>
-        </callout>
-
-        <callout arearefs="ex-emacsNix-7">
-          <para>
-            <varname>notmuch</varname> is taken from a nixpkgs derivation
-            which contains an Emacs mode.
-          </para>
-        </callout>
-
-      </calloutlist>
+    </example>
+    <calloutlist>
+     <callout arearefs="ex-emacsNix-1">
+      <para>
+       The first non-comment line in this file (<literal>{ pkgs ? ...
+       }</literal>) indicates that the whole file represents a function.
       </para>
-
+     </callout>
+     <callout arearefs="ex-emacsNix-2">
       <para>
-        The result of this configuration will be an
-        <command>emacs</command> command which launches Emacs with all
-        of your chosen packages in the <varname>load-path</varname>.
+       The <varname>let</varname> expression below defines a
+       <varname>myEmacs</varname> binding pointing to the current stable
+       version of Emacs. This binding is here to separate the choice of the
+       Emacs binary from the specification of the required packages.
       </para>
-
+     </callout>
+     <callout arearefs="ex-emacsNix-3">
       <para>
-        You can check that it works by executing this in a terminal:
-
+       This generates an <varname>emacsWithPackages</varname> function. It
+       takes a single argument: a function from a package set to a list of
+       packages (the packages that will be available in Emacs).
+      </para>
+     </callout>
+     <callout arearefs="ex-emacsNix-4">
+      <para>
+       The rest of the file specifies the list of packages to install. In the
+       example, two packages (<varname>magit</varname> and
+       <varname>zerodark-theme</varname>) are taken from MELPA stable.
+      </para>
+     </callout>
+     <callout arearefs="ex-emacsNix-5">
+      <para>
+       Two packages (<varname>undo-tree</varname> and
+       <varname>zoom-frm</varname>) are taken from MELPA.
+      </para>
+     </callout>
+     <callout arearefs="ex-emacsNix-6">
+      <para>
+       Three packages are taken from GNU ELPA.
+      </para>
+     </callout>
+     <callout arearefs="ex-emacsNix-7">
+      <para>
+       <varname>notmuch</varname> is taken from a nixpkgs derivation which
+       contains an Emacs mode.
+      </para>
+     </callout>
+    </calloutlist>
+   </para>
+
+   <para>
+    The result of this configuration will be an <command>emacs</command>
+    command which launches Emacs with all of your chosen packages in the
+    <varname>load-path</varname>.
+   </para>
+
+   <para>
+    You can check that it works by executing this in a terminal:
 <screen>
 $ nix-build emacs.nix
 $ ./result/bin/emacs -q
 </screen>
+    and then typing <literal>M-x package-initialize</literal>. Check that you
+    can use all the packages you want in this Emacs instance. For example, try
+    switching to the zerodark theme through <literal>M-x load-theme &lt;RET&gt;
+    zerodark &lt;RET&gt; y</literal>.
+   </para>
 
-        and then typing <literal>M-x package-initialize</literal>.
-        Check that you can use all the packages you want in this
-        Emacs instance. For example, try switching to the zerodark
-        theme through
-        <literal>M-x load-theme &lt;RET&gt; zerodark &lt;RET&gt; y</literal>.
-      </para>
-
-      <tip>
-        <para>
-          A few popular extensions worth checking out are: auctex,
-          company, edit-server, flycheck, helm, iedit, magit,
-          multiple-cursors, projectile, and yasnippet.
-        </para>
-      </tip>
-
-      <para>
-        The list of available packages in the various ELPA
-        repositories can be seen with the following commands:
-        <example xml:id="module-services-emacs-querying-packages">
-          <title>Querying Emacs packages</title>
-          <programlisting><![CDATA[
+   <tip>
+    <para>
+     A few popular extensions worth checking out are: auctex, company,
+     edit-server, flycheck, helm, iedit, magit, multiple-cursors, projectile,
+     and yasnippet.
+    </para>
+   </tip>
+
+   <para>
+    The list of available packages in the various ELPA repositories can be seen
+    with the following commands:
+    <example xml:id="module-services-emacs-querying-packages">
+     <title>Querying Emacs packages</title>
+<programlisting><![CDATA[
 nix-env -f "<nixpkgs>" -qaP -A emacsPackagesNg.elpaPackages
 nix-env -f "<nixpkgs>" -qaP -A emacsPackagesNg.melpaPackages
 nix-env -f "<nixpkgs>" -qaP -A emacsPackagesNg.melpaStablePackages
 nix-env -f "<nixpkgs>" -qaP -A emacsPackagesNg.orgPackages
 ]]></programlisting>
-        </example>
-      </para>
-
-      <para>
-        If you are on NixOS, you can install this particular Emacs for
-        all users by adding it to the list of system packages
-        (see <xref linkend="sec-declarative-package-mgmt" />). Simply
-        modify your file <filename>configuration.nix</filename> to
-        make it contain:
-        <example xml:id="module-services-emacs-configuration-nix">
-          <title>Custom Emacs in <filename>configuration.nix</filename></title>
-          <programlisting><![CDATA[
+    </example>
+   </para>
+
+   <para>
+    If you are on NixOS, you can install this particular Emacs for all users by
+    adding it to the list of system packages (see
+    <xref linkend="sec-declarative-package-mgmt" />). Simply modify your file
+    <filename>configuration.nix</filename> to make it contain:
+    <example xml:id="module-services-emacs-configuration-nix">
+     <title>Custom Emacs in <filename>configuration.nix</filename></title>
+<programlisting><![CDATA[
 {
  environment.systemPackages = [
    # [...]
@@ -301,60 +284,59 @@ nix-env -f "<nixpkgs>" -qaP -A emacsPackagesNg.orgPackages
   ];
 }
 ]]></programlisting>
-        </example>
-      </para>
+    </example>
+   </para>
 
-      <para>
-        In this case, the next <command>nixos-rebuild switch</command>
-        will take care of adding your <command>emacs</command> to the
-        <varname>PATH</varname> environment variable
-        (see <xref linkend="sec-changing-config" />).
-      </para>
+   <para>
+    In this case, the next <command>nixos-rebuild switch</command> will take
+    care of adding your <command>emacs</command> to the <varname>PATH</varname>
+    environment variable (see <xref linkend="sec-changing-config" />).
+   </para>
 
 <!-- fixme: i think the following is better done with config.nix
 https://nixos.org/nixpkgs/manual/#sec-modify-via-packageOverrides
 -->
-      <para>
-        If you are not on NixOS or want to install this particular
-        Emacs only for yourself, you can do so by adding it to your
-        <filename>~/.config/nixpkgs/config.nix</filename>
-        (see <link xlink:href="http://nixos.org/nixpkgs/manual/#sec-modify-via-packageOverrides">Nixpkgs manual</link>):
-        <example xml:id="module-services-emacs-config-nix">
-          <title>Custom Emacs in <filename>~/.config/nixpkgs/config.nix</filename></title>
-          <programlisting><![CDATA[
+
+   <para>
+    If you are not on NixOS or want to install this particular Emacs only for
+    yourself, you can do so by adding it to your
+    <filename>~/.config/nixpkgs/config.nix</filename> (see
+    <link xlink:href="http://nixos.org/nixpkgs/manual/#sec-modify-via-packageOverrides">Nixpkgs
+    manual</link>):
+    <example xml:id="module-services-emacs-config-nix">
+     <title>Custom Emacs in <filename>~/.config/nixpkgs/config.nix</filename></title>
+<programlisting><![CDATA[
 {
   packageOverrides = super: let self = super.pkgs; in {
     myemacs = import /path/to/emacs.nix { pkgs = self; };
   };
 }
 ]]></programlisting>
-        </example>
-      </para>
-
-      <para>
-        In this case, the next
-        <literal>nix-env -f '&lt;nixpkgs&gt;' -iA myemacs</literal>
-        will take care of adding your emacs to the
-        <varname>PATH</varname> environment variable.
-      </para>
-    </section>
-
-    <section xml:id="module-services-emacs-advanced">
-      <title>Advanced Emacs Configuration</title>
+    </example>
+   </para>
 
-      <para>
-        If you want, you can tweak the Emacs package itself from your
-        <filename>emacs.nix</filename>. For example, if you want to
-        have a GTK+3-based Emacs instead of the default GTK+2-based
-        binary and remove the automatically generated
-        <filename>emacs.desktop</filename> (useful is you only use
-        <command>emacsclient</command>), you can change your file
-        <filename>emacs.nix</filename> in this way:
-      </para>
+   <para>
+    In this case, the next <literal>nix-env -f '&lt;nixpkgs&gt;' -iA
+    myemacs</literal> will take care of adding your emacs to the
+    <varname>PATH</varname> environment variable.
+   </para>
+  </section>
 
-      <example xml:id="ex-emacsGtk3Nix">
-        <title>Custom Emacs build</title>
-        <programlisting><![CDATA[
+  <section xml:id="module-services-emacs-advanced">
+   <title>Advanced Emacs Configuration</title>
+
+   <para>
+    If you want, you can tweak the Emacs package itself from your
+    <filename>emacs.nix</filename>. For example, if you want to have a
+    GTK+3-based Emacs instead of the default GTK+2-based binary and remove the
+    automatically generated <filename>emacs.desktop</filename> (useful is you
+    only use <command>emacsclient</command>), you can change your file
+    <filename>emacs.nix</filename> in this way:
+   </para>
+
+   <example xml:id="ex-emacsGtk3Nix">
+    <title>Custom Emacs build</title>
+<programlisting><![CDATA[
 { pkgs ? import <nixpkgs> {} }:
 let
   myEmacs = (pkgs.emacs.override {
@@ -370,161 +352,143 @@ let
   });
 in [...]
 ]]></programlisting>
-      </example>
+   </example>
 
-      <para>
-        After building this file as shown in <xref linkend="ex-emacsNix" />,
-        you will get an GTK3-based Emacs binary pre-loaded with your
-        favorite packages.
-      </para>
-    </section>
+   <para>
+    After building this file as shown in <xref linkend="ex-emacsNix" />, you
+    will get an GTK3-based Emacs binary pre-loaded with your favorite packages.
+   </para>
   </section>
-
-<section xml:id="module-services-emacs-running">
+ </section>
+ <section xml:id="module-services-emacs-running">
   <title>Running Emacs as a Service</title>
+
   <para>
-    <productname>NixOS</productname> provides an optional
-    <command>systemd</command> service which launches
-    <link xlink:href="https://www.gnu.org/software/emacs/manual/html_node/emacs/Emacs-Server.html">
-      Emacs daemon
-    </link>
-    with the user's login session.
+   <productname>NixOS</productname> provides an optional
+   <command>systemd</command> service which launches
+   <link xlink:href="https://www.gnu.org/software/emacs/manual/html_node/emacs/Emacs-Server.html">
+   Emacs daemon </link> with the user's login session.
   </para>
 
   <para>
-    <emphasis>Source:</emphasis>
-    <filename>modules/services/editors/emacs.nix</filename>
+   <emphasis>Source:</emphasis>
+   <filename>modules/services/editors/emacs.nix</filename>
   </para>
 
   <section xml:id="module-services-emacs-enabling">
-    <title>Enabling the Service</title>
-
-    <para>
-      To install and enable the <command>systemd</command>
-      user service for Emacs daemon, add the following to your
-      <filename>configuration.nix</filename>:
+   <title>Enabling the Service</title>
 
+   <para>
+    To install and enable the <command>systemd</command> user service for Emacs
+    daemon, add the following to your <filename>configuration.nix</filename>:
 <programlisting>
 <xref linkend="opt-services.emacs.enable"/> = true;
 <xref linkend="opt-services.emacs.package"/> = import /home/cassou/.emacs.d { pkgs = pkgs; };
 </programlisting>
-    </para>
-
-    <para>
-      The <varname>services.emacs.package</varname> option allows a
-      custom derivation to be used, for example, one created by
-      <function>emacsWithPackages</function>.
-    </para>
-
-    <para>
-      Ensure that the Emacs server is enabled for your user's Emacs
-      configuration, either by customizing the
-      <varname>server-mode</varname> variable, or by adding
-      <literal>(server-start)</literal> to
-      <filename>~/.emacs.d/init.el</filename>.
-    </para>
-
-    <para>
-      To start the daemon, execute the following:
-
+   </para>
+
+   <para>
+    The <varname>services.emacs.package</varname> option allows a custom
+    derivation to be used, for example, one created by
+    <function>emacsWithPackages</function>.
+   </para>
+
+   <para>
+    Ensure that the Emacs server is enabled for your user's Emacs
+    configuration, either by customizing the <varname>server-mode</varname>
+    variable, or by adding <literal>(server-start)</literal> to
+    <filename>~/.emacs.d/init.el</filename>.
+   </para>
+
+   <para>
+    To start the daemon, execute the following:
 <screen>
 $ nixos-rebuild switch  # to activate the new configuration.nix
 $ systemctl --user daemon-reload        # to force systemd reload
 $ systemctl --user start emacs.service  # to start the Emacs daemon
 </screen>
-
-      The server should now be ready to serve Emacs clients.
-    </para>
-
+    The server should now be ready to serve Emacs clients.
+   </para>
   </section>
 
   <section xml:id="module-services-emacs-starting-client">
-    <title>Starting the client</title>
-    <para>
-      Ensure that the emacs server is enabled, either by customizing
-      the <varname>server-mode</varname> variable, or by adding
-      <literal>(server-start)</literal> to
-      <filename>~/.emacs</filename>.
-    </para>
+   <title>Starting the client</title>
 
-    <para>
-      To connect to the emacs daemon, run one of the following:
-      <programlisting><![CDATA[
+   <para>
+    Ensure that the emacs server is enabled, either by customizing the
+    <varname>server-mode</varname> variable, or by adding
+    <literal>(server-start)</literal> to <filename>~/.emacs</filename>.
+   </para>
+
+   <para>
+    To connect to the emacs daemon, run one of the following:
+<programlisting><![CDATA[
 emacsclient FILENAME
 emacsclient --create-frame  # opens a new frame (window)
 emacsclient --create-frame --tty  # opens a new frame on the current terminal
 ]]></programlisting>
-    </para>
+   </para>
   </section>
 
   <section xml:id="module-services-emacs-editor-variable">
-    <title>Configuring the <varname>EDITOR</varname> variable</title>
-    <!--<title><command>emacsclient</command> as the Default Editor</title>-->
-
-    <para>
-      If <xref linkend="opt-services.emacs.defaultEditor"/> is
-      <literal>true</literal>, the <varname>EDITOR</varname> variable
-      will be set to a wrapper script which launches
-      <command>emacsclient</command>.
-    </para>
-
-    <para>
-      Any setting of <varname>EDITOR</varname> in the shell config
-      files will override
-      <varname>services.emacs.defaultEditor</varname>.
-      To make sure <varname>EDITOR</varname> refers to the Emacs
-      wrapper script, remove any existing <varname>EDITOR</varname>
-      assignment from <filename>.profile</filename>,
-      <filename>.bashrc</filename>, <filename>.zshenv</filename> or
-      any other shell config file.
-    </para>
-
-    <para>
-      If you have formed certain bad habits when editing files,
-      these can be corrected with a shell alias to the wrapper
-      script:
-      <programlisting>alias vi=$EDITOR</programlisting>
-    </para>
+   <title>Configuring the <varname>EDITOR</varname> variable</title>
+
+<!--<title><command>emacsclient</command> as the Default Editor</title>-->
+
+   <para>
+    If <xref linkend="opt-services.emacs.defaultEditor"/> is
+    <literal>true</literal>, the <varname>EDITOR</varname> variable will be set
+    to a wrapper script which launches <command>emacsclient</command>.
+   </para>
+
+   <para>
+    Any setting of <varname>EDITOR</varname> in the shell config files will
+    override <varname>services.emacs.defaultEditor</varname>. To make sure
+    <varname>EDITOR</varname> refers to the Emacs wrapper script, remove any
+    existing <varname>EDITOR</varname> assignment from
+    <filename>.profile</filename>, <filename>.bashrc</filename>,
+    <filename>.zshenv</filename> or any other shell config file.
+   </para>
+
+   <para>
+    If you have formed certain bad habits when editing files, these can be
+    corrected with a shell alias to the wrapper script:
+<programlisting>alias vi=$EDITOR</programlisting>
+   </para>
   </section>
 
   <section xml:id="module-services-emacs-per-user">
-    <title>Per-User Enabling of the Service</title>
-
-    <para>
-      In general, <command>systemd</command> user services
-      are globally enabled by symlinks in
-      <filename>/etc/systemd/user</filename>. In the case where
-      Emacs daemon is not wanted for all users, it is possible to
-      install the service but not globally enable it:
+   <title>Per-User Enabling of the Service</title>
 
+   <para>
+    In general, <command>systemd</command> user services are globally enabled
+    by symlinks in <filename>/etc/systemd/user</filename>. In the case where
+    Emacs daemon is not wanted for all users, it is possible to install the
+    service but not globally enable it:
 <programlisting>
 <xref linkend="opt-services.emacs.enable"/> = false;
 <xref linkend="opt-services.emacs.install"/> = true;
 </programlisting>
-    </para>
-
-    <para>
-      To enable the <command>systemd</command> user service for just
-      the currently logged in user, run:
-
-      <programlisting>systemctl --user enable emacs</programlisting>
-
-      This will add the symlink
-      <filename>~/.config/systemd/user/emacs.service</filename>.
-    </para>
+   </para>
+
+   <para>
+    To enable the <command>systemd</command> user service for just the
+    currently logged in user, run:
+<programlisting>systemctl --user enable emacs</programlisting>
+    This will add the symlink
+    <filename>~/.config/systemd/user/emacs.service</filename>.
+   </para>
   </section>
-</section>
-
-<section xml:id="module-services-emacs-configuring">
+ </section>
+ <section xml:id="module-services-emacs-configuring">
   <title>Configuring Emacs</title>
 
   <para>
-    The Emacs init file should be changed to load the extension
-    packages at startup:
-
-    <example xml:id="module-services-emacs-package-initialisation">
-      <title>Package initialization in <filename>.emacs</filename></title>
-      <programlisting><![CDATA[
+   The Emacs init file should be changed to load the extension packages at
+   startup:
+   <example xml:id="module-services-emacs-package-initialisation">
+    <title>Package initialization in <filename>.emacs</filename></title>
+<programlisting><![CDATA[
 (require 'package)
 
 ;; optional. makes unpure packages archives unavailable
@@ -533,66 +497,71 @@ emacsclient --create-frame --tty  # opens a new frame on the current terminal
 (setq package-enable-at-startup nil)
 (package-initialize)
 ]]></programlisting>
-    </example>
+   </example>
   </para>
 
   <para>
-    After the declarative emacs package configuration has been
-    tested, previously downloaded packages can be cleaned up by
-    removing <filename>~/.emacs.d/elpa</filename> (do make a backup
-    first, in case you forgot a package).
+   After the declarative emacs package configuration has been tested,
+   previously downloaded packages can be cleaned up by removing
+   <filename>~/.emacs.d/elpa</filename> (do make a backup first, in case you
+   forgot a package).
   </para>
 
-  <!--
+<!--
       todo: is it worth documenting customizations for
       server-switch-hook, server-done-hook?
   -->
 
   <section xml:id="module-services-emacs-major-mode">
-    <title>A Major Mode for Nix Expressions</title>
+   <title>A Major Mode for Nix Expressions</title>
 
-    <para>
-      Of interest may be <varname>melpaPackages.nix-mode</varname>,
-      which provides syntax highlighting for the Nix language. This is
-      particularly convenient if you regularly edit Nix files.
-    </para>
+   <para>
+    Of interest may be <varname>melpaPackages.nix-mode</varname>, which
+    provides syntax highlighting for the Nix language. This is particularly
+    convenient if you regularly edit Nix files.
+   </para>
   </section>
 
   <section xml:id="module-services-emacs-man-pages">
-    <title>Accessing man pages</title>
-    <para>
-      You can use <function>woman</function> to get completion of all
-      available man pages. For example, type <literal>M-x woman
-      &lt;RET&gt; nixos-rebuild &lt;RET&gt;.</literal>
-    </para>
+   <title>Accessing man pages</title>
+
+   <para>
+    You can use <function>woman</function> to get completion of all available
+    man pages. For example, type <literal>M-x woman &lt;RET&gt; nixos-rebuild
+    &lt;RET&gt;.</literal>
+   </para>
   </section>
 
   <section xml:id="sec-emacs-docbook-xml">
-    <title>Editing DocBook 5 XML Documents</title>
-    <para>
-      Emacs includes <link
-      xlink:href="https://www.gnu.org/software/emacs/manual/html_node/nxml-mode/Introduction.html">nXML</link>,
-      a major-mode for validating and editing XML documents.
-      When editing DocBook 5.0 documents, such as
-      <link linkend="book-nixos-manual">this one</link>,
-      nXML needs to be configured with the relevant schema, which is
-      not included.
-    </para>
+   <title>Editing DocBook 5 XML Documents</title>
 
-    <para>
-      To install the DocBook 5.0 schemas, either add
-      <varname>pkgs.docbook5</varname> to
-      <xref linkend="opt-environment.systemPackages"/> (<link
+   <para>
+    Emacs includes
+    <link
+      xlink:href="https://www.gnu.org/software/emacs/manual/html_node/nxml-mode/Introduction.html">nXML</link>,
+    a major-mode for validating and editing XML documents. When editing DocBook
+    5.0 documents, such as <link linkend="book-nixos-manual">this one</link>,
+    nXML needs to be configured with the relevant schema, which is not
+    included.
+   </para>
+
+   <para>
+    To install the DocBook 5.0 schemas, either add
+    <varname>pkgs.docbook5</varname> to
+    <xref linkend="opt-environment.systemPackages"/>
+    (<link
       linkend="sec-declarative-package-mgmt">NixOS</link>), or run
-      <literal>nix-env -i pkgs.docbook5</literal>
-      (<link linkend="sec-ad-hoc-packages">Nix</link>).
-    </para>
-
-    <para>
-      Then customize the variable <varname>rng-schema-locating-files</varname> to include <filename>~/.emacs.d/schemas.xml</filename> and put the following text into that file:
-      <example xml:id="ex-emacs-docbook-xml">
-        <title>nXML Schema Configuration (<filename>~/.emacs.d/schemas.xml</filename>)</title>
-        <programlisting language="xml"><![CDATA[
+    <literal>nix-env -i pkgs.docbook5</literal>
+    (<link linkend="sec-ad-hoc-packages">Nix</link>).
+   </para>
+
+   <para>
+    Then customize the variable <varname>rng-schema-locating-files</varname> to
+    include <filename>~/.emacs.d/schemas.xml</filename> and put the following
+    text into that file:
+    <example xml:id="ex-emacs-docbook-xml">
+     <title>nXML Schema Configuration (<filename>~/.emacs.d/schemas.xml</filename>)</title>
+<programlisting language="xml"><![CDATA[
 <?xml version="1.0"?>
 <!--
   To let emacs find this file, evaluate:
@@ -612,9 +581,7 @@ emacsclient --create-frame --tty  # opens a new frame on the current terminal
 </locatingRules>
 ]]></programlisting>
     </example>
-  </para>
-
+   </para>
   </section>
-</section>
-
+ </section>
 </chapter>
diff --git a/nixos/modules/services/hardware/lirc.nix b/nixos/modules/services/hardware/lirc.nix
new file mode 100644
index 000000000000..5635d6f09715
--- /dev/null
+++ b/nixos/modules/services/hardware/lirc.nix
@@ -0,0 +1,89 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.lirc;
+in {
+
+  ###### interface
+
+  options = {
+    services.lirc = {
+
+      enable = mkEnableOption "LIRC daemon";
+
+      options = mkOption {
+        type = types.lines;
+        example = ''
+          [lircd]
+          nodaemon = False
+        '';
+        description = "LIRC default options descriped in man:lircd(8) (<filename>lirc_options.conf</filename>)";
+      };
+
+      configs = mkOption {
+        type = types.listOf types.lines;
+        description = "Configurations for lircd to load, see man:lircd.conf(5) for details (<filename>lircd.conf</filename>)";
+      };
+
+      extraArguments = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        description = "Extra arguments to lircd.";
+      };
+
+    };
+  };
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    # Note: LIRC executables raises a warning, if lirc_options.conf do not exists
+    environment.etc."lirc/lirc_options.conf".text = cfg.options;
+
+    environment.systemPackages = [ pkgs.lirc ];
+
+    systemd.sockets.lircd = {
+      description = "LIRC daemon socket";
+      wantedBy = [ "sockets.target" ];
+      socketConfig = {
+        ListenStream = "/run/lirc/lircd";
+        SocketUser = "lirc";
+        SocketMode = "0660";
+      };
+    };
+
+    systemd.services.lircd = let
+      configFile = pkgs.writeText "lircd.conf" (builtins.concatStringsSep "\n" cfg.configs);
+    in {
+      description = "LIRC daemon service";
+      after = [ "network.target" ];
+
+      unitConfig.Documentation = [ "man:lircd(8)" ];
+
+      serviceConfig = {
+        RuntimeDirectory = "lirc";
+
+        # socket lives in runtime directory; we have to keep is available
+        RuntimeDirectoryPreserve = true;
+
+        ExecStart = ''
+          ${pkgs.lirc}/bin/lircd --nodaemon \
+            ${escapeShellArgs cfg.extraArguments} \
+            ${configFile}
+        '';
+        User = "lirc";
+      };
+    };
+
+    users.users.lirc = {
+      uid = config.ids.uids.lirc;
+      group = "lirc";
+      description = "LIRC user for lircd";
+    };
+
+    users.groups.lirc.gid = config.ids.gids.lirc;
+  };
+}
diff --git a/nixos/modules/services/hardware/ratbagd.nix b/nixos/modules/services/hardware/ratbagd.nix
new file mode 100644
index 000000000000..103e1d2315ae
--- /dev/null
+++ b/nixos/modules/services/hardware/ratbagd.nix
@@ -0,0 +1,32 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.ratbagd;
+in
+{
+  ###### interface
+
+  options = {
+    services.ratbagd = {
+      enable = mkOption {
+        default = false;
+        description = ''
+          Whether to enable ratbagd for configuring gaming mice.
+        '';
+      };
+    };
+  };
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+    # Give users access to the "ratbagctl" tool
+    environment.systemPackages = [ pkgs.libratbag ];
+
+    services.dbus.packages = [ pkgs.libratbag ];
+
+    systemd.packages = [ pkgs.libratbag ];
+  };
+}
diff --git a/nixos/modules/services/hardware/trezord.nix b/nixos/modules/services/hardware/trezord.nix
index f2ec00a7d3e1..c06a0665d02f 100644
--- a/nixos/modules/services/hardware/trezord.nix
+++ b/nixos/modules/services/hardware/trezord.nix
@@ -26,15 +26,14 @@ in {
       name = "trezord-udev-rules";
       destination = "/etc/udev/rules.d/51-trezor.rules";
       text = ''
-        # Trezor 1
-        SUBSYSTEM=="usb",  ATTR{idVendor}=="534c",  ATTR{idProduct}=="0001",  MODE="0666", GROUP="dialout", SYMLINK+="trezor%n"
-        KERNEL=="hidraw*", ATTRS{idVendor}=="534c", ATTRS{idProduct}=="0001", MODE="0666", GROUP="dialout"
-
-        # Trezor 2 (Model-T)
-        SUBSYSTEM=="usb",  ATTR{idVendor}=="1209",  ATTR{idProduct}=="53c0",  MODE="0661", GROUP="dialout", TAG+="uaccess", TAG+="udev-acl", SYMLINK+="trezor%n"
-        SUBSYSTEM=="usb",  ATTR{idVendor}=="1209",  ATTR{idProduct}=="53c1",  MODE="0660", GROUP="dialout", TAG+="uaccess", TAG+="udev-acl", SYMLINK+="trezor%n"
-        KERNEL=="hidraw*", ATTRS{idVendor}=="1209", ATTRS{idProduct}=="53c1", MODE="0660", GROUP="dialout", TAG+="uaccess", TAG+="udev-acl"
-  ];
+        # TREZOR v1 (One)
+        SUBSYSTEM=="usb", ATTR{idVendor}=="534c", ATTR{idProduct}=="0001", MODE="0660", GROUP="trezord", TAG+="uaccess", SYMLINK+="trezor%n"
+        KERNEL=="hidraw*", ATTRS{idVendor}=="534c", ATTRS{idProduct}=="0001", MODE="0660", GROUP="trezord", TAG+="uaccess"
+
+        # TREZOR v2 (T)
+        SUBSYSTEM=="usb", ATTR{idVendor}=="1209", ATTR{idProduct}=="53c0", MODE="0660", GROUP="trezord", TAG+="uaccess", SYMLINK+="trezor%n"
+        SUBSYSTEM=="usb", ATTR{idVendor}=="1209", ATTR{idProduct}=="53c1", MODE="0660", GROUP="trezord", TAG+="uaccess", SYMLINK+="trezor%n"
+        KERNEL=="hidraw*", ATTRS{idVendor}=="1209", ATTRS{idProduct}=="53c1", MODE="0660", GROUP="trezord", TAG+="uaccess"
       '';
     });
 
diff --git a/nixos/modules/services/hardware/triggerhappy.nix b/nixos/modules/services/hardware/triggerhappy.nix
new file mode 100644
index 000000000000..81d4a1ae65bf
--- /dev/null
+++ b/nixos/modules/services/hardware/triggerhappy.nix
@@ -0,0 +1,114 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.triggerhappy;
+
+  socket = "/run/thd.socket";
+
+  configFile = pkgs.writeText "triggerhappy.conf" ''
+    ${concatMapStringsSep "\n"
+      ({ keys, event, cmd, ... }:
+        ''${concatMapStringsSep "+" (x: "KEY_" + x) keys} ${toString { press = 1; hold = 2; release = 0; }.${event}} ${cmd}''
+      )
+      cfg.bindings}
+    ${cfg.extraConfig}
+  '';
+
+  bindingCfg = { config, ... }: {
+    options = {
+
+      keys = mkOption {
+        type = types.listOf types.str;
+        description = "List of keys to match.  Key names as defined in linux/input-event-codes.h";
+      };
+
+      event = mkOption {
+        type = types.enum ["press" "hold" "release"];
+        default = "press";
+        description = "Event to match.";
+      };
+
+      cmd = mkOption {
+        type = types.str;
+        description = "What to run.";
+      };
+
+    };
+  };
+
+in
+
+{
+
+  ###### interface
+
+  options = {
+
+    services.triggerhappy = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to enable the <command>triggerhappy</command> hotkey daemon.
+        '';
+      };
+
+      bindings = mkOption {
+        type = types.listOf (types.submodule bindingCfg);
+        default = [];
+        example = lib.literalExample ''
+          [ { keys = ["PLAYPAUSE"];  cmd = "''${pkgs.mpc_cli}/bin/mpc -q toggle"; } ]
+        '';
+        description = ''
+          Key bindings for <command>triggerhappy</command>.
+        '';
+      };
+
+      extraConfig = mkOption {
+        type = types.lines;
+        default = "";
+        description = ''
+          Literal contents to append to the end of <command>triggerhappy</command> configuration file.
+        '';
+      };
+
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    systemd.sockets.triggerhappy = {
+      description = "Triggerhappy Socket";
+      wantedBy = [ "sockets.target" ];
+      socketConfig.ListenDatagram = socket;
+    };
+
+    systemd.services.triggerhappy = {
+      wantedBy = [ "multi-user.target" ];
+      after = [ "local-fs.target" ];
+      description = "Global hotkey daemon";
+      serviceConfig = {
+        ExecStart = "${pkgs.triggerhappy}/bin/thd --user nobody --socket ${socket} --triggers ${configFile} --deviceglob /dev/input/event*";
+      };
+    };
+
+    services.udev.packages = lib.singleton (pkgs.writeTextFile {
+      name = "triggerhappy-udev-rules";
+      destination = "/etc/udev/rules.d/61-triggerhappy.rules";
+      text = ''
+        ACTION=="add", SUBSYSTEM=="input", KERNEL=="event[0-9]*", ATTRS{name}!="triggerhappy", \
+          RUN+="${pkgs.triggerhappy}/bin/th-cmd --socket ${socket} --passfd --udev"
+      '';
+    });
+
+  };
+
+}
diff --git a/nixos/modules/services/hardware/upower.nix b/nixos/modules/services/hardware/upower.nix
index 2198842a4511..1da47349c077 100644
--- a/nixos/modules/services/hardware/upower.nix
+++ b/nixos/modules/services/hardware/upower.nix
@@ -56,6 +56,32 @@ in
           { Type = "dbus";
             BusName = "org.freedesktop.UPower";
             ExecStart = "@${cfg.package}/libexec/upowerd upowerd";
+            Restart = "on-failure";
+            # Upstream lockdown:
+            # Filesystem lockdown
+            ProtectSystem = "strict";
+            # Needed by keyboard backlight support
+            ProtectKernelTunables = false;
+            ProtectControlGroups = true;
+            ReadWritePaths = "/var/lib/upower";
+            ProtectHome = true;
+            PrivateTmp = true;
+
+            # Network
+            # PrivateNetwork=true would block udev's netlink socket
+            RestrictAddressFamilies = "AF_UNIX AF_NETLINK";
+
+            # Execute Mappings
+            MemoryDenyWriteExecute = true;
+
+            # Modules
+            ProtectKernelModules = true;
+
+            # Real-time
+            RestrictRealtime = true;
+
+            # Privilege escalation
+            NoNewPrivileges = true;
           };
       };
 
diff --git a/nixos/modules/services/logging/journaldriver.nix b/nixos/modules/services/logging/journaldriver.nix
index 74ac3d4c2365..9bd581e9ec0e 100644
--- a/nixos/modules/services/logging/journaldriver.nix
+++ b/nixos/modules/services/logging/journaldriver.nix
@@ -7,7 +7,7 @@
 # to be set.
 #
 # For further information please consult the documentation in the
-# upstream repository at: https://github.com/aprilabank/journaldriver/
+# upstream repository at: https://github.com/tazjin/journaldriver/
 
 { config, lib, pkgs, ...}:
 
diff --git a/nixos/modules/services/mail/clamsmtp.nix b/nixos/modules/services/mail/clamsmtp.nix
index 8f4f39aa7288..fc1267c5d280 100644
--- a/nixos/modules/services/mail/clamsmtp.nix
+++ b/nixos/modules/services/mail/clamsmtp.nix
@@ -176,4 +176,6 @@ in
         }
       ) cfg.instances);
     };
+
+  meta.maintainers = with lib.maintainers; [ ekleog ];
 }
diff --git a/nixos/modules/services/mail/dkimproxy-out.nix b/nixos/modules/services/mail/dkimproxy-out.nix
index 894b88e25c1b..f4ac9e47007a 100644
--- a/nixos/modules/services/mail/dkimproxy-out.nix
+++ b/nixos/modules/services/mail/dkimproxy-out.nix
@@ -115,4 +115,6 @@ in
         };
       };
     };
+
+  meta.maintainers = with lib.maintainers; [ ekleog ];
 }
diff --git a/nixos/modules/services/mail/dovecot.nix b/nixos/modules/services/mail/dovecot.nix
index e6091182b2ab..30ad7d82fb80 100644
--- a/nixos/modules/services/mail/dovecot.nix
+++ b/nixos/modules/services/mail/dovecot.nix
@@ -311,7 +311,7 @@ in
       { name = "dovenull";
         uid = config.ids.uids.dovenull2;
         description = "Dovecot user for untrusted logins";
-        group = cfg.group;
+        group = "dovenull";
       }
     ] ++ optional (cfg.user == "dovecot2")
          { name = "dovecot2";
@@ -332,6 +332,10 @@ in
       }
     ++ optional (cfg.createMailUser && cfg.mailGroup != null)
       { name = cfg.mailGroup;
+      }
+    ++ singleton
+      { name = "dovenull";
+        gid = config.ids.gids.dovenull2;
       };
 
     environment.etc."dovecot/modules".source = modulesDir;
diff --git a/nixos/modules/services/mail/exim.nix b/nixos/modules/services/mail/exim.nix
index 06c4b2811b3f..c05811291359 100644
--- a/nixos/modules/services/mail/exim.nix
+++ b/nixos/modules/services/mail/exim.nix
@@ -2,7 +2,7 @@
 
 let
   inherit (lib) mkIf mkOption singleton types;
-  inherit (pkgs) coreutils exim;
+  inherit (pkgs) coreutils;
   cfg = config.services.exim;
 in
 
@@ -57,6 +57,16 @@ in
         '';
       };
 
+      package = mkOption {
+        type = types.package;
+        default = pkgs.exim;
+        defaultText = "pkgs.exim";
+        description = ''
+          The Exim derivation to use.
+          This can be used to enable features such as LDAP or PAM support.
+        '';
+      };
+
     };
 
   };
@@ -74,7 +84,7 @@ in
         spool_directory = ${cfg.spoolDir}
         ${cfg.config}
       '';
-      systemPackages = [ exim ];
+      systemPackages = [ cfg.package ];
     };
 
     users.users = singleton {
@@ -89,14 +99,14 @@ in
       gid = config.ids.gids.exim;
     };
 
-    security.wrappers.exim.source = "${exim}/bin/exim";
+    security.wrappers.exim.source = "${cfg.package}/bin/exim";
 
     systemd.services.exim = {
       description = "Exim Mail Daemon";
       wantedBy = [ "multi-user.target" ];
       restartTriggers = [ config.environment.etc."exim.conf".source ];
       serviceConfig = {
-        ExecStart   = "${exim}/bin/exim -bdf -q30m";
+        ExecStart   = "${cfg.package}/bin/exim -bdf -q30m";
         ExecReload  = "${coreutils}/bin/kill -HUP $MAINPID";
       };
       preStart = ''
diff --git a/nixos/modules/services/mail/postfix.nix b/nixos/modules/services/mail/postfix.nix
index 33249aa3e554..d43733484ffa 100644
--- a/nixos/modules/services/mail/postfix.nix
+++ b/nixos/modules/services/mail/postfix.nix
@@ -602,7 +602,7 @@ in
             target = "postfix";
           };
 
-        # This makes comfortable for root to run 'postqueue' for example.
+        # This makes it comfortable to run 'postqueue/postdrop' for example.
         systemPackages = [ pkgs.postfix ];
       };
 
@@ -616,6 +616,22 @@ in
         setgid = true;
       };
 
+      security.wrappers.postqueue = {
+        program = "postqueue";
+        source = "${pkgs.postfix}/bin/postqueue";
+        group = setgidGroup;
+        setuid = false;
+        setgid = true;
+      };
+
+      security.wrappers.postdrop = {
+        program = "postdrop";
+        source = "${pkgs.postfix}/bin/postdrop";
+        group = setgidGroup;
+        setuid = false;
+        setgid = true;
+      };
+
       users.users = optional (user == "postfix")
         { name = "postfix";
           description = "Postfix mail server user";
diff --git a/nixos/modules/services/mail/rmilter.nix b/nixos/modules/services/mail/rmilter.nix
index 7f38d7570132..492c64583219 100644
--- a/nixos/modules/services/mail/rmilter.nix
+++ b/nixos/modules/services/mail/rmilter.nix
@@ -52,7 +52,7 @@ in
 
       enable = mkOption {
         type = types.bool;
-        default = cfg.rspamd.enable;
+        default = false;
         description = "Whether to run the rmilter daemon.";
       };
 
@@ -89,7 +89,7 @@ in
 
       bindSocket.path = mkOption {
        type = types.str;
-       default = "/run/rmilter/rmilter.sock";
+       default = "/run/rmilter.sock";
        description = ''
           Path to Unix domain socket to listen on.
         '';
@@ -193,6 +193,9 @@ in
   config = mkMerge [
 
     (mkIf cfg.enable {
+      warnings = [
+        ''`config.services.rmilter' is deprecated, `rmilter' deprecated and unsupported by upstream, and will be removed from next releases. Use built-in rspamd milter instead.''
+      ];
 
       users.users = singleton {
         name = cfg.user;
diff --git a/nixos/modules/services/mail/rspamd.nix b/nixos/modules/services/mail/rspamd.nix
index b3dae60c2c7e..ff01a5dee53d 100644
--- a/nixos/modules/services/mail/rspamd.nix
+++ b/nixos/modules/services/mail/rspamd.nix
@@ -115,36 +115,10 @@ let
     };
   };
 
-  indexOf = default: start: list: e:
-    if list == []
-    then default
-    else if (head list) == e then start
-    else (indexOf default (start + (length (listenStreams (head list).socket))) (tail list) e);
-
-  systemdSocket = indexOf (abort "Socket not found") 0 allSockets;
-
   isUnixSocket = socket: hasPrefix "/" (if (isString socket) then socket else socket.socket);
-  isPort = hasPrefix "*:";
-  isIPv4Socket = hasPrefix "*v4:";
-  isIPv6Socket = hasPrefix "*v6:";
-  isLocalHost = hasPrefix "localhost:";
-  listenStreams = socket:
-    if (isLocalHost socket) then
-      let port = (removePrefix "localhost:" socket);
-      in [ "127.0.0.1:${port}" ] ++ (if config.networking.enableIPv6 then ["[::1]:${port}"] else [])
-    else if (isIPv6Socket socket) then [removePrefix "*v6:" socket]
-    else if (isPort socket) then [removePrefix "*:" socket]
-    else if (isIPv4Socket socket) then
-      throw "error: IPv4 only socket not supported in rspamd with socket activation"
-    else if (length (splitString " " socket)) != 1 then
-      throw "error: string options not supported in rspamd with socket activation"
-    else [socket];
-
-  mkBindSockets = enabled: socks: concatStringsSep "\n  " (flatten (map (each:
-    if cfg.socketActivation && enabled != false then
-      let systemd = (systemdSocket each);
-      in (imap (idx: e: "bind_socket = \"systemd:${toString (systemd + idx - 1)}\";") (listenStreams each.socket))
-    else "bind_socket = \"${each.rawEntry}\";") socks));
+
+  mkBindSockets = enabled: socks: concatStringsSep "\n  "
+    (flatten (map (each: "bind_socket = \"${each.rawEntry}\";") socks));
 
   rspamdConfFile = pkgs.writeText "rspamd.conf"
     ''
@@ -175,18 +149,6 @@ let
       ${cfg.extraConfig}
    '';
 
-  allMappedSockets = flatten (mapAttrsToList (name: value:
-    if value.enable != false
-    then imap (idx: each: {
-        name = "${name}";
-        index = idx;
-        value = each;
-      }) value.bindSockets
-    else []) cfg.workers);
-  allSockets = map (e: e.value) allMappedSockets;
-
-  allSocketNames = map (each: "rspamd-${each.name}-${toString each.index}.socket") allMappedSockets;
-
 in
 
 {
@@ -197,7 +159,7 @@ in
 
     services.rspamd = {
 
-      enable = mkEnableOption "Whether to run the rspamd daemon.";
+      enable = mkEnableOption "rspamd, the Rapid spam filtering system";
 
       debug = mkOption {
         type = types.bool;
@@ -205,13 +167,6 @@ in
         description = "Whether to run the rspamd daemon in debug mode.";
       };
 
-      socketActivation = mkOption {
-        type = types.bool;
-        description = ''
-          Enable systemd socket activation for rspamd.
-        '';
-      };
-
       workers = mkOption {
         type = with types; attrsOf (submodule workerOpts);
         description = ''
@@ -272,13 +227,6 @@ in
 
   config = mkIf cfg.enable {
 
-    services.rspamd.socketActivation = mkDefault (!opts.bindSocket.isDefined && !opts.bindUISocket.isDefined);
-
-    assertions = [ {
-      assertion = !cfg.socketActivation || !(opts.bindSocket.isDefined || opts.bindUISocket.isDefined);
-      message = "Can't use socketActivation for rspamd when using renamed bind socket options";
-    } ];
-
     # Allow users to run 'rspamc' and 'rspamadm'.
     environment.systemPackages = [ pkgs.rspamd ];
 
@@ -299,17 +247,14 @@ in
     systemd.services.rspamd = {
       description = "Rspamd Service";
 
-      wantedBy = mkIf (!cfg.socketActivation) [ "multi-user.target" ];
-      after = [ "network.target" ] ++
-       (if cfg.socketActivation then allSocketNames else []);
-      requires = mkIf cfg.socketActivation allSocketNames;
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
 
       serviceConfig = {
         ExecStart = "${pkgs.rspamd}/bin/rspamd ${optionalString cfg.debug "-d"} --user=${cfg.user} --group=${cfg.group} --pid=/run/rspamd.pid -c ${rspamdConfFile} -f";
         Restart = "always";
         RuntimeDirectory = "rspamd";
         PrivateTmp = true;
-        Sockets = mkIf cfg.socketActivation (concatStringsSep " " allSocketNames);
       };
 
       preStart = ''
@@ -317,24 +262,10 @@ in
         ${pkgs.coreutils}/bin/chown ${cfg.user}:${cfg.group} /var/lib/rspamd
       '';
     };
-    systemd.sockets = mkIf cfg.socketActivation
-      (listToAttrs (map (each: {
-        name = "rspamd-${each.name}-${toString each.index}";
-        value = {
-          description = "Rspamd socket ${toString each.index} for worker ${each.name}";
-          wantedBy = [ "sockets.target" ];
-          listenStreams = (listenStreams each.value.socket);
-          socketConfig = {
-            BindIPv6Only = mkIf (isIPv6Socket each.value.socket) "ipv6-only";
-            Service = "rspamd.service";
-            SocketUser = mkIf (isUnixSocket each.value.socket) each.value.owner;
-            SocketGroup = mkIf (isUnixSocket each.value.socket) each.value.group;
-            SocketMode = mkIf (isUnixSocket each.value.socket) each.value.mode;
-          };
-        };
-      }) allMappedSockets));
   };
   imports = [
+    (mkRemovedOptionModule [ "services" "rspamd" "socketActivation" ]
+	     "Socket activation never worked correctly and could at this time not be fixed and so was removed")
     (mkRenamedOptionModule [ "services" "rspamd" "bindSocket" ] [ "services" "rspamd" "workers" "normal" "bindSockets" ])
     (mkRenamedOptionModule [ "services" "rspamd" "bindUISocket" ] [ "services" "rspamd" "workers" "controller" "bindSockets" ])
   ];
diff --git a/nixos/modules/services/misc/airsonic.nix b/nixos/modules/services/misc/airsonic.nix
index 083587b8ebb1..01d7b3cf6b9d 100644
--- a/nixos/modules/services/misc/airsonic.nix
+++ b/nixos/modules/services/misc/airsonic.nix
@@ -73,6 +73,24 @@ in {
           ${cfg.home}/transcoders.
         '';
       };
+
+      jvmOptions = mkOption {
+        description = ''
+          Extra command line options for the JVM running AirSonic.
+          Useful for sending jukebox output to non-default alsa
+          devices.
+        '';
+        default = [
+        ];
+        type = types.listOf types.str;
+        example = [
+          "-Djavax.sound.sampled.Clip='#CODEC [plughw:1,0]'"
+          "-Djavax.sound.sampled.Port='#Port CODEC [hw:1]'"
+          "-Djavax.sound.sampled.SourceDataLine='#CODEC [plughw:1,0]'"
+          "-Djavax.sound.sampled.TargetDataLine='#CODEC [plughw:1,0]'"
+        ];
+      };
+
     };
   };
 
@@ -98,6 +116,7 @@ in {
           -Dserver.port=${toString cfg.port} \
           -Dairsonic.contextPath=${cfg.contextPath} \
           -Djava.awt.headless=true \
+          ${toString cfg.jvmOptions} \
           -verbose:gc \
           -jar ${pkgs.airsonic}/webapps/airsonic.war
         '';
diff --git a/nixos/modules/services/misc/emby.nix b/nixos/modules/services/misc/emby.nix
index 64cc9c610ac3..0ad4a3f7376f 100644
--- a/nixos/modules/services/misc/emby.nix
+++ b/nixos/modules/services/misc/emby.nix
@@ -36,11 +36,18 @@ in
       after = [ "network.target" ];
       wantedBy = [ "multi-user.target" ];
       preStart = ''
-        test -d ${cfg.dataDir} || {
-          echo "Creating initial Emby data directory in ${cfg.dataDir}"
-          mkdir -p ${cfg.dataDir}
-          chown -R ${cfg.user}:${cfg.group} ${cfg.dataDir}
-          }
+        if [ -d ${cfg.dataDir} ]
+        then
+            for plugin in ${cfg.dataDir}/plugins/*
+            do
+                echo "Correcting permissions of plugin: $plugin"
+                chmod u+w $plugin
+            done
+        else
+            echo "Creating initial Emby data directory in ${cfg.dataDir}"
+            mkdir -p ${cfg.dataDir}
+            chown -R ${cfg.user}:${cfg.group} ${cfg.dataDir}
+        fi
       '';
 
       serviceConfig = {
@@ -48,7 +55,7 @@ in
         User = cfg.user;
         Group = cfg.group;
         PermissionsStartOnly = "true";
-        ExecStart = "${pkgs.emby}/bin/MediaBrowser.Server.Mono";
+        ExecStart = "${pkgs.emby}/bin/emby -programdata ${cfg.dataDir}";
         Restart = "on-failure";
       };
     };
diff --git a/nixos/modules/services/misc/gitlab.nix b/nixos/modules/services/misc/gitlab.nix
index 5bf66354f487..8ea831afb7c1 100644
--- a/nixos/modules/services/misc/gitlab.nix
+++ b/nixos/modules/services/misc/gitlab.nix
@@ -53,6 +53,7 @@ let
     repos_path: "${cfg.statePath}/repositories"
     secret_file: "${cfg.statePath}/config/gitlab_shell_secret"
     log_file: "${cfg.statePath}/log/gitlab-shell.log"
+    custom_hooks_dir: "${cfg.statePath}/custom_hooks"
     redis:
       bin: ${pkgs.redis}/bin/redis-cli
       host: 127.0.0.1
@@ -162,7 +163,7 @@ let
       makeWrapper ${cfg.packages.gitlab.rubyEnv}/bin/rake $out/bin/gitlab-rake \
           ${concatStrings (mapAttrsToList (name: value: "--set ${name} '${value}' ") gitlabEnv)} \
           --set GITLAB_CONFIG_PATH '${cfg.statePath}/config' \
-          --set PATH '${lib.makeBinPath [ pkgs.nodejs pkgs.gzip pkgs.git pkgs.gnutar config.services.postgresql.package ]}:$PATH' \
+          --set PATH '${lib.makeBinPath [ pkgs.nodejs pkgs.gzip pkgs.git pkgs.gnutar config.services.postgresql.package pkgs.coreutils pkgs.procps ]}:$PATH' \
           --set RAKEOPT '-f ${cfg.packages.gitlab}/share/gitlab/Rakefile' \
           --run 'cd ${cfg.packages.gitlab}/share/gitlab'
      '';
@@ -203,6 +204,7 @@ in {
         default = pkgs.gitlab;
         defaultText = "pkgs.gitlab";
         description = "Reference to the gitlab package";
+        example = "pkgs.gitlab-ee";
       };
 
       packages.gitlab-shell = mkOption {
@@ -501,7 +503,7 @@ in {
     };
 
     systemd.services.gitlab-workhorse = {
-      after = [ "network.target" "gitlab.service" ];
+      after = [ "network.target" ];
       wantedBy = [ "multi-user.target" ];
       environment.HOME = gitlabEnv.HOME;
       environment.GITLAB_SHELL_CONFIG_PATH = gitlabEnv.GITLAB_SHELL_CONFIG_PATH;
@@ -561,6 +563,9 @@ in {
         mkdir -p ${cfg.statePath}/shell
         mkdir -p ${cfg.statePath}/db
         mkdir -p ${cfg.statePath}/uploads
+        mkdir -p ${cfg.statePath}/custom_hooks/pre-receive.d
+        mkdir -p ${cfg.statePath}/custom_hooks/post-receive.d
+        mkdir -p ${cfg.statePath}/custom_hooks/update.d
 
         rm -rf ${cfg.statePath}/config ${cfg.statePath}/shell/hooks
         mkdir -p ${cfg.statePath}/config
@@ -569,9 +574,9 @@ in {
 
         mkdir -p /run/gitlab
         mkdir -p ${cfg.statePath}/log
-        ln -sf ${cfg.statePath}/log /run/gitlab/log
-        ln -sf ${cfg.statePath}/tmp /run/gitlab/tmp
-        ln -sf ${cfg.statePath}/uploads /run/gitlab/uploads
+        [ -d /run/gitlab/log ] || ln -sf ${cfg.statePath}/log /run/gitlab/log
+        [ -d /run/gitlab/tmp ] || ln -sf ${cfg.statePath}/tmp /run/gitlab/tmp
+        [ -d /run/gitlab/uploads ] || ln -sf ${cfg.statePath}/uploads /run/gitlab/uploads
         ln -sf $GITLAB_SHELL_CONFIG_PATH /run/gitlab/shell-config.yml
         chown -R ${cfg.user}:${cfg.group} /run/gitlab
 
@@ -629,6 +634,10 @@ in {
           touch "${cfg.statePath}/db-seeded"
         fi
 
+        # The gitlab:shell:setup regenerates the authorized_keys file so that
+        # the store path to the gitlab-shell in it gets updated
+        ${pkgs.sudo}/bin/sudo -u ${cfg.user} force=yes ${gitlab-rake}/bin/gitlab-rake gitlab:shell:setup RAILS_ENV=production
+
         # The gitlab:shell:create_hooks task seems broken for fixing links
         # so we instead delete all the hooks and create them anew
         rm -f ${cfg.statePath}/repositories/**/*.git/hooks
diff --git a/nixos/modules/services/misc/gitlab.xml b/nixos/modules/services/misc/gitlab.xml
index 67b9f1d3e474..ab99d7bd3a60 100644
--- a/nixos/modules/services/misc/gitlab.xml
+++ b/nixos/modules/services/misc/gitlab.xml
@@ -3,20 +3,22 @@
          xmlns:xi="http://www.w3.org/2001/XInclude"
          version="5.0"
          xml:id="module-services-gitlab">
-
-<title>Gitlab</title>
-
-<para>Gitlab is a feature-rich git hosting service.</para>
-
-<section xml:id="module-services-gitlab-prerequisites"><title>Prerequisites</title>
-
-<para>The gitlab service exposes only an Unix socket at
-<literal>/run/gitlab/gitlab-workhorse.socket</literal>. You need to configure a
-webserver to proxy HTTP requests to the socket.</para>
-
-<para>For instance, the following configuration could be used to use nginx as
-    frontend proxy:
-
+ <title>Gitlab</title>
+ <para>
+  Gitlab is a feature-rich git hosting service.
+ </para>
+ <section xml:id="module-services-gitlab-prerequisites">
+  <title>Prerequisites</title>
+
+  <para>
+   The gitlab service exposes only an Unix socket at
+   <literal>/run/gitlab/gitlab-workhorse.socket</literal>. You need to
+   configure a webserver to proxy HTTP requests to the socket.
+  </para>
+
+  <para>
+   For instance, the following configuration could be used to use nginx as
+   frontend proxy:
 <programlisting>
 <link linkend="opt-services.nginx.enable">services.nginx</link> = {
   <link linkend="opt-services.nginx.enable">enable</link> = true;
@@ -31,21 +33,24 @@ webserver to proxy HTTP requests to the socket.</para>
   };
 };
 </programlisting>
-</para>
-
-</section>
-
-<section xml:id="module-services-gitlab-configuring"><title>Configuring</title>
-
-<para>Gitlab depends on both PostgreSQL and Redis and will automatically enable
-both services. In the case of PostgreSQL, a database and a role will be created.
-</para>
-
-<para>The default state dir is <literal>/var/gitlab/state</literal>. This is where
-all data like the repositories and uploads will be stored.</para>
-
-<para>A basic configuration with some custom settings could look like this:
-
+  </para>
+ </section>
+ <section xml:id="module-services-gitlab-configuring">
+  <title>Configuring</title>
+
+  <para>
+   Gitlab depends on both PostgreSQL and Redis and will automatically enable
+   both services. In the case of PostgreSQL, a database and a role will be
+   created.
+  </para>
+
+  <para>
+   The default state dir is <literal>/var/gitlab/state</literal>. This is where
+   all data like the repositories and uploads will be stored.
+  </para>
+
+  <para>
+   A basic configuration with some custom settings could look like this:
 <programlisting>
 services.gitlab = {
   <link linkend="opt-services.gitlab.enable">enable</link> = true;
@@ -105,40 +110,41 @@ services.gitlab = {
   };
 };
 </programlisting>
-</para>
-
-<para>If you're setting up a new Gitlab instance, generate new secrets. You
-for instance use <literal>tr -dc A-Za-z0-9 &lt; /dev/urandom | head -c 128</literal>
-to generate a new secret. Gitlab encrypts sensitive data stored in the database.
-If you're restoring an existing Gitlab instance, you must specify the secrets
-secret from <literal>config/secrets.yml</literal> located in your Gitlab state
-folder.</para>
-
-<para>Refer to <xref linkend="ch-options" /> for all available configuration
-options for the <link linkend="opt-services.gitlab.enable">services.gitlab</link> module.</para>
-
-</section>
-
-<section xml:id="module-services-gitlab-maintenance"><title>Maintenance</title>
-
-<para>You can run Gitlab's rake tasks with <literal>gitlab-rake</literal>
-which will be available on the system when gitlab is enabled. You will
-have to run the command as the user that you configured to run gitlab
-with.</para>
-
-<para>For example, to backup a Gitlab instance:
-
+  </para>
+
+  <para>
+   If you're setting up a new Gitlab instance, generate new secrets. You for
+   instance use <literal>tr -dc A-Za-z0-9 &lt; /dev/urandom | head -c
+   128</literal> to generate a new secret. Gitlab encrypts sensitive data
+   stored in the database. If you're restoring an existing Gitlab instance, you
+   must specify the secrets secret from <literal>config/secrets.yml</literal>
+   located in your Gitlab state folder.
+  </para>
+
+  <para>
+   Refer to <xref linkend="ch-options" /> for all available configuration
+   options for the
+   <link linkend="opt-services.gitlab.enable">services.gitlab</link> module.
+  </para>
+ </section>
+ <section xml:id="module-services-gitlab-maintenance">
+  <title>Maintenance</title>
+
+  <para>
+   You can run Gitlab's rake tasks with <literal>gitlab-rake</literal> which
+   will be available on the system when gitlab is enabled. You will have to run
+   the command as the user that you configured to run gitlab with.
+  </para>
+
+  <para>
+   For example, to backup a Gitlab instance:
 <programlisting>
 $ sudo -u git -H gitlab-rake gitlab:backup:create
 </programlisting>
-
-A list of all availabe rake tasks can be obtained by running:
-
+   A list of all availabe rake tasks can be obtained by running:
 <programlisting>
 $ sudo -u git -H gitlab-rake -T
 </programlisting>
-</para>
-
-</section>
-
+  </para>
+ </section>
 </chapter>
diff --git a/nixos/modules/services/misc/home-assistant.nix b/nixos/modules/services/misc/home-assistant.nix
index 0756e81612ac..2e9aa33aeeee 100644
--- a/nixos/modules/services/misc/home-assistant.nix
+++ b/nixos/modules/services/misc/home-assistant.nix
@@ -157,6 +157,7 @@ in {
         Restart = "on-failure";
         ProtectSystem = "strict";
         ReadWritePaths = "${cfg.configDir}";
+        KillSignal = "SIGINT";
         PrivateTmp = true;
         RemoveIPC = true;
       };
diff --git a/nixos/modules/services/misc/lidarr.nix b/nixos/modules/services/misc/lidarr.nix
new file mode 100644
index 000000000000..627f22334fe8
--- /dev/null
+++ b/nixos/modules/services/misc/lidarr.nix
@@ -0,0 +1,46 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+  cfg = config.services.lidarr;
+in
+{
+  options = {
+    services.lidarr = {
+      enable = mkEnableOption "Lidarr";
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.lidarr = {
+      description = "Lidarr";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      preStart = ''
+        [ ! -d /var/lib/lidarr ] && mkdir -p /var/lib/lidarr
+        chown -R lidarr:lidarr /var/lib/lidarr
+      '';
+
+      serviceConfig = {
+        Type = "simple";
+        User = "lidarr";
+        Group = "lidarr";
+        PermissionsStartOnly = "true";
+        ExecStart = "${pkgs.lidarr}/bin/Lidarr";
+        Restart = "on-failure";
+
+        StateDirectory = "/var/lib/lidarr/";
+        StateDirectoryMode = "0770";
+      };
+    };
+
+    users.users.lidarr = {
+      uid = config.ids.uids.lidarr;
+      home = "/var/lib/lidarr";
+      group = "lidarr";
+    };
+
+    users.groups.lidarr.gid = config.ids.gids.lidarr;
+  };
+}
diff --git a/nixos/modules/services/misc/nix-daemon.nix b/nixos/modules/services/misc/nix-daemon.nix
index c0eb882c58f3..5e171c08d893 100644
--- a/nixos/modules/services/misc/nix-daemon.nix
+++ b/nixos/modules/services/misc/nix-daemon.nix
@@ -345,7 +345,6 @@ in
         type = types.listOf types.str;
         default =
           [
-            "$HOME/.nix-defexpr/channels"
             "nixpkgs=/nix/var/nix/profiles/per-user/root/channels/nixos"
             "nixos-config=/etc/nixos/configuration.nix"
             "/nix/var/nix/profiles/per-user/root/channels"
@@ -400,8 +399,8 @@ in
     systemd.sockets.nix-daemon.wantedBy = [ "sockets.target" ];
 
     systemd.services.nix-daemon =
-      { path = [ nix pkgs.utillinux ]
-          ++ optionals cfg.distributedBuilds [ config.programs.ssh.package pkgs.gzip ]
+      { path = [ nix pkgs.utillinux config.programs.ssh.package ]
+          ++ optionals cfg.distributedBuilds [ pkgs.gzip ]
           ++ optionals (!isNix20) [ pkgs.openssl.bin ];
 
         environment = cfg.envVars
@@ -436,7 +435,7 @@ in
 
     # Set up the environment variables for running Nix.
     environment.sessionVariables = cfg.envVars //
-      { NIX_PATH = concatStringsSep ":" cfg.nixPath;
+      { NIX_PATH = cfg.nixPath;
       };
 
     environment.extraInit = optionalString (!isNix20)
@@ -446,6 +445,10 @@ in
         if [ "$USER" != root -o ! -w /nix/var/nix/db ]; then
             export NIX_REMOTE=daemon
         fi
+      '' + ''
+        if [ -e "$HOME/.nix-defexpr/channels" ]; then
+          export NIX_PATH="$HOME/.nix-defexpr/channels''${NIX_PATH:+:$NIX_PATH}"
+        fi
       '';
 
     nix.nrBuildUsers = mkDefault (lib.max 32 cfg.maxJobs);
diff --git a/nixos/modules/services/misc/nix-optimise.nix b/nixos/modules/services/misc/nix-optimise.nix
index 6f75e4dd03ea..416529f690e0 100644
--- a/nixos/modules/services/misc/nix-optimise.nix
+++ b/nixos/modules/services/misc/nix-optimise.nix
@@ -40,6 +40,8 @@ in
 
     systemd.services.nix-optimise =
       { description = "Nix Store Optimiser";
+        # No point running it inside a nixos-container. It should be on the host instead.
+        unitConfig.ConditionVirtualization = "!container";
         serviceConfig.ExecStart = "${config.nix.package}/bin/nix-store --optimise";
         startAt = optionals cfg.automatic cfg.dates;
       };
diff --git a/nixos/modules/services/misc/nixos-manual.nix b/nixos/modules/services/misc/nixos-manual.nix
index 993b59590bb0..df3e71c80dea 100644
--- a/nixos/modules/services/misc/nixos-manual.nix
+++ b/nixos/modules/services/misc/nixos-manual.nix
@@ -1,91 +1,21 @@
-# This module includes the NixOS man-pages in the system environment,
-# and optionally starts a browser that shows the NixOS manual on one
-# of the virtual consoles.  The latter is useful for the installation
+# This module optionally starts a browser that shows the NixOS manual
+# on one of the virtual consoles which is useful for the installation
 # CD.
 
-{ config, lib, pkgs, baseModules, ... }:
+{ config, lib, pkgs, ... }:
 
 with lib;
 
 let
-
   cfg = config.services.nixosManual;
-
-  /* For the purpose of generating docs, evaluate options with each derivation
-    in `pkgs` (recursively) replaced by a fake with path "\${pkgs.attribute.path}".
-    It isn't perfect, but it seems to cover a vast majority of use cases.
-    Caveat: even if the package is reached by a different means,
-    the path above will be shown and not e.g. `${config.services.foo.package}`. */
-  manual = import ../../../doc/manual rec {
-    inherit pkgs config;
-    version = config.system.nixos.release;
-    revision = "release-${version}";
-    options =
-      let
-        scrubbedEval = evalModules {
-          modules = [ { nixpkgs.localSystem = config.nixpkgs.localSystem; } ] ++ baseModules;
-          args = (config._module.args) // { modules = [ ]; };
-          specialArgs = { pkgs = scrubDerivations "pkgs" pkgs; };
-        };
-        scrubDerivations = namePrefix: pkgSet: mapAttrs
-          (name: value:
-            let wholeName = "${namePrefix}.${name}"; in
-            if isAttrs value then
-              scrubDerivations wholeName value
-              // (optionalAttrs (isDerivation value) { outPath = "\${${wholeName}}"; })
-            else value
-          )
-          pkgSet;
-      in scrubbedEval.options;
-  };
-
-  entry = "${manual.manual}/share/doc/nixos/index.html";
-
-  helpScript = pkgs.writeScriptBin "nixos-help"
-    ''
-      #! ${pkgs.runtimeShell} -e
-      # Finds first executable browser in a colon-separated list.
-      # (see how xdg-open defines BROWSER)
-      browser="$(
-        IFS=: ; for b in $BROWSER; do
-          [ -n "$(type -P "$b" || true)" ] && echo "$b" && break
-        done
-      )"
-      if [ -z "$browser" ]; then
-        browser="$(type -P xdg-open || true)"
-        if [ -z "$browser" ]; then
-          browser="$(type -P w3m || true)"
-          if [ -z "$browser" ]; then
-            echo "$0: unable to start a web browser; please set \$BROWSER"
-            exit 1
-          fi
-        fi
-      fi
-      exec "$browser" ${entry}
-    '';
-
-  desktopItem = pkgs.makeDesktopItem {
-    name = "nixos-manual";
-    desktopName = "NixOS Manual";
-    genericName = "View NixOS documentation in a web browser";
-    icon = "nix-snowflake";
-    exec = "${helpScript}/bin/nixos-help";
-    categories = "System";
-  };
+  cfgd = config.documentation;
 in
 
 {
 
   options = {
 
-    services.nixosManual.enable = mkOption {
-      type = types.bool;
-      default = true;
-      description = ''
-        Whether to build the NixOS manual pages.
-      '';
-    };
-
+    # TODO(@oxij): rename this to `.enable` eventually.
     services.nixosManual.showManual = mkOption {
       type = types.bool;
       default = false;
@@ -114,37 +44,30 @@ in
   };
 
 
-  config = mkIf cfg.enable {
-
-    system.build.manual = manual;
-
-    environment.systemPackages = []
-      ++ optionals config.services.xserver.enable [ desktopItem pkgs.nixos-icons ]
-      ++ optional  config.documentation.man.enable manual.manpages
-      ++ optionals config.documentation.doc.enable [ manual.manual helpScript ];
-
-    boot.extraTTYs = mkIf cfg.showManual ["tty${toString cfg.ttyNumber}"];
-
-    systemd.services = optionalAttrs cfg.showManual
-      { "nixos-manual" =
-        { description = "NixOS Manual";
-          wantedBy = [ "multi-user.target" ];
-          serviceConfig =
-            { ExecStart = "${cfg.browser} ${entry}";
-              StandardInput = "tty";
-              StandardOutput = "tty";
-              TTYPath = "/dev/tty${toString cfg.ttyNumber}";
-              TTYReset = true;
-              TTYVTDisallocate = true;
-              Restart = "always";
-            };
+  config = mkMerge [
+    (mkIf cfg.showManual {
+      assertions = singleton {
+        assertion = cfgd.enable && cfgd.nixos.enable;
+        message   = "Can't enable `services.nixosManual.showManual` without `documentation.nixos.enable`";
+      };
+    })
+    (mkIf (cfg.showManual && cfgd.enable && cfgd.nixos.enable) {
+      boot.extraTTYs = [ "tty${toString cfg.ttyNumber}" ];
+
+      systemd.services."nixos-manual" = {
+        description = "NixOS Manual";
+        wantedBy = [ "multi-user.target" ];
+        serviceConfig = {
+          ExecStart = "${cfg.browser} ${config.system.build.manual.manualHTMLIndex}";
+          StandardInput = "tty";
+          StandardOutput = "tty";
+          TTYPath = "/dev/tty${toString cfg.ttyNumber}";
+          TTYReset = true;
+          TTYVTDisallocate = true;
+          Restart = "always";
         };
       };
-
-      services.mingetty.helpLine = "\nRun `nixos-help` "
-        + lib.optionalString cfg.showManual "or press <Alt-F${toString cfg.ttyNumber}> "
-        + "for the NixOS manual.";
-
-  };
+    })
+  ];
 
 }
diff --git a/nixos/modules/services/misc/redmine.nix b/nixos/modules/services/misc/redmine.nix
index f763ba21d0b2..8d25ac5cb76f 100644
--- a/nixos/modules/services/misc/redmine.nix
+++ b/nixos/modules/services/misc/redmine.nix
@@ -5,7 +5,7 @@ with lib;
 let
   cfg = config.services.redmine;
 
-  bundle = "${pkgs.redmine}/share/redmine/bin/bundle";
+  bundle = "${cfg.package}/share/redmine/bin/bundle";
 
   databaseYml = pkgs.writeText "database.yml" ''
     production:
@@ -15,6 +15,7 @@ let
       port: ${toString cfg.database.port}
       username: ${cfg.database.user}
       password: #dbpass#
+      ${optionalString (cfg.database.socket != null) "socket: ${cfg.database.socket}"}
   '';
 
   configurationYml = pkgs.writeText "configuration.yml" ''
@@ -29,6 +30,19 @@ let
     ${cfg.extraConfig}
   '';
 
+  unpackTheme = unpack "theme";
+  unpackPlugin = unpack "plugin";
+  unpack = id: (name: source:
+    pkgs.stdenv.mkDerivation {
+      name = "redmine-${id}-${name}";
+      buildInputs = [ pkgs.unzip ];
+      buildCommand = ''
+        mkdir -p $out
+        cd $out
+        unpackFile ${source}
+      '';
+  });
+
 in
 
 {
@@ -40,6 +54,14 @@ in
         description = "Enable the Redmine service.";
       };
 
+      package = mkOption {
+        type = types.package;
+        default = pkgs.redmine;
+        defaultText = "pkgs.redmine";
+        description = "Which Redmine package to use.";
+        example = "pkgs.redmine.override { ruby = pkgs.ruby_2_3; }";
+      };
+
       user = mkOption {
         type = types.str;
         default = "redmine";
@@ -52,6 +74,12 @@ in
         description = "Group under which Redmine is ran.";
       };
 
+      port = mkOption {
+        type = types.int;
+        default = 3000;
+        description = "Port on which Redmine is ran.";
+      };
+
       stateDir = mkOption {
         type = types.str;
         default = "/var/lib/redmine";
@@ -66,6 +94,41 @@ in
 
           See https://guides.rubyonrails.org/action_mailer_basics.html#action-mailer-configuration
         '';
+        example = literalExample ''
+          email_delivery:
+            delivery_method: smtp
+            smtp_settings:
+              address: mail.example.com
+              port: 25
+        '';
+      };
+
+      themes = mkOption {
+        type = types.attrsOf types.path;
+        default = {};
+        description = "Set of themes.";
+        example = literalExample ''
+          {
+            dkuk-redmine_alex_skin = builtins.fetchurl {
+              url = https://bitbucket.org/dkuk/redmine_alex_skin/get/1842ef675ef3.zip;
+              sha256 = "0hrin9lzyi50k4w2bd2b30vrf1i4fi1c0gyas5801wn8i7kpm9yl";
+            };
+          }
+        '';
+      };
+
+      plugins = mkOption {
+        type = types.attrsOf types.path;
+        default = {};
+        description = "Set of plugins.";
+        example = literalExample ''
+          {
+            redmine_env_auth = builtins.fetchurl {
+              url = https://github.com/Intera/redmine_env_auth/archive/0.6.zip;
+              sha256 = "0yyr1yjd8gvvh832wdc8m3xfnhhxzk2pk3gm2psg5w9jdvd6skak";
+            };
+          }
+        '';
       };
 
       database = {
@@ -78,7 +141,7 @@ in
 
         host = mkOption {
           type = types.str;
-          default = "127.0.0.1";
+          default = (if cfg.database.socket != null then "localhost" else "127.0.0.1");
           description = "Database host address.";
         };
 
@@ -119,6 +182,13 @@ in
             <option>database.user</option>.
           '';
         };
+
+        socket = mkOption {
+          type = types.nullOr types.path;
+          default = null;
+          example = "/run/mysqld/mysqld.sock";
+          description = "Path to the unix socket file to use for authentication.";
+        };
       };
     };
   };
@@ -126,17 +196,20 @@ in
   config = mkIf cfg.enable {
 
     assertions = [
-      { assertion = cfg.database.passwordFile != null || cfg.database.password != "";
-        message = "either services.redmine.database.passwordFile or services.redmine.database.password must be set";
+      { assertion = cfg.database.passwordFile != null || cfg.database.password != "" || cfg.database.socket != null;
+        message = "one of services.redmine.database.socket, services.redmine.database.passwordFile, or services.redmine.database.password must be set";
+      }
+      { assertion = cfg.database.socket != null -> (cfg.database.type == "mysql2");
+        message = "Socket authentication is only available for the mysql2 database type";
       }
     ];
 
-    environment.systemPackages = [ pkgs.redmine ];
+    environment.systemPackages = [ cfg.package ];
 
     systemd.services.redmine = {
       after = [ "network.target" (if cfg.database.type == "mysql2" then "mysql.service" else "postgresql.service") ];
       wantedBy = [ "multi-user.target" ];
-      environment.HOME = "${pkgs.redmine}/share/redmine";
+      environment.HOME = "${cfg.package}/share/redmine";
       environment.RAILS_ENV = "production";
       environment.RAILS_CACHE = "${cfg.stateDir}/cache";
       environment.REDMINE_LANG = "en";
@@ -151,43 +224,80 @@ in
         subversion
       ];
       preStart = ''
-        # start with a fresh config directory every time
-        rm -rf ${cfg.stateDir}/config
-        cp -r ${pkgs.redmine}/share/redmine/config.dist ${cfg.stateDir}/config
+        # ensure cache directory exists for db:migrate command
+        mkdir -p "${cfg.stateDir}/cache"
 
-        # create the basic state directory layout pkgs.redmine expects
-        mkdir -p /run/redmine
+        # create the basic directory layout the redmine package expects
+        mkdir -p /run/redmine/public
 
         for i in config files log plugins tmp; do
-          mkdir -p ${cfg.stateDir}/$i
-          ln -fs ${cfg.stateDir}/$i /run/redmine/$i
+          mkdir -p "${cfg.stateDir}/$i"
+          ln -fs "${cfg.stateDir}/$i" /run/redmine/
+        done
+
+        for i in plugin_assets themes; do
+          mkdir -p "${cfg.stateDir}/public/$i"
+          ln -fs "${cfg.stateDir}/public/$i" /run/redmine/public/
         done
 
-        # ensure cache directory exists for db:migrate command
-        mkdir -p ${cfg.stateDir}/cache
+
+        # start with a fresh config directory
+        # the config directory is copied instead of linked as some mutable data is stored in there
+        rm -rf "${cfg.stateDir}/config/"*
+        cp -r ${cfg.package}/share/redmine/config.dist/* "${cfg.stateDir}/config/"
 
         # link in the application configuration
-        ln -fs ${configurationYml} ${cfg.stateDir}/config/configuration.yml
+        ln -fs ${configurationYml} "${cfg.stateDir}/config/configuration.yml"
+
+
+        # link in all user specified themes
+        rm -rf "${cfg.stateDir}/public/themes/"*
+        for theme in ${concatStringsSep " " (mapAttrsToList unpackTheme cfg.themes)}; do
+          ln -fs $theme/* "${cfg.stateDir}/public/themes"
+        done
+
+        # link in redmine provided themes
+        ln -sf ${cfg.package}/share/redmine/public/themes.dist/* "${cfg.stateDir}/public/themes/"
+
 
-        chmod -R ug+rwX,o-rwx+x ${cfg.stateDir}/
+        # link in all user specified plugins
+        rm -rf "${cfg.stateDir}/plugins/"*
+        for plugin in ${concatStringsSep " " (mapAttrsToList unpackPlugin cfg.plugins)}; do
+          ln -fs $plugin/* "${cfg.stateDir}/plugins/''${plugin##*-redmine-plugin-}"
+        done
+
+
+        # ensure correct permissions for most files
+        chmod -R ug+rwX,o-rwx+x "${cfg.stateDir}/"
 
-        # handle database.passwordFile
+
+        # handle database.passwordFile & permissions
         DBPASS=$(head -n1 ${cfg.database.passwordFile})
-        cp -f ${databaseYml} ${cfg.stateDir}/config/database.yml
-        sed -e "s,#dbpass#,$DBPASS,g" -i ${cfg.stateDir}/config/database.yml
-        chmod 440 ${cfg.stateDir}/config/database.yml
+        cp -f ${databaseYml} "${cfg.stateDir}/config/database.yml"
+        sed -e "s,#dbpass#,$DBPASS,g" -i "${cfg.stateDir}/config/database.yml"
+        chmod 440 "${cfg.stateDir}/config/database.yml"
+
 
         # generate a secret token if required
         if ! test -e "${cfg.stateDir}/config/initializers/secret_token.rb"; then
           ${bundle} exec rake generate_secret_token
-          chmod 440 ${cfg.stateDir}/config/initializers/secret_token.rb
+          chmod 440 "${cfg.stateDir}/config/initializers/secret_token.rb"
         fi
 
+
         # ensure everything is owned by ${cfg.user}
-        chown -R ${cfg.user}:${cfg.group} ${cfg.stateDir}
+        chown -R ${cfg.user}:${cfg.group} "${cfg.stateDir}"
+
+
+        # execute redmine required commands prior to starting the application
+        # NOTE: su required in case using mysql socket authentication
+        /run/wrappers/bin/su -s ${pkgs.bash}/bin/bash -m -l redmine -c '${bundle} exec rake db:migrate'
+        /run/wrappers/bin/su -s ${pkgs.bash}/bin/bash -m -l redmine -c '${bundle} exec rake redmine:load_default_data'
+
 
-        ${bundle} exec rake db:migrate
-        ${bundle} exec rake redmine:load_default_data
+        # log files don't exist until after first command has been executed
+        # correct ownership of files generated by calling exec rake ...
+        chown -R ${cfg.user}:${cfg.group} "${cfg.stateDir}/log"
       '';
 
       serviceConfig = {
@@ -196,13 +306,13 @@ in
         User = cfg.user;
         Group = cfg.group;
         TimeoutSec = "300";
-        WorkingDirectory = "${pkgs.redmine}/share/redmine";
-        ExecStart="${bundle} exec rails server webrick -e production -P ${cfg.stateDir}/redmine.pid";
+        WorkingDirectory = "${cfg.package}/share/redmine";
+        ExecStart="${bundle} exec rails server webrick -e production -p ${toString cfg.port} -P '${cfg.stateDir}/redmine.pid'";
       };
 
     };
 
-    users.extraUsers = optionalAttrs (cfg.user == "redmine") (singleton
+    users.users = optionalAttrs (cfg.user == "redmine") (singleton
       { name = "redmine";
         group = cfg.group;
         home = cfg.stateDir;
@@ -210,7 +320,7 @@ in
         uid = config.ids.uids.redmine;
       });
 
-    users.extraGroups = optionalAttrs (cfg.group == "redmine") (singleton
+    users.groups = optionalAttrs (cfg.group == "redmine") (singleton
       { name = "redmine";
         gid = config.ids.gids.redmine;
       });
diff --git a/nixos/modules/services/misc/sickbeard.nix b/nixos/modules/services/misc/sickbeard.nix
new file mode 100644
index 000000000000..5cfbbe516ae1
--- /dev/null
+++ b/nixos/modules/services/misc/sickbeard.nix
@@ -0,0 +1,92 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  name = "sickbeard";
+
+  cfg = config.services.sickbeard;
+  sickbeard = cfg.package;
+
+in
+{
+
+  ###### interface
+
+  options = {
+    services.sickbeard = {
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Whether to enable the sickbeard server.";
+      };
+      package = mkOption {
+        type = types.package;
+        default = pkgs.sickbeard;
+        example = literalExample "pkgs.sickrage";
+        description =''
+          Enable <literal>pkgs.sickrage</literal> or <literal>pkgs.sickgear</literal>
+          as an alternative to SickBeard
+        '';
+      };
+      dataDir = mkOption {
+        type = types.path;
+        default = "/var/lib/${name}";
+        description = "Path where to store data files.";
+      };
+      configFile = mkOption {
+        type = types.path;
+        default = "${cfg.dataDir}/config.ini";
+        description = "Path to config file.";
+      };
+      port = mkOption {
+        type = types.ints.u16;
+        default = 8081;
+        description = "Port to bind to.";
+      };
+      user = mkOption {
+        type = types.str;
+        default = name;
+        description = "User to run the service as";
+      };
+      group = mkOption {
+        type = types.str;
+        default = name;
+        description = "Group to run the service as";
+      };
+    };
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    users.users = optionalAttrs (cfg.user == name) (singleton {
+      name = name;
+      uid = config.ids.uids.sickbeard;
+      group = cfg.group;
+      description = "sickbeard user";
+      home = cfg.dataDir;
+      createHome = true;
+    });
+
+    users.groups = optionalAttrs (cfg.group == name) (singleton {
+      name = name;
+      gid = config.ids.gids.sickbeard;
+    });
+
+    systemd.services.sickbeard = {
+      description = "Sickbeard Server";
+      wantedBy    = [ "multi-user.target" ];
+      after = [ "network.target" ];
+
+      serviceConfig = {
+        User = cfg.user;
+        Group = cfg.group;
+        ExecStart = "${sickbeard}/SickBeard.py --datadir ${cfg.dataDir} --config ${cfg.configFile} --port ${toString cfg.port}";
+      };
+    };
+  };
+}
diff --git a/nixos/modules/services/misc/taskserver/doc.xml b/nixos/modules/services/misc/taskserver/doc.xml
index 21d25ecf391f..5eac8d9ef784 100644
--- a/nixos/modules/services/misc/taskserver/doc.xml
+++ b/nixos/modules/services/misc/taskserver/doc.xml
@@ -2,101 +2,93 @@
     xmlns:xlink="http://www.w3.org/1999/xlink"
     version="5.0"
     xml:id="module-taskserver">
-
-  <title>Taskserver</title>
+ <title>Taskserver</title>
+ <para>
+  Taskserver is the server component of
+  <link xlink:href="https://taskwarrior.org/">Taskwarrior</link>, a free and
+  open source todo list application.
+ </para>
+ <para>
+  <emphasis>Upstream documentation:</emphasis>
+  <link xlink:href="https://taskwarrior.org/docs/#taskd"/>
+ </para>
+ <section xml:id="module-services-taskserver-configuration">
+  <title>Configuration</title>
 
   <para>
-    Taskserver is the server component of
-    <link xlink:href="https://taskwarrior.org/">Taskwarrior</link>, a free and
-    open source todo list application.
+   Taskserver does all of its authentication via TLS using client certificates,
+   so you either need to roll your own CA or purchase a certificate from a
+   known CA, which allows creation of client certificates. These certificates
+   are usually advertised as <quote>server certificates</quote>.
   </para>
 
   <para>
-    <emphasis>Upstream documentation:</emphasis>
-    <link xlink:href="https://taskwarrior.org/docs/#taskd"/>
+   So in order to make it easier to handle your own CA, there is a helper tool
+   called <command>nixos-taskserver</command> which manages the custom CA along
+   with Taskserver organisations, users and groups.
   </para>
 
-  <section xml:id="module-services-taskserver-configuration">
-    <title>Configuration</title>
-
-    <para>
-      Taskserver does all of its authentication via TLS using client
-      certificates, so you either need to roll your own CA or purchase a
-      certificate from a known CA, which allows creation of client
-      certificates.
-
-      These certificates are usually advertised as
-      <quote>server certificates</quote>.
-    </para>
-
-    <para>
-      So in order to make it easier to handle your own CA, there is a helper
-      tool called <command>nixos-taskserver</command> which manages the custom
-      CA along with Taskserver organisations, users and groups.
-    </para>
-
-    <para>
-      While the client certificates in Taskserver only authenticate whether a
-      user is allowed to connect, every user has its own UUID which identifies
-      it as an entity.
-    </para>
-
-    <para>
-      With <command>nixos-taskserver</command> the client certificate is created
-      along with the UUID of the user, so it handles all of the credentials
-      needed in order to setup the Taskwarrior client to work with a Taskserver.
-    </para>
-  </section>
+  <para>
+   While the client certificates in Taskserver only authenticate whether a user
+   is allowed to connect, every user has its own UUID which identifies it as an
+   entity.
+  </para>
 
-  <section xml:id="module-services-taskserver-nixos-taskserver-tool">
-    <title>The nixos-taskserver tool</title>
+  <para>
+   With <command>nixos-taskserver</command> the client certificate is created
+   along with the UUID of the user, so it handles all of the credentials needed
+   in order to setup the Taskwarrior client to work with a Taskserver.
+  </para>
+ </section>
+ <section xml:id="module-services-taskserver-nixos-taskserver-tool">
+  <title>The nixos-taskserver tool</title>
 
-    <para>
-      Because Taskserver by default only provides scripts to setup users
-      imperatively, the <command>nixos-taskserver</command> tool is used for
-      addition and deletion of organisations along with users and groups defined
-      by <xref linkend="opt-services.taskserver.organisations"/> and as well for
-      imperative set up.
-    </para>
+  <para>
+   Because Taskserver by default only provides scripts to setup users
+   imperatively, the <command>nixos-taskserver</command> tool is used for
+   addition and deletion of organisations along with users and groups defined
+   by <xref linkend="opt-services.taskserver.organisations"/> and as well for
+   imperative set up.
+  </para>
 
-    <para>
-      The tool is designed to not interfere if the command is used to manually
-      set up some organisations, users or groups.
-    </para>
+  <para>
+   The tool is designed to not interfere if the command is used to manually set
+   up some organisations, users or groups.
+  </para>
 
-    <para>
-      For example if you add a new organisation using
-      <command>nixos-taskserver org add foo</command>, the organisation is not
-      modified and deleted no matter what you define in
-      <option>services.taskserver.organisations</option>, even if you're adding
-      the same organisation in that option.
-    </para>
+  <para>
+   For example if you add a new organisation using <command>nixos-taskserver
+   org add foo</command>, the organisation is not modified and deleted no
+   matter what you define in
+   <option>services.taskserver.organisations</option>, even if you're adding
+   the same organisation in that option.
+  </para>
 
-    <para>
-      The tool is modelled to imitate the official <command>taskd</command>
-      command, documentation for each subcommand can be shown by using the
-      <option>--help</option> switch.
-    </para>
-  </section>
-  <section xml:id="module-services-taskserver-declarative-ca-management">
-    <title>Declarative/automatic CA management</title>
+  <para>
+   The tool is modelled to imitate the official <command>taskd</command>
+   command, documentation for each subcommand can be shown by using the
+   <option>--help</option> switch.
+  </para>
+ </section>
+ <section xml:id="module-services-taskserver-declarative-ca-management">
+  <title>Declarative/automatic CA management</title>
 
-    <para>
-      Everything is done according to what you specify in the module options,
-      however in order to set up a Taskwarrior client for synchronisation with a
-      Taskserver instance, you have to transfer the keys and certificates to the
-      client machine.
-    </para>
+  <para>
+   Everything is done according to what you specify in the module options,
+   however in order to set up a Taskwarrior client for synchronisation with a
+   Taskserver instance, you have to transfer the keys and certificates to the
+   client machine.
+  </para>
 
-    <para>
-      This is done using
-      <command>nixos-taskserver user export $orgname $username</command> which
-      is printing a shell script fragment to stdout which can either be used
-      verbatim or adjusted to import the user on the client machine.
-    </para>
+  <para>
+   This is done using <command>nixos-taskserver user export $orgname
+   $username</command> which is printing a shell script fragment to stdout
+   which can either be used verbatim or adjusted to import the user on the
+   client machine.
+  </para>
 
-    <para>
-      For example, let's say you have the following configuration:
+  <para>
+   For example, let's say you have the following configuration:
 <screen>
 {
   <xref linkend="opt-services.taskserver.enable"/> = true;
@@ -105,40 +97,39 @@
   <link linkend="opt-services.taskserver.organisations._name_.users">services.taskserver.organisations.my-company.users</link> = [ "alice" ];
 }
 </screen>
-      This creates an organisation called <literal>my-company</literal> with the
-      user <literal>alice</literal>.
-    </para>
+   This creates an organisation called <literal>my-company</literal> with the
+   user <literal>alice</literal>.
+  </para>
 
-    <para>
-      Now in order to import the <literal>alice</literal> user to another
-      machine <literal>alicebox</literal>, all we need to do is something like
-      this:
+  <para>
+   Now in order to import the <literal>alice</literal> user to another machine
+   <literal>alicebox</literal>, all we need to do is something like this:
 <screen>
 $ ssh server nixos-taskserver user export my-company alice | sh
 </screen>
-      Of course, if no SSH daemon is available on the server you can also copy
-      &amp; paste it directly into a shell.
-    </para>
+   Of course, if no SSH daemon is available on the server you can also copy
+   &amp; paste it directly into a shell.
+  </para>
 
-    <para>
-      After this step the user should be set up and you can start synchronising
-      your tasks for the first time with <command>task sync init</command> on
-      <literal>alicebox</literal>.
-    </para>
+  <para>
+   After this step the user should be set up and you can start synchronising
+   your tasks for the first time with <command>task sync init</command> on
+   <literal>alicebox</literal>.
+  </para>
 
-    <para>
-      Subsequent synchronisation requests merely require the command
-      <command>task sync</command> after that stage.
-    </para>
-  </section>
-  <section xml:id="module-services-taskserver-manual-ca-management">
-    <title>Manual CA management</title>
+  <para>
+   Subsequent synchronisation requests merely require the command <command>task
+   sync</command> after that stage.
+  </para>
+ </section>
+ <section xml:id="module-services-taskserver-manual-ca-management">
+  <title>Manual CA management</title>
 
-    <para>
-      If you set any options within
-      <link linkend="opt-services.taskserver.pki.manual.ca.cert">service.taskserver.pki.manual</link>.*,
-      <command>nixos-taskserver</command> won't issue certificates, but you can
-      still use it for adding or removing user accounts.
-    </para>
-  </section>
+  <para>
+   If you set any options within
+   <link linkend="opt-services.taskserver.pki.manual.ca.cert">service.taskserver.pki.manual</link>.*,
+   <command>nixos-taskserver</command> won't issue certificates, but you can
+   still use it for adding or removing user accounts.
+  </para>
+ </section>
 </chapter>
diff --git a/nixos/modules/services/misc/weechat.nix b/nixos/modules/services/misc/weechat.nix
new file mode 100644
index 000000000000..c6ff540ea12f
--- /dev/null
+++ b/nixos/modules/services/misc/weechat.nix
@@ -0,0 +1,58 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.weechat;
+in
+
+{
+  options.services.weechat = {
+    enable = mkEnableOption "weechat";
+    root = mkOption {
+      description = "Weechat state directory.";
+      type = types.str;
+      default = "/var/lib/weechat";
+    };
+    sessionName = mkOption {
+      description = "Name of the `screen' session for weechat.";
+      default = "weechat-screen";
+      type = types.str;
+    };
+    binary = mkOption {
+      description = "Binary to execute (by default \${weechat}/bin/weechat).";
+      example = literalExample ''
+        ''${pkgs.weechat}/bin/weechat-headless
+      '';
+      default = "${pkgs.weechat}/bin/weechat";
+    };
+  };
+
+  config = mkIf cfg.enable {
+    users = {
+      groups.weechat = {};
+      users.weechat = {
+        createHome = true;
+        group = "weechat";
+        home = cfg.root;
+        isSystemUser = true;
+      };
+    };
+
+    systemd.services.weechat = {
+      environment.WEECHAT_HOME = cfg.root;
+      serviceConfig = {
+        User = "weechat";
+        Group = "weechat";
+        RemainAfterExit = "yes";
+      };
+      script = "exec ${config.security.wrapperDir}/screen -Dm -S ${cfg.sessionName} ${cfg.binary}";
+      wantedBy = [ "multi-user.target" ];
+      wants = [ "network.target" ];
+    };
+
+    security.wrappers.screen.source = "${pkgs.screen}/bin/screen";
+  };
+
+  meta.doc = ./weechat.xml;
+}
diff --git a/nixos/modules/services/misc/weechat.xml b/nixos/modules/services/misc/weechat.xml
new file mode 100644
index 000000000000..b7f755bbc5c7
--- /dev/null
+++ b/nixos/modules/services/misc/weechat.xml
@@ -0,0 +1,66 @@
+<chapter xmlns="http://docbook.org/ns/docbook"
+         xmlns:xlink="http://www.w3.org/1999/xlink"
+         xmlns:xi="http://www.w3.org/2001/XInclude"
+         version="5.0"
+         xml:id="module-services-weechat">
+ <title>WeeChat</title>
+ <para>
+  <link xlink:href="https://weechat.org/">WeeChat</link> is a fast and
+  extensible IRC client.
+ </para>
+ <section>
+  <title>Basic Usage</title>
+
+  <para>
+   By default, the module creates a
+   <literal><link xlink:href="https://www.freedesktop.org/wiki/Software/systemd/">systemd</link></literal>
+   unit which runs the chat client in a detached
+   <literal><link xlink:href="https://www.gnu.org/software/screen/">screen</link></literal>
+   session.
+  </para>
+
+  <para>
+   This can be done by enabling the <literal>weechat</literal> service:
+<programlisting>
+{ ... }:
+
+{
+  <link linkend="opt-services.weechat.enable">services.weechat.enable</link> = true;
+}
+</programlisting>
+  </para>
+
+  <para>
+   The service is managed by a dedicated user named <literal>weechat</literal>
+   in the state directory <literal>/var/lib/weechat</literal>.
+  </para>
+ </section>
+ <section>
+  <title>Re-attaching to WeeChat</title>
+
+  <para>
+   WeeChat runs in a screen session owned by a dedicated user. To explicitly
+   allow your another user to attach to this session, the
+   <literal>screenrc</literal> needs to be tweaked by adding
+   <link xlink:href="https://www.gnu.org/software/screen/manual/html_node/Multiuser.html#Multiuser">multiuser</link>
+   support:
+<programlisting>
+{
+  <link linkend="opt-programs.screen.screenrc">programs.screen.screenrc</link> = ''
+    multiuser on
+    acladd normal_user
+  '';
+}
+</programlisting>
+   Now, the session can be re-attached like this:
+<programlisting>
+screen -x weechat/weechat-screen
+</programlisting>
+  </para>
+
+  <para>
+   <emphasis>The session name can be changed using
+   <link linkend="opt-services.weechat.sessionName">services.weechat.sessionName.</link></emphasis>
+  </para>
+ </section>
+</chapter>
diff --git a/nixos/modules/services/monitoring/datadog-agent.nix b/nixos/modules/services/monitoring/datadog-agent.nix
index e545e06b3495..5434fe99347d 100644
--- a/nixos/modules/services/monitoring/datadog-agent.nix
+++ b/nixos/modules/services/monitoring/datadog-agent.nix
@@ -7,8 +7,7 @@ let
 
   ddConf = {
     dd_url              = "https://app.datadoghq.com";
-    skip_ssl_validation = "no";
-    api_key             = "";
+    skip_ssl_validation = false;
     confd_path          = "/etc/datadog-agent/conf.d";
     additional_checksd  = "/etc/datadog-agent/checks.d";
     use_dogstatsd       = true;
@@ -16,6 +15,8 @@ let
   // optionalAttrs (cfg.logLevel != null) { log_level = cfg.logLevel; }
   // optionalAttrs (cfg.hostname != null) { inherit (cfg) hostname; }
   // optionalAttrs (cfg.tags != null ) { tags = concatStringsSep ", " cfg.tags; }
+  // optionalAttrs (cfg.enableLiveProcessCollection) { process_config = { enabled = "true"; }; }
+  // optionalAttrs (cfg.enableTraceAgent) { apm_config = { enabled = true; }; }
   // cfg.extraConfig;
 
   # Generate Datadog configuration files for each configured checks.
@@ -125,6 +126,22 @@ in {
       '';
      };
 
+    enableLiveProcessCollection = mkOption {
+      description = ''
+        Whether to enable the live process collection agent.
+      '';
+      default = false;
+      type = types.bool;
+    };
+
+    enableTraceAgent = mkOption {
+      description = ''
+        Whether to enable the trace agent.
+      '';
+      default = false;
+      type = types.bool;
+    };
+
     checks = mkOption {
       description = ''
         Configuration for all Datadog checks. Keys of this attribute
@@ -206,7 +223,6 @@ in {
           Group = "datadog";
           Restart = "always";
           RestartSec = 2;
-          PrivateTmp = true;
         };
         restartTriggers = [ datadogPkg ] ++ map (etc: etc.source) etcfiles;
       } attrs;
@@ -229,6 +245,25 @@ in {
         path = [ datadogPkg pkgs.python pkgs.sysstat pkgs.procps pkgs.jdk ];
         serviceConfig.ExecStart = "${datadogPkg}/bin/dd-jmxfetch";
       });
+
+      datadog-process-agent = lib.mkIf cfg.enableLiveProcessCollection (makeService {
+        description = "Datadog Live Process Agent";
+        path = [ ];
+        script = ''
+          export DD_API_KEY=$(head -n 1 ${cfg.apiKeyFile})
+          ${pkgs.datadog-process-agent}/bin/agent --config /etc/datadog-agent/datadog.yaml
+        '';
+      });
+
+      datadog-trace-agent = lib.mkIf cfg.enableTraceAgent (makeService {
+        description = "Datadog Trace Agent";
+        path = [ ];
+        script = ''
+          export DD_API_KEY=$(head -n 1 ${cfg.apiKeyFile})
+          ${pkgs.datadog-trace-agent}/bin/trace-agent -config /etc/datadog-agent/datadog.yaml
+        '';
+      });
+
     };
 
     environment.etc = etcfiles;
diff --git a/nixos/modules/services/monitoring/grafana.nix b/nixos/modules/services/monitoring/grafana.nix
index 3e801f9b838d..c0c16a429d89 100644
--- a/nixos/modules/services/monitoring/grafana.nix
+++ b/nixos/modules/services/monitoring/grafana.nix
@@ -4,6 +4,7 @@ with lib;
 
 let
   cfg = config.services.grafana;
+  opt = options.services.grafana;
 
   envOptions = {
     PATHS_DATA = cfg.dataDir;
@@ -41,6 +42,12 @@ let
     AUTH_ANONYMOUS_ORG_ROLE = cfg.auth.anonymous.org_role;
 
     ANALYTICS_REPORTING_ENABLED = boolToString cfg.analytics.reporting.enable;
+
+    SMTP_ENABLE = boolToString cfg.smtp.enable;
+    SMTP_HOST = cfg.smtp.host;
+    SMTP_USER = cfg.smtp.user;
+    SMTP_PASSWORD = cfg.smtp.password;
+    SMTP_FROM_ADDRESS = cfg.smtp.fromAddress;
   } // cfg.extraOptions;
 
 in {
@@ -134,11 +141,23 @@ in {
       };
 
       password = mkOption {
-        description = "Database password.";
+        description = ''
+          Database password.
+          This option is mutual exclusive with the passwordFile option.
+        '';
         default = "";
         type = types.str;
       };
 
+      passwordFile = mkOption {
+        description = ''
+          File that containts the database password.
+          This option is mutual exclusive with the password option.
+        '';
+        default = null;
+        type = types.nullOr types.path;
+      };
+
       path = mkOption {
         description = "Database path.";
         default = "${cfg.dataDir}/data/grafana.db";
@@ -163,16 +182,69 @@ in {
       };
 
       adminPassword = mkOption {
-        description = "Default admin password.";
+        description = ''
+          Default admin password.
+          This option is mutual exclusive with the adminPasswordFile option.
+        '';
         default = "admin";
         type = types.str;
       };
 
+      adminPasswordFile = mkOption {
+        description = ''
+          Default admin password.
+          This option is mutual exclusive with the <literal>adminPassword</literal> option.
+        '';
+        default = null;
+        type = types.nullOr types.path;
+      };
+
       secretKey = mkOption {
         description = "Secret key used for signing.";
         default = "SW2YcwTIb9zpOOhoPsMm";
         type = types.str;
       };
+
+      secretKeyFile = mkOption {
+        description = "Secret key used for signing.";
+        default = null;
+        type = types.nullOr types.path;
+      };
+    };
+
+    smtp = {
+      enable = mkEnableOption "smtp";
+      host = mkOption {
+        description = "Host to connect to";
+        default = "localhost:25";
+        type = types.str;
+      };
+      user = mkOption {
+        description = "User used for authentication";
+        default = "";
+        type = types.str;
+      };
+      password = mkOption {
+        description = ''
+          Password used for authentication.
+          This option is mutual exclusive with the passwordFile option.
+        '';
+        default = "";
+        type = types.str;
+      };
+      passwordFile = mkOption {
+        description = ''
+          Password used for authentication.
+          This option is mutual exclusive with the password option.
+        '';
+        default = null;
+        type = types.nullOr types.path;
+      };
+      fromAddress = mkOption {
+        description = "Email address used for sending";
+        default = "admin@grafana.localhost";
+        type = types.str;
+      };
     };
 
     users = {
@@ -235,18 +307,37 @@ in {
         but without GF_ prefix
       '';
       default = {};
-      type = types.attrsOf types.str;
+      type = with types; attrsOf (either str path);
     };
   };
 
   config = mkIf cfg.enable {
     warnings = optional (
-      cfg.database.password != options.services.grafana.database.password.default ||
-      cfg.security.adminPassword != options.services.grafana.security.adminPassword.default
+      cfg.database.password != opt.database.password.default ||
+      cfg.security.adminPassword != opt.security.adminPassword.default
     ) "Grafana passwords will be stored as plaintext in the Nix store!";
 
     environment.systemPackages = [ cfg.package ];
 
+    assertions = [
+      {
+        assertion = cfg.database.password != opt.database.password.default -> cfg.database.passwordFile == null;
+        message = "Cannot set both password and passwordFile";
+      }
+      {
+        assertion = cfg.security.adminPassword != opt.security.adminPassword.default -> cfg.security.adminPasswordFile == null;
+        message = "Cannot set both adminPassword and adminPasswordFile";
+      }
+      {
+        assertion = cfg.security.secretKeyFile != opt.security.secretKeyFile.default -> cfg.security.secretKeyFile == null;
+        message = "Cannot set both secretKey and secretKeyFile";
+      }
+      {
+        assertion = cfg.smtp.password != opt.smtp.password.default -> cfg.smtp.passwordFile == null;
+        message = "Cannot set both password and secretKeyFile";
+      }
+    ];
+
     systemd.services.grafana = {
       description = "Grafana Service Daemon";
       wantedBy = ["multi-user.target"];
@@ -254,8 +345,22 @@ in {
       environment = {
         QT_QPA_PLATFORM = "offscreen";
       } // mapAttrs' (n: v: nameValuePair "GF_${n}" (toString v)) envOptions;
+      script = ''
+        ${optionalString (cfg.database.passwordFile != null) ''
+          export GF_DATABASE_PASSWORD="$(cat ${escapeShellArg cfg.database.passwordFile})"
+        ''}
+        ${optionalString (cfg.security.adminPasswordFile != null) ''
+          export GF_SECURITY_ADMIN_PASSWORD="$(cat ${escapeShellArg cfg.security.adminPasswordFile})"
+        ''}
+        ${optionalString (cfg.security.secretKeyFile != null) ''
+          export GF_SECURITY_SECRET_KEY="$(cat ${escapeShellArg cfg.security.secretKeyFile})"
+        ''}
+        ${optionalString (cfg.smtp.passwordFile != null) ''
+          export GF_SMTP_PASSWORD="$(cat ${escapeShellArg cfg.smtp.passwordFile})"
+        ''}
+        exec ${cfg.package.bin}/bin/grafana-server -homepath ${cfg.dataDir}
+      '';
       serviceConfig = {
-        ExecStart = "${cfg.package.bin}/bin/grafana-server -homepath ${cfg.dataDir}";
         WorkingDirectory = cfg.dataDir;
         User = "grafana";
       };
diff --git a/nixos/modules/services/monitoring/munin.nix b/nixos/modules/services/monitoring/munin.nix
index ff9604c7dbcd..2b265d5b5a90 100644
--- a/nixos/modules/services/monitoring/munin.nix
+++ b/nixos/modules/services/monitoring/munin.nix
@@ -5,8 +5,8 @@
 
 # TODO: support fastcgi
 # http://munin-monitoring.org/wiki/CgiHowto2
-# spawn-fcgi -s /var/run/munin/fastcgi-graph.sock -U www-data   -u munin -g munin /usr/lib/munin/cgi/munin-cgi-graph
-# spawn-fcgi -s /var/run/munin/fastcgi-html.sock  -U www-data   -u munin -g munin /usr/lib/munin/cgi/munin-cgi-html
+# spawn-fcgi -s /run/munin/fastcgi-graph.sock -U www-data   -u munin -g munin /usr/lib/munin/cgi/munin-cgi-graph
+# spawn-fcgi -s /run/munin/fastcgi-html.sock  -U www-data   -u munin -g munin /usr/lib/munin/cgi/munin-cgi-html
 # https://paste.sh/vofcctHP#-KbDSXVeWoifYncZmLfZzgum
 # nginx http://munin.readthedocs.org/en/latest/example/webserver/nginx.html
 
@@ -22,7 +22,7 @@ let
       dbdir     /var/lib/munin
       htmldir   /var/www/munin
       logdir    /var/log/munin
-      rundir    /var/run/munin
+      rundir    /run/munin
 
       ${cronCfg.extraGlobalConfig}
 
@@ -170,7 +170,7 @@ in
       wantedBy = [ "multi-user.target" ];
       path = with pkgs; [ munin smartmontools "/run/current-system/sw" "/run/wrappers" ];
       environment.MUNIN_LIBDIR = "${pkgs.munin}/lib";
-      environment.MUNIN_PLUGSTATE = "/var/run/munin";
+      environment.MUNIN_PLUGSTATE = "/run/munin";
       environment.MUNIN_LOGDIR = "/var/log/munin";
       preStart = ''
         echo "updating munin plugins..."
@@ -188,7 +188,7 @@ in
     };
 
     # munin_stats plugin breaks as of 2.0.33 when this doesn't exist
-    systemd.tmpfiles.rules = [ "d /var/run/munin 0755 munin munin -" ];
+    systemd.tmpfiles.rules = [ "d /run/munin 0755 munin munin -" ];
 
   }) (mkIf cronCfg.enable {
 
@@ -210,7 +210,7 @@ in
     };
 
     systemd.tmpfiles.rules = [
-      "d /var/run/munin 0755 munin munin -"
+      "d /run/munin 0755 munin munin -"
       "d /var/log/munin 0755 munin munin -"
       "d /var/www/munin 0755 munin munin -"
       "d /var/lib/munin 0755 munin munin -"
diff --git a/nixos/modules/services/monitoring/prometheus/alertmanager.nix b/nixos/modules/services/monitoring/prometheus/alertmanager.nix
index 8a47c9f1e7d8..8a44cf7fd8f6 100644
--- a/nixos/modules/services/monitoring/prometheus/alertmanager.nix
+++ b/nixos/modules/services/monitoring/prometheus/alertmanager.nix
@@ -9,6 +9,15 @@ let
     if cfg.configText != null then
       pkgs.writeText "alertmanager.yml" cfg.configText
     else mkConfigFile;
+  cmdlineArgs = cfg.extraFlags ++ [
+    "--config.file ${alertmanagerYml}"
+    "--web.listen-address ${cfg.listenAddress}:${toString cfg.port}"
+    "--log.level ${cfg.logLevel}"
+    ] ++ (optional (cfg.webExternalUrl != null)
+      "--web.external-url ${cfg.webExternalUrl}"
+    ) ++ (optional (cfg.logFormat != null)
+      "--log.format ${cfg.logFormat}"
+  );
 in {
   options = {
     services.prometheus.alertmanager = {
@@ -99,6 +108,14 @@ in {
           Open port in firewall for incoming connections.
         '';
       };
+
+      extraFlags = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        description = ''
+          Extra commandline options when launching the Alertmanager.
+        '';
+      };
     };
   };
 
@@ -111,11 +128,7 @@ in {
       after    = [ "network.target" ];
       script = ''
         ${pkgs.prometheus-alertmanager.bin}/bin/alertmanager \
-        --config.file ${alertmanagerYml} \
-        --web.listen-address ${cfg.listenAddress}:${toString cfg.port} \
-        --log.level ${cfg.logLevel} \
-        ${optionalString (cfg.webExternalUrl != null) ''--web.external-url ${cfg.webExternalUrl} \''}
-        ${optionalString (cfg.logFormat != null) "--log.format ${cfg.logFormat}"}
+          ${concatStringsSep " \\\n  " cmdlineArgs}
       '';
 
       serviceConfig = {
diff --git a/nixos/modules/services/monitoring/prometheus/default.nix b/nixos/modules/services/monitoring/prometheus/default.nix
index 5dda763bd56b..e2ee995cea80 100644
--- a/nixos/modules/services/monitoring/prometheus/default.nix
+++ b/nixos/modules/services/monitoring/prometheus/default.nix
@@ -39,6 +39,7 @@ let
     "-alertmanager.notification-queue-capacity=${toString cfg.alertmanagerNotificationQueueCapacity}"
     "-alertmanager.timeout=${toString cfg.alertmanagerTimeout}s"
     (optionalString (cfg.alertmanagerURL != []) "-alertmanager.url=${concatStringsSep "," cfg.alertmanagerURL}")
+    (optionalString (cfg.webExternalUrl != null) "-web.external-url=${cfg.webExternalUrl}")
   ];
 
   promTypes.globalConfig = types.submodule {
@@ -467,6 +468,16 @@ in {
           Alert manager HTTP API timeout (in seconds).
         '';
       };
+
+      webExternalUrl = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        example = "https://example.com/";
+        description = ''
+          The URL under which Prometheus is externally reachable (for example,
+          if Prometheus is served via a reverse proxy).
+        '';
+      };
     };
   };
 
diff --git a/nixos/modules/services/monitoring/prometheus/exporters.nix b/nixos/modules/services/monitoring/prometheus/exporters.nix
index 1d5f400250fd..950af848c0f6 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters.nix
@@ -30,6 +30,7 @@ let
     postfix   = import ./exporters/postfix.nix   { inherit config lib pkgs; };
     snmp      = import ./exporters/snmp.nix      { inherit config lib pkgs; };
     surfboard = import ./exporters/surfboard.nix { inherit config lib pkgs; };
+    tor       = import ./exporters/tor.nix       { inherit config lib pkgs; };
     unifi     = import ./exporters/unifi.nix     { inherit config lib pkgs; };
     varnish   = import ./exporters/varnish.nix   { inherit config lib pkgs; };
   };
@@ -123,15 +124,13 @@ let
       systemd.services."prometheus-${name}-exporter" = mkMerge ([{
         wantedBy = [ "multi-user.target" ];
         after = [ "network.target" ];
-        serviceConfig = {
-          Restart = mkDefault "always";
-          PrivateTmp = mkDefault true;
-          WorkingDirectory = mkDefault /tmp;
-        } // mkIf (!(serviceOpts.serviceConfig.DynamicUser or false)) {
-          User = conf.user;
-          Group = conf.group;
-        };
-      } serviceOpts ]);
+        serviceConfig.Restart = mkDefault "always";
+        serviceConfig.PrivateTmp = mkDefault true;
+        serviceConfig.WorkingDirectory = mkDefault /tmp;
+      } serviceOpts ] ++ optional (serviceOpts.serviceConfig.DynamicUser or false) {
+        serviceConfig.User = conf.user;
+        serviceConfig.Group = conf.group;
+      });
   };
 in
 {
@@ -172,5 +171,8 @@ in
     }) exporterOpts)
   );
 
-  meta.doc = ./exporters.xml;
+  meta = {
+    doc = ./exporters.xml;
+    maintainers = [ maintainers.willibutz ];
+  };
 }
diff --git a/nixos/modules/services/monitoring/prometheus/exporters.xml b/nixos/modules/services/monitoring/prometheus/exporters.xml
index be86abb74b44..7a0a1bdf2c14 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters.xml
+++ b/nixos/modules/services/monitoring/prometheus/exporters.xml
@@ -3,13 +3,19 @@
          xmlns:xi="http://www.w3.org/2001/XInclude"
          version="5.0"
          xml:id="module-services-prometheus-exporters">
+ <title>Prometheus exporters</title>
+ <para>
+  Prometheus exporters provide metrics for the
+  <link xlink:href="https://prometheus.io">prometheus monitoring system</link>.
+ </para>
+ <section xml:id="module-services-prometheus-exporters-configuration">
+  <title>Configuration</title>
 
-<title>Prometheus exporters</title>
-
-<para>Prometheus exporters provide metrics for the <link xlink:href="https://prometheus.io">prometheus monitoring system</link>.</para>
-
-<section xml:id="module-services-prometheus-exporters-configuration"><title>Configuration</title>
-  <para>One of the most common exporters is the <link xlink:href="https://github.com/prometheus/node_exporter">node exporter</link>, it provides hardware and OS metrics from the host it's running on. The exporter could be configured as follows:
+  <para>
+   One of the most common exporters is the
+   <link xlink:href="https://github.com/prometheus/node_exporter">node
+   exporter</link>, it provides hardware and OS metrics from the host it's
+   running on. The exporter could be configured as follows:
 <programlisting>
   services.promtheus.exporters.node = {
     enable = true;
@@ -24,43 +30,88 @@
     firewallFilter = "-i br0 -p tcp -m tcp --dport 9100";
   };
 </programlisting>
-It should now serve all metrics from the collectors
-that are explicitly enabled and the ones that are
-<link xlink:href="https://github.com/prometheus/node_exporter#enabled-by-default">enabled by default</link>, via http under <literal>/metrics</literal>. In this example the firewall should just
-allow incoming connections to the exporter's port on the bridge interface <literal>br0</literal>
-(this would have to be configured seperately of course).
-For more information about configuration see <literal>man configuration.nix</literal> or
-search through the <link xlink:href="https://nixos.org/nixos/options.html#prometheus.exporters">available options</link>.
-</para>
-</section>
-<section xml:id="module-services-prometheus-exporters-new-exporter"><title>Adding a new exporter</title>
-  <para>To add a new exporter, it has to be packaged first (see <literal>nixpkgs/pkgs/servers/monitoring/prometheus/</literal> for examples), then a module can be added. The postfix exporter is used in this example:</para>
-<itemizedlist>
-  <listitem>
+   It should now serve all metrics from the collectors that are explicitly
+   enabled and the ones that are
+   <link xlink:href="https://github.com/prometheus/node_exporter#enabled-by-default">enabled
+   by default</link>, via http under <literal>/metrics</literal>. In this
+   example the firewall should just allow incoming connections to the
+   exporter's port on the bridge interface <literal>br0</literal> (this would
+   have to be configured seperately of course). For more information about
+   configuration see <literal>man configuration.nix</literal> or search through
+   the
+   <link xlink:href="https://nixos.org/nixos/options.html#prometheus.exporters">available
+   options</link>.
+  </para>
+ </section>
+ <section xml:id="module-services-prometheus-exporters-new-exporter">
+  <title>Adding a new exporter</title>
+
+  <para>
+   To add a new exporter, it has to be packaged first (see
+   <literal>nixpkgs/pkgs/servers/monitoring/prometheus/</literal> for
+   examples), then a module can be added. The postfix exporter is used in this
+   example:
+  </para>
+
+  <itemizedlist>
+   <listitem>
     <para>
-      Some default options for all exporters are provided by
-      <literal>nixpkgs/nixos/modules/services/monitoring/prometheus/exporters.nix</literal>:
+     Some default options for all exporters are provided by
+     <literal>nixpkgs/nixos/modules/services/monitoring/prometheus/exporters.nix</literal>:
     </para>
-  </listitem>
-  <listitem override='none'>
+   </listitem>
+   <listitem override='none'>
     <itemizedlist>
-      <listitem><para><literal>enable</literal></para></listitem>
-      <listitem><para><literal>port</literal></para></listitem>
-      <listitem><para><literal>listenAddress</literal></para></listitem>
-      <listitem><para><literal>extraFlags</literal></para></listitem>
-      <listitem><para><literal>openFirewall</literal></para></listitem>
-      <listitem><para><literal>firewallFilter</literal></para></listitem>
-      <listitem><para><literal>user</literal></para></listitem>
-      <listitem><para><literal>group</literal></para></listitem>
+     <listitem>
+      <para>
+       <literal>enable</literal>
+      </para>
+     </listitem>
+     <listitem>
+      <para>
+       <literal>port</literal>
+      </para>
+     </listitem>
+     <listitem>
+      <para>
+       <literal>listenAddress</literal>
+      </para>
+     </listitem>
+     <listitem>
+      <para>
+       <literal>extraFlags</literal>
+      </para>
+     </listitem>
+     <listitem>
+      <para>
+       <literal>openFirewall</literal>
+      </para>
+     </listitem>
+     <listitem>
+      <para>
+       <literal>firewallFilter</literal>
+      </para>
+     </listitem>
+     <listitem>
+      <para>
+       <literal>user</literal>
+      </para>
+     </listitem>
+     <listitem>
+      <para>
+       <literal>group</literal>
+      </para>
+     </listitem>
     </itemizedlist>
-  </listitem>
-  <listitem>
-    <para>As there is already a package available, the module can now be added.
-      This is accomplished by adding a new file to the
-      <literal>nixos/modules/services/monitoring/prometheus/exporters/</literal> directory,
-      which will be called postfix.nix and contains all exporter specific options
-      and configuration:
-      <programlisting>
+   </listitem>
+   <listitem>
+    <para>
+     As there is already a package available, the module can now be added. This
+     is accomplished by adding a new file to the
+     <literal>nixos/modules/services/monitoring/prometheus/exporters/</literal>
+     directory, which will be called postfix.nix and contains all exporter
+     specific options and configuration:
+<programlisting>
         # nixpgs/nixos/modules/services/prometheus/exporters/postfix.nix
         { config, lib, pkgs }:
 
@@ -121,15 +172,16 @@ search through the <link xlink:href="https://nixos.org/nixos/options.html#promet
         }
       </programlisting>
     </para>
-  </listitem>
-  <listitem>
+   </listitem>
+   <listitem>
     <para>
-      This should already be enough for the postfix exporter. Additionally one could
-      now add assertions and conditional default values. This can be done in the
-      'meta-module' that combines all exporter definitions and generates the submodules:
-      <literal>nixpkgs/nixos/modules/services/prometheus/exporters.nix</literal>
+     This should already be enough for the postfix exporter. Additionally one
+     could now add assertions and conditional default values. This can be done
+     in the 'meta-module' that combines all exporter definitions and generates
+     the submodules:
+     <literal>nixpkgs/nixos/modules/services/prometheus/exporters.nix</literal>
     </para>
-  </listitem>
-</itemizedlist>
-</section>
+   </listitem>
+  </itemizedlist>
+ </section>
 </chapter>
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/snmp.nix b/nixos/modules/services/monitoring/prometheus/exporters/snmp.nix
index 404cd0a1896b..0d9194124325 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/snmp.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/snmp.nix
@@ -60,10 +60,10 @@ in
       DynamicUser = true;
       ExecStart = ''
         ${pkgs.prometheus-snmp-exporter.bin}/bin/snmp_exporter \
-          -config.file ${configFile} \
-          -log.format ${cfg.logFormat} \
-          -log.level ${cfg.logLevel} \
-          -web.listen-address ${cfg.listenAddress}:${toString cfg.port} \
+          --config.file=${configFile} \
+          --log.format=${cfg.logFormat} \
+          --log.level=${cfg.logLevel} \
+          --web.listen-address=${cfg.listenAddress}:${toString cfg.port} \
           ${concatStringsSep " \\\n  " cfg.extraFlags}
       '';
     };
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/tor.nix b/nixos/modules/services/monitoring/prometheus/exporters/tor.nix
new file mode 100644
index 000000000000..0e2a13c44ab7
--- /dev/null
+++ b/nixos/modules/services/monitoring/prometheus/exporters/tor.nix
@@ -0,0 +1,40 @@
+{ config, lib, pkgs }:
+
+with lib;
+
+let
+  cfg = config.services.prometheus.exporters.tor;
+in
+{
+  port = 9130;
+  extraOpts = {
+    torControlAddress = mkOption {
+      type = types.str;
+      default = "127.0.0.1";
+      description = ''
+        Tor control IP address or hostname.
+      '';
+    };
+
+    torControlPort = mkOption {
+      type = types.int;
+      default = 9051;
+      description = ''
+        Tor control port.
+      '';
+    };
+  };
+  serviceOpts = {
+    serviceConfig = {
+      DynamicUser = true;
+      ExecStart = ''
+        ${pkgs.prometheus-tor-exporter}/bin/prometheus-tor-exporter \
+          -b ${cfg.listenAddress} \
+          -p ${toString cfg.port} \
+          -a ${cfg.torControlAddress} \
+          -c ${toString cfg.torControlPort} \
+          ${concatStringsSep " \\\n  " cfg.extraFlags}
+      '';
+    };
+  };
+}
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/varnish.nix b/nixos/modules/services/monitoring/prometheus/exporters/varnish.nix
index 8dbf2d735ab9..aaed76175b84 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/varnish.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/varnish.nix
@@ -69,6 +69,7 @@ in
     path = [ pkgs.varnish ];
     serviceConfig = {
       DynamicUser = true;
+      RestartSec = mkDefault 1;
       ExecStart = ''
         ${pkgs.prometheus-varnish-exporter}/bin/prometheus_varnish_exporter \
           --web.listen-address ${cfg.listenAddress}:${toString cfg.port} \
diff --git a/nixos/modules/services/monitoring/riemann.nix b/nixos/modules/services/monitoring/riemann.nix
index 237de53456f9..13d2b1cc0602 100644
--- a/nixos/modules/services/monitoring/riemann.nix
+++ b/nixos/modules/services/monitoring/riemann.nix
@@ -17,9 +17,9 @@ let
 
   launcher = writeScriptBin "riemann" ''
     #!/bin/sh
-    exec ${jdk}/bin/java ${concatStringsSep "\n" cfg.extraJavaOpts} \
+    exec ${jdk}/bin/java ${concatStringsSep " " cfg.extraJavaOpts} \
       -cp ${classpath} \
-      riemann.bin ${writeText "riemann-config.clj" riemannConfig}
+      riemann.bin ${cfg.configFile}
   '';
 
 in {
@@ -37,7 +37,8 @@ in {
       config = mkOption {
         type = types.lines;
         description = ''
-          Contents of the Riemann configuration file.
+          Contents of the Riemann configuration file. For more complicated
+          config you should use configFile.
         '';
       };
       configFiles = mkOption {
@@ -47,7 +48,15 @@ in {
           Extra files containing Riemann configuration. These files will be
           loaded at runtime by Riemann (with Clojure's
           <literal>load-file</literal> function) at the end of the
-          configuration.
+          configuration if you use the config option, this is ignored if you
+          use configFile.
+        '';
+      };
+      configFile = mkOption {
+        type = types.str;
+        description = ''
+          A Riemann config file. Any files in the same directory as this file
+          will be added to the classpath by Riemann.
         '';
       };
       extraClasspathEntries = mkOption {
@@ -77,6 +86,10 @@ in {
       group = "riemann";
     };
 
+    services.riemann.configFile = mkDefault (
+      writeText "riemann-config.clj" riemannConfig
+    );
+
     systemd.services.riemann = {
       wantedBy = [ "multi-user.target" ];
       path = [ inetutils ];
@@ -84,6 +97,7 @@ in {
         User = "riemann";
         ExecStart = "${launcher}/bin/riemann";
       };
+      serviceConfig.LimitNOFILE = 65536;
     };
 
   };
diff --git a/nixos/modules/services/network-filesystems/openafs/client.nix b/nixos/modules/services/network-filesystems/openafs/client.nix
index 52c0966e05bc..240c1392088f 100644
--- a/nixos/modules/services/network-filesystems/openafs/client.nix
+++ b/nixos/modules/services/network-filesystems/openafs/client.nix
@@ -149,11 +149,13 @@ in
       packages = {
         module = mkOption {
           default = config.boot.kernelPackages.openafs;
+          defaultText = "config.boot.kernelPackages.openafs";
           type = types.package;
           description = "OpenAFS kernel module package. MUST match the userland package!";
         };
         programs = mkOption {
           default = getBin pkgs.openafs;
+          defaultText = "config.boot.kernelPackages.openafs";
           type = types.package;
           description = "OpenAFS programs package. MUST match the kernel module package!";
         };
diff --git a/nixos/modules/services/network-filesystems/openafs/server.nix b/nixos/modules/services/network-filesystems/openafs/server.nix
index 4c80ed0839f7..095024d2c8af 100644
--- a/nixos/modules/services/network-filesystems/openafs/server.nix
+++ b/nixos/modules/services/network-filesystems/openafs/server.nix
@@ -80,6 +80,7 @@ in {
 
       package = mkOption {
         default = pkgs.openafs.server or pkgs.openafs;
+        defaultText = "pkgs.openafs.server or pkgs.openafs";
         type = types.package;
         description = "OpenAFS package for the server binaries";
       };
diff --git a/nixos/modules/services/networking/bitlbee.nix b/nixos/modules/services/networking/bitlbee.nix
index 392a8d5c2e7c..274b36171608 100644
--- a/nixos/modules/services/networking/bitlbee.nix
+++ b/nixos/modules/services/networking/bitlbee.nix
@@ -7,9 +7,10 @@ let
   cfg = config.services.bitlbee;
   bitlbeeUid = config.ids.uids.bitlbee;
 
-  bitlbeePkg = if cfg.libpurple_plugins == []
-  then pkgs.bitlbee
-  else pkgs.bitlbee.override { enableLibPurple = true; };
+  bitlbeePkg = pkgs.bitlbee.override {
+    enableLibPurple = cfg.libpurple_plugins != [];
+    enablePam = cfg.authBackend == "pam";
+  };
 
   bitlbeeConfig = pkgs.writeText "bitlbee.conf"
     ''
@@ -20,6 +21,7 @@ let
     DaemonInterface = ${cfg.interface}
     DaemonPort = ${toString cfg.portNumber}
     AuthMode = ${cfg.authMode}
+    AuthBackend = ${cfg.authBackend}
     Plugindir = ${pkgs.bitlbee-plugins cfg.plugins}/lib/bitlbee
     ${lib.optionalString (cfg.hostName != "") "HostName = ${cfg.hostName}"}
     ${lib.optionalString (cfg.protocols != "") "Protocols = ${cfg.protocols}"}
@@ -31,7 +33,7 @@ let
 
   purple_plugin_path =
     lib.concatMapStringsSep ":"
-      (plugin: "${plugin}/lib/pidgin/")
+      (plugin: "${plugin}/lib/pidgin/:${plugin}/lib/purple-2/")
       cfg.libpurple_plugins
     ;
 
@@ -70,6 +72,16 @@ in
         '';
       };
 
+      authBackend = mkOption {
+        default = "storage";
+        type = types.enum [ "storage" "pam" ];
+        description = ''
+          How users are authenticated
+            storage -- save passwords internally
+            pam -- Linux PAM authentication
+        '';
+      };
+
       authMode = mkOption {
         default = "Open";
         type = types.enum [ "Open" "Closed" "Registered" ];
@@ -147,23 +159,22 @@ in
 
   ###### implementation
 
-  config = mkIf config.services.bitlbee.enable {
-
-    users.users = singleton
-      { name = "bitlbee";
+  config =  mkMerge [
+    (mkIf config.services.bitlbee.enable {
+      users.users = singleton {
+        name = "bitlbee";
         uid = bitlbeeUid;
         description = "BitlBee user";
         home = "/var/lib/bitlbee";
         createHome = true;
       };
 
-    users.groups = singleton
-      { name = "bitlbee";
+      users.groups = singleton {
+        name = "bitlbee";
         gid = config.ids.gids.bitlbee;
       };
 
-    systemd.services.bitlbee =
-      {
+      systemd.services.bitlbee = {
         environment.PURPLE_PLUGIN_PATH = purple_plugin_path;
         description = "BitlBee IRC to other chat networks gateway";
         after = [ "network.target" ];
@@ -172,8 +183,12 @@ in
         serviceConfig.ExecStart = "${bitlbeePkg}/sbin/bitlbee -F -n -c ${bitlbeeConfig}";
       };
 
-    environment.systemPackages = [ bitlbeePkg ];
+      environment.systemPackages = [ bitlbeePkg ];
 
-  };
+    })
+    (mkIf (config.services.bitlbee.authBackend == "pam") {
+      security.pam.services.bitlbee = {};
+    })
+  ];
 
 }
diff --git a/nixos/modules/services/networking/charybdis.nix b/nixos/modules/services/networking/charybdis.nix
index 6d57faa9ac2b..3d02dc8d1375 100644
--- a/nixos/modules/services/networking/charybdis.nix
+++ b/nixos/modules/services/networking/charybdis.nix
@@ -90,7 +90,7 @@ in
           BANDB_DBPATH = "${cfg.statedir}/ban.db";
         };
         serviceConfig = {
-          ExecStart   = "${charybdis}/bin/charybdis-ircd -foreground -logfile /dev/stdout -configfile ${configFile}";
+          ExecStart   = "${charybdis}/bin/charybdis -foreground -logfile /dev/stdout -configfile ${configFile}";
           Group = cfg.group;
           User = cfg.user;
           PermissionsStartOnly = true; # preStart needs to run with root permissions
diff --git a/nixos/modules/services/networking/chrony.nix b/nixos/modules/services/networking/chrony.nix
index cef30661cc33..a363b545d649 100644
--- a/nixos/modules/services/networking/chrony.nix
+++ b/nixos/modules/services/networking/chrony.nix
@@ -3,12 +3,10 @@
 with lib;
 
 let
+  cfg = config.services.chrony;
 
   stateDir = "/var/lib/chrony";
-
-  keyFile = "/etc/chrony.keys";
-
-  cfg = config.services.chrony;
+  keyFile = "${stateDir}/chrony.keys";
 
   configFile = pkgs.writeText "chrony.conf" ''
     ${concatMapStringsSep "\n" (server: "server " + server) cfg.servers}
@@ -19,7 +17,6 @@ let
     }
 
     driftfile ${stateDir}/chrony.drift
-
     keyfile ${keyFile}
 
     ${optionalString (!config.time.hardwareClockInLocalTime) "rtconutc"}
@@ -27,18 +24,11 @@ let
     ${cfg.extraConfig}
   '';
 
-  chronyFlags = "-n -m -u chrony -f ${configFile} ${toString cfg.extraFlags}";
-
+  chronyFlags = "-m -u chrony -f ${configFile} ${toString cfg.extraFlags}";
 in
-
 {
-
-  ###### interface
-
   options = {
-
     services.chrony = {
-
       enable = mkOption {
         default = false;
         description = ''
@@ -83,15 +73,9 @@ in
         description = "Extra flags passed to the chronyd command.";
       };
     };
-
   };
 
-
-  ###### implementation
-
   config = mkIf cfg.enable {
-
-    # Make chronyc available in the system path
     environment.systemPackages = [ pkgs.chrony ];
 
     users.groups = singleton
@@ -113,26 +97,30 @@ in
       { description = "chrony NTP daemon";
 
         wantedBy = [ "multi-user.target" ];
-        wants = [ "time-sync.target" ];
-        before = [ "time-sync.target" ];
-        after = [ "network.target" ];
+        wants    = [ "time-sync.target" ];
+        before   = [ "time-sync.target" ];
+        after    = [ "network.target" ];
         conflicts = [ "ntpd.service" "systemd-timesyncd.service" ];
 
         path = [ pkgs.chrony ];
 
-        preStart =
-          ''
-            mkdir -m 0755 -p ${stateDir}
-            touch ${keyFile}
-            chmod 0640 ${keyFile}
-            chown chrony:chrony ${stateDir} ${keyFile}
-          '';
+        preStart = ''
+          mkdir -m 0755 -p ${stateDir}
+          touch ${keyFile}
+          chmod 0640 ${keyFile}
+          chown chrony:chrony ${stateDir} ${keyFile}
+        '';
 
         serviceConfig =
-          { ExecStart = "${pkgs.chrony}/bin/chronyd ${chronyFlags}";
+          { Type = "forking";
+            ExecStart = "${pkgs.chrony}/bin/chronyd ${chronyFlags}";
+
+            ProtectHome = "yes";
+            ProtectSystem = "full";
+            PrivateTmp = "yes";
+
+            ConditionCapability = "CAP_SYS_TIME";
           };
       };
-
   };
-
 }
diff --git a/nixos/modules/services/networking/ddclient.nix b/nixos/modules/services/networking/ddclient.nix
index 9a2e13e9553c..a70967820b32 100644
--- a/nixos/modules/services/networking/ddclient.nix
+++ b/nixos/modules/services/networking/ddclient.nix
@@ -182,9 +182,10 @@ with lib;
       serviceConfig = rec {
         DynamicUser = true;
         RuntimeDirectory = StateDirectory;
+        RuntimeDirectoryMode = "0750";
         StateDirectory = builtins.baseNameOf dataDir;
         Type = "oneshot";
-        ExecStartPre = "!${lib.getBin pkgs.coreutils}/bin/install -m666 ${cfg.configFile} /run/${RuntimeDirectory}/ddclient.conf";
+        ExecStartPre = "!${lib.getBin pkgs.coreutils}/bin/install -m660 ${cfg.configFile} /run/${RuntimeDirectory}/ddclient.conf";
         ExecStart = "${lib.getBin pkgs.ddclient}/bin/ddclient -file /run/${RuntimeDirectory}/ddclient.conf";
       };
     };
diff --git a/nixos/modules/services/networking/dnscrypt-proxy.xml b/nixos/modules/services/networking/dnscrypt-proxy.xml
index a97579202523..f90eef69848c 100644
--- a/nixos/modules/services/networking/dnscrypt-proxy.xml
+++ b/nixos/modules/services/networking/dnscrypt-proxy.xml
@@ -3,67 +3,64 @@
          xmlns:xi="http://www.w3.org/2001/XInclude"
          version="5.0"
          xml:id="sec-dnscrypt-proxy">
-
-  <title>DNSCrypt client proxy</title>
+ <title>DNSCrypt client proxy</title>
+ <para>
+  The DNSCrypt client proxy relays DNS queries to a DNSCrypt enabled upstream
+  resolver. The traffic between the client and the upstream resolver is
+  encrypted and authenticated, mitigating the risk of MITM attacks, DNS
+  poisoning attacks, and third-party snooping (assuming the upstream is
+  trustworthy).
+ </para>
+ <sect1 xml:id="sec-dnscrypt-proxy-configuration">
+  <title>Basic configuration</title>
 
   <para>
-    The DNSCrypt client proxy relays DNS queries to a DNSCrypt enabled
-    upstream resolver. The traffic between the client and the upstream
-    resolver is encrypted and authenticated, mitigating the risk of MITM
-    attacks, DNS poisoning attacks, and third-party snooping (assuming the
-    upstream is trustworthy).
-  </para>
-
-  <sect1 xml:id="sec-dnscrypt-proxy-configuration"><title>Basic configuration</title>
-
-  <para>
-    To enable the client proxy, set
-    <programlisting>
+   To enable the client proxy, set
+<programlisting>
 <xref linkend="opt-services.dnscrypt-proxy.enable"/> = true;
     </programlisting>
   </para>
 
   <para>
-    Enabling the client proxy does not alter the system nameserver; to
-    relay local queries, prepend <literal>127.0.0.1</literal> to
-    <option>networking.nameservers</option>.
+   Enabling the client proxy does not alter the system nameserver; to relay
+   local queries, prepend <literal>127.0.0.1</literal> to
+   <option>networking.nameservers</option>.
   </para>
-
-  </sect1>
-
-  <sect1 xml:id="sec-dnscrypt-proxy-forwarder"><title>As a forwarder for another DNS client</title>
+ </sect1>
+ <sect1 xml:id="sec-dnscrypt-proxy-forwarder">
+  <title>As a forwarder for another DNS client</title>
 
   <para>
-    To run the DNSCrypt proxy client as a forwarder for another
-    DNS client, change the default proxy listening port to a
-    non-standard value and point the other client to it:
-    <programlisting>
+   To run the DNSCrypt proxy client as a forwarder for another DNS client,
+   change the default proxy listening port to a non-standard value and point
+   the other client to it:
+<programlisting>
 <xref linkend="opt-services.dnscrypt-proxy.localPort"/> = 43;
     </programlisting>
   </para>
 
-  <sect2 xml:id="sec-dnscrypt-proxy-forwarder-dsnmasq"><title>dnsmasq</title>
-  <para>
-    <programlisting>
+  <sect2 xml:id="sec-dnscrypt-proxy-forwarder-dsnmasq">
+   <title>dnsmasq</title>
+   <para>
+<programlisting>
 {
   <xref linkend="opt-services.dnsmasq.enable"/> = true;
   <xref linkend="opt-services.dnsmasq.servers"/> = [ "127.0.0.1#43" ];
 }
     </programlisting>
-  </para>
+   </para>
   </sect2>
 
-  <sect2 xml:id="sec-dnscrypt-proxy-forwarder-unbound"><title>unbound</title>
-  <para>
-    <programlisting>
+  <sect2 xml:id="sec-dnscrypt-proxy-forwarder-unbound">
+   <title>unbound</title>
+   <para>
+<programlisting>
 {
   <xref linkend="opt-services.unbound.enable"/> = true;
   <xref linkend="opt-services.unbound.forwardAddresses"/> = [ "127.0.0.1@43" ];
 }
     </programlisting>
-  </para>
+   </para>
   </sect2>
-
-  </sect1>
-
+ </sect1>
 </chapter>
diff --git a/nixos/modules/services/networking/epmd.nix b/nixos/modules/services/networking/epmd.nix
new file mode 100644
index 000000000000..692b75e4f086
--- /dev/null
+++ b/nixos/modules/services/networking/epmd.nix
@@ -0,0 +1,56 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.epmd;
+
+in
+
+{
+  ###### interface
+  options.services.epmd = {
+    enable = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Whether to enable socket activation for Erlang Port Mapper Daemon (epmd),
+        which acts as a name server on all hosts involved in distributed
+        Erlang computations.
+      '';
+    };
+    package = mkOption {
+      type = types.package;
+      default = pkgs.erlang;
+      description = ''
+        The Erlang package to use to get epmd binary. That way you can re-use
+        an Erlang runtime that is already installed for other purposes.
+      '';
+    };
+  };
+
+  ###### implementation
+  config = mkIf cfg.enable {
+    systemd.sockets.epmd = rec {
+      description = "Erlang Port Mapper Daemon Activation Socket";
+      wantedBy = [ "sockets.target" ];
+      before = wantedBy;
+      socketConfig = {
+        ListenStream = "4369";
+        Accept = "false";
+      };
+    };
+
+    systemd.services.epmd = {
+      description = "Erlang Port Mapper Daemon";
+      after = [ "network.target" ];
+      requires = [ "epmd.socket" ];
+
+      serviceConfig = {
+        DynamicUser = true;
+        ExecStart = "${cfg.package}/bin/epmd -systemd";
+        Type = "notify";
+      };
+    };
+  };
+}
diff --git a/nixos/modules/services/networking/eternal-terminal.nix b/nixos/modules/services/networking/eternal-terminal.nix
new file mode 100644
index 000000000000..be7337ece7e4
--- /dev/null
+++ b/nixos/modules/services/networking/eternal-terminal.nix
@@ -0,0 +1,89 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.eternal-terminal;
+
+in
+
+{
+
+  ###### interface
+
+  options = {
+
+    services.eternal-terminal = {
+
+      enable = mkEnableOption "Eternal Terminal server";
+
+      port = mkOption {
+        default = 2022;
+        type = types.int;
+        description = ''
+          The port the server should listen on. Will use the server's default (2022) if not specified.
+        '';
+      };
+
+      verbosity = mkOption {
+        default = 0;
+        type = types.enum (lib.range 0 9);
+        description = ''
+          The verbosity level (0-9).
+        '';
+      };
+
+      silent = mkOption {
+        default = false;
+        type = types.bool;
+        description = ''
+          If enabled, disables all logging.
+        '';
+      };
+
+      logSize = mkOption {
+        default = 20971520;
+        type = types.int;
+        description = ''
+          The maximum log size.
+        '';
+      };
+    };
+  };
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    # We need to ensure the et package is fully installed because
+    # the (remote) et client runs the `etterminal` binary when it
+    # connects.
+    environment.systemPackages = [ pkgs.eternal-terminal ];
+
+    systemd.services = {
+      eternal-terminal = {
+        description = "Eternal Terminal server.";
+        wantedBy = [ "multi-user.target" ];
+        after = [ "syslog.target" "network.target" ];
+        serviceConfig = {
+          Type = "forking";
+          ExecStart = "${pkgs.eternal-terminal}/bin/etserver --daemon --cfgfile=${pkgs.writeText "et.cfg" ''
+            ; et.cfg : Config file for Eternal Terminal
+            ;
+
+            [Networking]
+            port = ${toString cfg.port}
+
+            [Debug]
+            verbose = ${toString cfg.verbosity}
+            silent = ${if cfg.silent then "1" else "0"}
+            logsize = ${toString cfg.logSize}
+          ''}";
+          Restart = "on-failure";
+          KillMode = "process";
+        };
+      };
+    };
+  };
+}
diff --git a/nixos/modules/services/networking/hostapd.nix b/nixos/modules/services/networking/hostapd.nix
index 3af0441a89d8..9f74e4963296 100644
--- a/nixos/modules/services/networking/hostapd.nix
+++ b/nixos/modules/services/networking/hostapd.nix
@@ -157,9 +157,9 @@ in
       { description = "hostapd wireless AP";
 
         path = [ pkgs.hostapd ];
-        wantedBy = [ "network.target" ];
-
-        after = [ "${cfg.interface}-cfg.service" "nat.service" "bind.service" "dhcpd.service" "sys-subsystem-net-devices-${cfg.interface}.device" ];
+        after = [ "sys-subsystem-net-devices-${cfg.interface}.device" ];
+        bindsTo = [ "sys-subsystem-net-devices-${cfg.interface}.device" ];
+        requiredBy = [ "network-link-${cfg.interface}.service" ];
 
         serviceConfig =
           { ExecStart = "${pkgs.hostapd}/bin/hostapd ${configFile}";
diff --git a/nixos/modules/services/networking/hylafax/default.nix b/nixos/modules/services/networking/hylafax/default.nix
new file mode 100644
index 000000000000..4c63b822d165
--- /dev/null
+++ b/nixos/modules/services/networking/hylafax/default.nix
@@ -0,0 +1,29 @@
+{ config, lib, pkgs, ... }:
+
+{
+
+  imports = [
+    ./options.nix
+    ./systemd.nix
+  ];
+
+  config = lib.modules.mkIf config.services.hylafax.enable {
+    environment.systemPackages = [ pkgs.hylafaxplus ];
+    users.users.uucp = {
+      uid = config.ids.uids.uucp;
+      group = "uucp";
+      description = "Unix-to-Unix CoPy system";
+      isSystemUser = true;
+      inherit (config.users.users.nobody) home;
+    };
+    assertions = [{
+      assertion = config.services.hylafax.modems != {};
+      message = ''
+        HylaFAX cannot be used without modems.
+        Please define at least one modem with
+        <option>config.services.hylafax.modems</option>.
+      '';
+    }];
+  };
+
+}
diff --git a/nixos/modules/services/networking/hylafax/faxq-default.nix b/nixos/modules/services/networking/hylafax/faxq-default.nix
new file mode 100644
index 000000000000..a2630ce66b71
--- /dev/null
+++ b/nixos/modules/services/networking/hylafax/faxq-default.nix
@@ -0,0 +1,12 @@
+{ ... }:
+
+# see man:hylafax-config(5)
+
+{
+
+  ModemGroup = [ ''"any:.*"'' ];
+  ServerTracing = "0x78701";
+  SessionTracing = "0x78701";
+  UUCPLockDir = "/var/lock";
+
+}
diff --git a/nixos/modules/services/networking/hylafax/faxq-wait.sh b/nixos/modules/services/networking/hylafax/faxq-wait.sh
new file mode 100755
index 000000000000..8c39e9d20c18
--- /dev/null
+++ b/nixos/modules/services/networking/hylafax/faxq-wait.sh
@@ -0,0 +1,29 @@
+#! @shell@ -e
+
+# skip this if there are no modems at all
+if ! stat -t "@spoolAreaPath@"/etc/config.* >/dev/null 2>&1
+then
+  exit 0
+fi
+
+echo "faxq started, waiting for modem(s) to initialize..."
+
+for i in `seq @timeoutSec@0 -1 0`  # gracefully timeout
+do
+  sleep 0.1
+  # done if status files exist, but don't mention initialization
+  if \
+    stat -t "@spoolAreaPath@"/status/* >/dev/null 2>&1 \
+    && \
+    ! grep --silent --ignore-case 'initializing server' \
+    "@spoolAreaPath@"/status/*
+  then
+    echo "modem(s) apparently ready"
+    exit 0
+  fi
+  # if i reached 0, modems probably failed to initialize
+  if test $i -eq 0
+  then
+    echo "warning: modem initialization timed out"
+  fi
+done
diff --git a/nixos/modules/services/networking/hylafax/hfaxd-default.nix b/nixos/modules/services/networking/hylafax/hfaxd-default.nix
new file mode 100644
index 000000000000..8999dae57f41
--- /dev/null
+++ b/nixos/modules/services/networking/hylafax/hfaxd-default.nix
@@ -0,0 +1,10 @@
+{ ... }:
+
+# see man:hfaxd(8)
+
+{
+
+  ServerTracing = "0x91";
+  XferLogFile = "/clientlog";
+
+}
diff --git a/nixos/modules/services/networking/hylafax/modem-default.nix b/nixos/modules/services/networking/hylafax/modem-default.nix
new file mode 100644
index 000000000000..7529b5b0aafd
--- /dev/null
+++ b/nixos/modules/services/networking/hylafax/modem-default.nix
@@ -0,0 +1,22 @@
+{ pkgs, ... }:
+
+# see man:hylafax-config(5)
+
+{
+
+  TagLineFont = "etc/LiberationSans-25.pcf";
+  TagLineLocale = ''en_US.UTF-8'';
+
+  AdminGroup = "root";  # groups that can change server config
+  AnswerRotary = "fax";  # don't accept anything else but faxes
+  LogFileMode = "0640";
+  PriorityScheduling = true;
+  RecvFileMode = "0640";
+  ServerTracing = "0x78701";
+  SessionTracing = "0x78701";
+  UUCPLockDir = "/var/lock";
+
+  SendPageCmd = ''${pkgs.coreutils}/bin/false'';  # prevent pager transmit
+  SendUUCPCmd = ''${pkgs.coreutils}/bin/false'';  # prevent UUCP transmit
+
+}
diff --git a/nixos/modules/services/networking/hylafax/options.nix b/nixos/modules/services/networking/hylafax/options.nix
new file mode 100644
index 000000000000..4ac6d3fa8432
--- /dev/null
+++ b/nixos/modules/services/networking/hylafax/options.nix
@@ -0,0 +1,375 @@
+{ config, lib, pkgs, ... }:
+
+let
+
+  inherit (lib.options) literalExample mkEnableOption mkOption;
+  inherit (lib.types) bool enum int lines loaOf nullOr path str submodule;
+  inherit (lib.modules) mkDefault mkIf mkMerge;
+
+  commonDescr = ''
+    Values can be either strings or integers
+    (which will be added to the config file verbatimly)
+    or lists thereof
+    (which will be translated to multiple
+    lines with the same configuration key).
+    Boolean values are translated to "Yes" or "No".
+    The default contains some reasonable
+    configuration to yield an operational system.
+  '';
+
+  str1 = lib.types.addCheck str (s: s!="");  # non-empty string
+  int1 = lib.types.addCheck int (i: i>0);  # positive integer
+
+  configAttrType =
+    # Options in HylaFAX configuration files can be
+    # booleans, strings, integers, or list thereof
+    # representing multiple config directives with the same key.
+    # This type definition resolves all
+    # those types into a list of strings.
+    let
+      inherit (lib.types) attrsOf coercedTo listOf;
+      innerType = coercedTo bool (x: if x then "Yes" else "No")
+        (coercedTo int (toString) str);
+    in
+      attrsOf (coercedTo innerType lib.singleton (listOf innerType));
+
+  cfg = config.services.hylafax;
+
+  modemConfigOptions = { name, config, ... }: {
+    options = {
+      name = mkOption {
+        type = str1;
+        example = "ttyS1";
+        description = ''
+          Name of modem device,
+          will be searched for in <filename>/dev</filename>.
+        '';
+      };
+      type = mkOption {
+        type = str1;
+        example = "cirrus";
+        description = ''
+          Name of modem configuration file,
+          will be searched for in <filename>config</filename>
+          in the spooling area directory.
+        '';
+      };
+      config = mkOption {
+        type = configAttrType;
+        example = {
+          AreaCode = "49";
+          LocalCode = "30";
+          FAXNumber = "123456";
+          LocalIdentifier = "LostInBerlin";
+        };
+        description = ''
+          Attribute set of values for the given modem.
+          ${commonDescr}
+          Options defined here override options in
+          <option>commonModemConfig</option> for this modem.
+        '';
+      };
+    };
+    config.name = mkDefault name;
+    config.config.Include = [ "config/${config.type}" ];
+  };
+
+  defaultConfig =
+    let
+      inherit (config.security) wrapperDir;
+      inherit (config.services.mail.sendmailSetuidWrapper) program;
+      mkIfDefault = cond: value: mkIf cond (mkDefault value);
+      noWrapper = config.services.mail.sendmailSetuidWrapper==null;
+      # If a sendmail setuid wrapper exists,
+      # we add the path to the default configuration file.
+      # Otherwise, we use `false` to provoke
+      # an error if hylafax tries to use it.
+      c.sendmailPath = mkMerge [
+        (mkIfDefault noWrapper ''${pkgs.coreutils}/bin/false'')
+        (mkIfDefault (!noWrapper) ''${wrapperDir}/${program}'')
+      ];
+      importDefaultConfig = file:
+        lib.attrsets.mapAttrs
+        (lib.trivial.const mkDefault)
+        (import file { inherit pkgs; });
+      c.commonModemConfig = importDefaultConfig ./modem-default.nix;
+      c.faxqConfig = importDefaultConfig ./faxq-default.nix;
+      c.hfaxdConfig = importDefaultConfig ./hfaxd-default.nix;
+    in
+      c;
+
+  localConfig =
+    let
+      c.hfaxdConfig.UserAccessFile = cfg.userAccessFile;
+      c.faxqConfig = lib.attrsets.mapAttrs
+        (lib.trivial.const (v: mkIf (v!=null) v))
+        {
+          AreaCode = cfg.areaCode;
+          CountryCode = cfg.countryCode;
+          LongDistancePrefix = cfg.longDistancePrefix;
+          InternationalPrefix = cfg.internationalPrefix;
+        };
+      c.commonModemConfig = c.faxqConfig;
+    in
+      c;
+
+in
+
+
+{
+
+
+  options.services.hylafax = {
+
+    enable = mkEnableOption ''HylaFAX server'';
+
+    autostart = mkOption {
+      type = bool;
+      default = true;
+      example = false;
+      description = ''
+        Autostart the HylaFAX queue manager at system start.
+        If this is <literal>false</literal>, the queue manager
+        will still be started if there are pending
+        jobs or if a user tries to connect to it.
+      '';
+    };
+
+    countryCode = mkOption {
+      type = nullOr str1;
+      default = null;
+      example = "49";
+      description = ''Country code for server and all modems.'';
+    };
+
+    areaCode = mkOption {
+      type = nullOr str1;
+      default = null;
+      example = "30";
+      description = ''Area code for server and all modems.'';
+    };
+
+    longDistancePrefix = mkOption {
+      type = nullOr str;
+      default = null;
+      example = "0";
+      description = ''Long distance prefix for server and all modems.'';
+    };
+
+    internationalPrefix = mkOption {
+      type = nullOr str;
+      default = null;
+      example = "00";
+      description = ''International prefix for server and all modems.'';
+    };
+
+    spoolAreaPath = mkOption {
+      type = path;
+      default = "/var/spool/fax";
+      description = ''
+        The spooling area will be created/maintained
+        at the location given here.
+      '';
+    };
+
+    userAccessFile = mkOption {
+      type = path;
+      default = "/etc/hosts.hfaxd";
+      description = ''
+        The <filename>hosts.hfaxd</filename>
+        file entry in the spooling area
+        will be symlinked to the location given here.
+        This file must exist and be
+        readable only by the <literal>uucp</literal> user.
+        See hosts.hfaxd(5) for details.
+        This configuration permits access for all users:
+        <literal>
+          environment.etc."hosts.hfaxd" = {
+            mode = "0600";
+            user = "uucp";
+            text = ".*";
+          };
+        </literal>
+        Note that host-based access can be controlled with
+        <option>config.systemd.sockets.hylafax-hfaxd.listenStreams</option>;
+        by default, only 127.0.0.1 is permitted to connect.
+      '';
+    };
+
+    sendmailPath = mkOption {
+      type = path;
+      example = literalExample "''${pkgs.postfix}/bin/sendmail";
+      # '' ;  # fix vim
+      description = ''
+        Path to <filename>sendmail</filename> program.
+        The default uses the local sendmail wrapper
+        (see <option>config.services.mail.sendmailSetuidWrapper</option>),
+        otherwise the <filename>false</filename>
+        binary to cause an error if used.
+      '';
+    };
+
+    hfaxdConfig = mkOption {
+      type = configAttrType;
+      example.RecvqProtection = "0400";
+      description = ''
+        Attribute set of lines for the global
+        hfaxd config file <filename>etc/hfaxd.conf</filename>.
+        ${commonDescr}
+      '';
+    };
+
+    faxqConfig = mkOption {
+      type = configAttrType;
+      example = {
+        InternationalPrefix = "00";
+        LongDistancePrefix = "0";
+      };
+      description = ''
+        Attribute set of lines for the global
+        faxq config file <filename>etc/config</filename>.
+        ${commonDescr}
+      '';
+    };
+
+    commonModemConfig = mkOption {
+      type = configAttrType;
+      example = {
+        InternationalPrefix = "00";
+        LongDistancePrefix = "0";
+      };
+      description = ''
+        Attribute set of default values for
+        modem config files <filename>etc/config.*</filename>.
+        ${commonDescr}
+        Think twice before changing
+        paths of fax-processing scripts.
+      '';
+    };
+
+    modems = mkOption {
+      type = loaOf (submodule [ modemConfigOptions ]);
+      default = {};
+      example.ttyS1 = {
+        type = "cirrus";
+        config = {
+          FAXNumber = "123456";
+          LocalIdentifier = "Smith";
+        };
+      };
+      description = ''
+        Description of installed modems.
+        At least on modem must be defined
+        to enable the HylaFAX server.
+      '';
+    };
+
+    spoolExtraInit = mkOption {
+      type = lines;
+      default = "";
+      example = ''chmod 0755 .  # everyone may read my faxes'';
+      description = ''
+        Additional shell code that is executed within the
+        spooling area directory right after its setup.
+      '';
+    };
+
+    faxcron.enable.spoolInit = mkEnableOption ''
+      Purge old files from the spooling area with
+      <filename>faxcron</filename>
+      each time the spooling area is initialized.
+    '';
+    faxcron.enable.frequency = mkOption {
+      type = nullOr str1;
+      default = null;
+      example = "daily";
+      description = ''
+        Purge old files from the spooling area with
+        <filename>faxcron</filename> with the given frequency
+        (see systemd.time(7)).
+      '';
+    };
+    faxcron.infoDays = mkOption {
+      type = int1;
+      default = 30;
+      description = ''
+        Set the expiration time for data in the
+        remote machine information directory in days.
+      '';
+    };
+    faxcron.logDays = mkOption {
+      type = int1;
+      default = 30;
+      description = ''
+        Set the expiration time for
+        session trace log files in days.
+      '';
+    };
+    faxcron.rcvDays = mkOption {
+      type = int1;
+      default = 7;
+      description = ''
+        Set the expiration time for files in
+        the received facsimile queue in days.
+      '';
+    };
+
+    faxqclean.enable.spoolInit = mkEnableOption ''
+      Purge old files from the spooling area with
+      <filename>faxqclean</filename>
+      each time the spooling area is initialized.
+    '';
+    faxqclean.enable.frequency = mkOption {
+      type = nullOr str1;
+      default = null;
+      example = "daily";
+      description = ''
+        Purge old files from the spooling area with
+        <filename>faxcron</filename> with the given frequency
+        (see systemd.time(7)).
+      '';
+    };
+    faxqclean.archiving = mkOption {
+      type = enum [ "never" "as-flagged" "always" ];
+      default = "as-flagged";
+      example = "always";
+      description = ''
+        Enable or suppress job archiving:
+        <literal>never</literal> disables job archiving,
+        <literal>as-flagged</literal> archives jobs that
+        have been flagged for archiving by sendfax,
+        <literal>always</literal> forces archiving of all jobs.
+        See also sendfax(1) and faxqclean(8).
+      '';
+    };
+    faxqclean.doneqMinutes = mkOption {
+      type = int1;
+      default = 15;
+      example = literalExample ''24*60'';
+      description = ''
+        Set the job
+        age threshold (in minutes) that controls how long
+        jobs may reside in the doneq directory.
+      '';
+    };
+    faxqclean.docqMinutes = mkOption {
+      type = int1;
+      default = 60;
+      example = literalExample ''24*60'';
+      description = ''
+        Set the document
+        age threshold (in minutes) that controls how long
+        unreferenced files may reside in the docq directory.
+      '';
+    };
+
+  };
+
+
+  config.services.hylafax =
+    mkIf
+    (config.services.hylafax.enable)
+    (mkMerge [ defaultConfig localConfig ])
+  ;
+
+}
diff --git a/nixos/modules/services/networking/hylafax/spool.sh b/nixos/modules/services/networking/hylafax/spool.sh
new file mode 100755
index 000000000000..31e930e8c597
--- /dev/null
+++ b/nixos/modules/services/networking/hylafax/spool.sh
@@ -0,0 +1,111 @@
+#! @shell@ -e
+
+# The following lines create/update the HylaFAX spool directory:
+# Subdirectories/files with persistent data are kept,
+# other directories/files are removed/recreated,
+# mostly from the template spool
+# directory in the HylaFAX package.
+
+# This block explains how the spool area is
+# derived from the spool template in the HylaFAX package:
+#
+#                  + capital letter: directory; file otherwise
+#                  + P/p: persistent directory
+#                  + F/f: directory with symlinks per entry
+#                  + T/t: temporary data
+#                  + S/s: single symlink into package
+#                  |
+#                  | + u: change ownership to uucp:uucp
+#                  | + U: ..also change access mode to user-only
+#                  | |
+# archive          P U
+# bin              S
+# client           T u  (client connection info)
+# config           S
+# COPYRIGHT        s
+# dev              T u  (maybe some FIFOs)
+# docq             P U
+# doneq            P U
+# etc              F    contains customized config files!
+# etc/hosts.hfaxd  f
+# etc/xferfaxlog   f
+# info             P u  (database of called devices)
+# log              P u  (communication logs)
+# pollq            P U
+# recvq            P u
+# sendq            P U
+# status           T u  (modem status info files)
+# tmp              T U
+
+
+shopt -s dotglob  # if bash sees "*", it also includes dot files
+lnsym () { ln --symbol "$@" ; }
+lnsymfrc () { ln --symbolic --force "$@" ; }
+cprd () { cp --remove-destination "$@" ; }
+update () { install --owner=@faxuser@ --group=@faxgroup@ "$@" ; }
+
+
+## create/update spooling area
+
+update --mode=0750 -d "@spoolAreaPath@"
+cd "@spoolAreaPath@"
+
+persist=(archive docq doneq info log pollq recvq sendq)
+
+# remove entries that don't belong here
+touch dummy  # ensure "*" resolves to something
+for k in *
+do
+  keep=0
+  for j in "${persist[@]}" xferfaxlog clientlog faxcron.lastrun
+  do
+    if test "$k" == "$j"
+    then
+      keep=1
+      break
+    fi
+  done
+  if test "$keep" == "0"
+  then
+    rm --recursive "$k"
+  fi
+done
+
+# create persistent data directories (unless they exist already)
+update --mode=0700 -d "${persist[@]}"
+chmod 0755 info log recvq
+
+# create ``xferfaxlog``, ``faxcron.lastrun``, ``clientlog``
+touch clientlog faxcron.lastrun xferfaxlog
+chown @faxuser@:@faxgroup@ clientlog faxcron.lastrun xferfaxlog
+
+# create symlinks for frozen directories/files
+lnsym --target-directory=. "@hylafax@"/spool/{COPYRIGHT,bin,config}
+
+# create empty temporary directories
+update --mode=0700 -d client dev status
+update -d tmp
+
+
+## create and fill etc
+
+install -d "@spoolAreaPath@/etc"
+cd "@spoolAreaPath@/etc"
+
+# create symlinks to all files in template's etc
+lnsym --target-directory=. "@hylafax@/spool/etc"/*
+
+# set LOCKDIR in setup.cache
+sed --regexp-extended 's|^(UUCP_LOCKDIR=).*$|\1'"'@lockPath@'|g" --in-place setup.cache
+
+# etc/{xferfaxlog,lastrun} are stored in the spool root
+lnsymfrc --target-directory=. ../xferfaxlog
+lnsymfrc --no-target-directory ../faxcron.lastrun lastrun
+
+# etc/hosts.hfaxd is provided by the NixOS configuration
+lnsymfrc --no-target-directory "@userAccessFile@" hosts.hfaxd
+
+# etc/config and etc/config.${DEVID} must be copied:
+# hfaxd reads these file after locking itself up in a chroot
+cprd --no-target-directory "@globalConfigPath@" config
+cprd --target-directory=. "@modemConfigPath@"/*
diff --git a/nixos/modules/services/networking/hylafax/systemd.nix b/nixos/modules/services/networking/hylafax/systemd.nix
new file mode 100644
index 000000000000..91d9c1a37da6
--- /dev/null
+++ b/nixos/modules/services/networking/hylafax/systemd.nix
@@ -0,0 +1,249 @@
+{ config, lib, pkgs, ... }:
+
+
+let
+
+  inherit (lib) mkIf mkMerge;
+  inherit (lib) concatStringsSep optionalString;
+
+  cfg = config.services.hylafax;
+  mapModems = lib.flip map (lib.attrValues cfg.modems);
+
+  mkConfigFile = name: conf:
+    # creates hylafax config file,
+    # makes sure "Include" is listed *first*
+    let
+      mkLines = conf:
+        (lib.concatLists
+        (lib.flip lib.mapAttrsToList conf
+        (k: map (v: ''${k}: ${v}'')
+      )));
+      include = mkLines { Include = conf.Include or []; };
+      other = mkLines ( conf // { Include = []; } );
+    in
+      pkgs.writeText ''hylafax-config${name}''
+      (concatStringsSep "\n" (include ++ other));
+
+  globalConfigPath = mkConfigFile "" cfg.faxqConfig;
+
+  modemConfigPath =
+    let
+      mkModemConfigFile = { config, name, ... }:
+        mkConfigFile ''.${name}''
+        (cfg.commonModemConfig // config);
+      mkLine = { name, type, ... }@modem: ''
+        # check if modem config file exists:
+        test -f "${pkgs.hylafaxplus}/spool/config/${type}"
+        ln \
+          --symbolic \
+          --no-target-directory \
+          "${mkModemConfigFile modem}" \
+          "$out/config.${name}"
+      '';
+    in
+      pkgs.runCommand "hylafax-config-modems" {}
+      ''mkdir --parents "$out/" ${concatStringsSep "\n" (mapModems mkLine)}'';
+
+  setupSpoolScript = pkgs.substituteAll {
+    name = "hylafax-setup-spool.sh";
+    src = ./spool.sh;
+    isExecutable = true;
+    inherit (pkgs.stdenv) shell;
+    hylafax = pkgs.hylafaxplus;
+    faxuser = "uucp";
+    faxgroup = "uucp";
+    lockPath = "/var/lock";
+    inherit globalConfigPath modemConfigPath;
+    inherit (cfg) sendmailPath spoolAreaPath userAccessFile;
+  };
+
+  waitFaxqScript = pkgs.substituteAll {
+    # This script checks the modems status files
+    # and waits until all modems report readiness.
+    name = "hylafax-faxq-wait-start.sh";
+    src = ./faxq-wait.sh;
+    isExecutable = true;
+    timeoutSec = toString 10;
+    inherit (pkgs.stdenv) shell;
+    inherit (cfg) spoolAreaPath;
+  };
+
+  sockets."hylafax-hfaxd" = {
+    description = "HylaFAX server socket";
+    documentation = [ "man:hfaxd(8)" ];
+    wantedBy = [ "multi-user.target" ];
+    listenStreams = [ "127.0.0.1:4559" ];
+    socketConfig.FreeBind = true;
+    socketConfig.Accept = true;
+  };
+
+  paths."hylafax-faxq" = {
+    description = "HylaFAX queue manager sendq watch";
+    documentation = [ "man:faxq(8)" "man:sendq(5)" ];
+    wantedBy = [ "multi-user.target" ];
+    pathConfig.PathExistsGlob = [ ''${cfg.spoolAreaPath}/sendq/q*'' ];
+  };
+
+  timers = mkMerge [
+    (
+      mkIf (cfg.faxcron.enable.frequency!=null)
+      { "hylafax-faxcron".timerConfig.Persistent = true; }
+    )
+    (
+      mkIf (cfg.faxqclean.enable.frequency!=null)
+      { "hylafax-faxqclean".timerConfig.Persistent = true; }
+    )
+  ];
+
+  hardenService =
+    # Add some common systemd service hardening settings,
+    # but allow each service (here) to override
+    # settings by explicitely setting those to `null`.
+    # More hardening would be nice but makes
+    # customizing hylafax setups very difficult.
+    # If at all, it should only be added along
+    # with some options to customize it.
+    let
+      hardening = {
+        PrivateDevices = true;  # breaks /dev/tty...
+        PrivateNetwork = true;
+        PrivateTmp = true;
+        ProtectControlGroups = true;
+        #ProtectHome = true;  # breaks custom spool dirs
+        ProtectKernelModules = true;
+        ProtectKernelTunables = true;
+        #ProtectSystem = "strict";  # breaks custom spool dirs
+        RestrictNamespaces = true;
+        RestrictRealtime = true;
+      };
+      filter = key: value: (value != null) || ! (lib.hasAttr key hardening);
+      apply = service: lib.filterAttrs filter (hardening // (service.serviceConfig or {}));
+    in
+      service: service // { serviceConfig = apply service; };
+
+  services."hylafax-spool" = {
+    description = "HylaFAX spool area preparation";
+    documentation = [ "man:hylafax-server(4)" ];
+    script = ''
+      ${setupSpoolScript}
+      cd "${cfg.spoolAreaPath}"
+      ${cfg.spoolExtraInit}
+      if ! test -f "${cfg.spoolAreaPath}/etc/hosts.hfaxd"
+      then
+        echo hosts.hfaxd is missing
+        exit 1
+      fi
+    '';
+    serviceConfig.ExecStop = ''${setupSpoolScript}'';
+    serviceConfig.RemainAfterExit = true;
+    serviceConfig.Type = "oneshot";
+    unitConfig.RequiresMountsFor = [ cfg.spoolAreaPath ];
+  };
+
+  services."hylafax-faxq" = {
+    description = "HylaFAX queue manager";
+    documentation = [ "man:faxq(8)" ];
+    requires = [ "hylafax-spool.service" ];
+    after = [ "hylafax-spool.service" ];
+    wants = mapModems ( { name, ... }: ''hylafax-faxgetty@${name}.service'' );
+    wantedBy = mkIf cfg.autostart [ "multi-user.target" ];
+    serviceConfig.Type = "forking";
+    serviceConfig.ExecStart = ''${pkgs.hylafaxplus}/spool/bin/faxq -q "${cfg.spoolAreaPath}"'';
+    # This delays the "readiness" of this service until
+    # all modems are initialized (or a timeout is reached).
+    # Otherwise, sending a fax with the fax service
+    # stopped will always yield a failed send attempt:
+    # The fax service is started when the job is created with
+    # `sendfax`, but modems need some time to initialize.
+    serviceConfig.ExecStartPost = [ ''${waitFaxqScript}'' ];
+    # faxquit fails if the pipe is already gone
+    # (e.g. the service is already stopping)
+    serviceConfig.ExecStop = ''-${pkgs.hylafaxplus}/spool/bin/faxquit -q "${cfg.spoolAreaPath}"'';
+    # disable some systemd hardening settings
+    serviceConfig.PrivateDevices = null;
+    serviceConfig.RestrictRealtime = null;
+  };
+
+  services."hylafax-hfaxd@" = {
+    description = "HylaFAX server";
+    documentation = [ "man:hfaxd(8)" ];
+    after = [ "hylafax-faxq.service" ];
+    requires = [ "hylafax-faxq.service" ];
+    serviceConfig.StandardInput = "socket";
+    serviceConfig.StandardOutput = "socket";
+    serviceConfig.ExecStart = ''${pkgs.hylafaxplus}/spool/bin/hfaxd -q "${cfg.spoolAreaPath}" -d -I'';
+    unitConfig.RequiresMountsFor = [ cfg.userAccessFile ];
+    # disable some systemd hardening settings
+    serviceConfig.PrivateDevices = null;
+    serviceConfig.PrivateNetwork = null;
+  };
+
+  services."hylafax-faxcron" = rec {
+    description = "HylaFAX spool area maintenance";
+    documentation = [ "man:faxcron(8)" ];
+    after = [ "hylafax-spool.service" ];
+    requires = [ "hylafax-spool.service" ];
+    wantedBy = mkIf cfg.faxcron.enable.spoolInit requires;
+    startAt = mkIf (cfg.faxcron.enable.frequency!=null) cfg.faxcron.enable.frequency;
+    serviceConfig.ExecStart = concatStringsSep " " [
+      ''${pkgs.hylafaxplus}/spool/bin/faxcron''
+      ''-q "${cfg.spoolAreaPath}"''
+      ''-info ${toString cfg.faxcron.infoDays}''
+      ''-log  ${toString cfg.faxcron.logDays}''
+      ''-rcv  ${toString cfg.faxcron.rcvDays}''
+    ];
+  };
+
+  services."hylafax-faxqclean" = rec {
+    description = "HylaFAX spool area queue cleaner";
+    documentation = [ "man:faxqclean(8)" ];
+    after = [ "hylafax-spool.service" ];
+    requires = [ "hylafax-spool.service" ];
+    wantedBy = mkIf cfg.faxqclean.enable.spoolInit requires;
+    startAt = mkIf (cfg.faxqclean.enable.frequency!=null) cfg.faxqclean.enable.frequency;
+    serviceConfig.ExecStart = concatStringsSep " " [
+      ''${pkgs.hylafaxplus}/spool/bin/faxqclean''
+      ''-q "${cfg.spoolAreaPath}"''
+      ''-v''
+      (optionalString (cfg.faxqclean.archiving!="never") ''-a'')
+      (optionalString (cfg.faxqclean.archiving=="always")  ''-A'')
+      ''-j ${toString (cfg.faxqclean.doneqMinutes*60)}''
+      ''-d ${toString (cfg.faxqclean.docqMinutes*60)}''
+    ];
+  };
+
+  mkFaxgettyService = { name, ... }:
+    lib.nameValuePair ''hylafax-faxgetty@${name}'' rec {
+      description = "HylaFAX faxgetty for %I";
+      documentation = [ "man:faxgetty(8)" ];
+      bindsTo = [ "dev-%i.device" ];
+      requires = [ "hylafax-spool.service" ];
+      after = bindsTo ++ requires;
+      before = [ "hylafax-faxq.service" "getty.target" ];
+      unitConfig.StopWhenUnneeded = true;
+      unitConfig.AssertFileNotEmpty = ''${cfg.spoolAreaPath}/etc/config.%I'';
+      serviceConfig.UtmpIdentifier = "%I";
+      serviceConfig.TTYPath = "/dev/%I";
+      serviceConfig.Restart = "always";
+      serviceConfig.KillMode = "process";
+      serviceConfig.IgnoreSIGPIPE = false;
+      serviceConfig.ExecStart = ''-${pkgs.hylafaxplus}/spool/bin/faxgetty -q "${cfg.spoolAreaPath}" /dev/%I'';
+      # faxquit fails if the pipe is already gone
+      # (e.g. the service is already stopping)
+      serviceConfig.ExecStop = ''-${pkgs.hylafaxplus}/spool/bin/faxquit -q "${cfg.spoolAreaPath}" %I'';
+      # disable some systemd hardening settings
+      serviceConfig.PrivateDevices = null;
+      serviceConfig.RestrictRealtime = null;
+    };
+
+  modemServices =
+    lib.listToAttrs (mapModems mkFaxgettyService);
+
+in
+
+{
+  config.systemd = mkIf cfg.enable {
+    inherit sockets timers paths;
+    services = lib.mapAttrs (lib.const hardenService) (services // modemServices);
+  };
+}
diff --git a/nixos/modules/services/networking/i2pd.nix b/nixos/modules/services/networking/i2pd.nix
index 3afafaf3fed5..0e9b354cfcaf 100644
--- a/nixos/modules/services/networking/i2pd.nix
+++ b/nixos/modules/services/networking/i2pd.nix
@@ -8,6 +8,17 @@ let
 
   homeDir = "/var/lib/i2pd";
 
+  strOpt = k: v: k + " = " + v;
+  boolOpt = k: v: k + " = " + boolToString v;
+  intOpt = k: v: k + " = " + toString v;
+  lstOpt = k: xs: k + " = " + concatStringsSep "," xs;
+  optionalNullString = o: s: optional (! isNull s) (strOpt o s);
+  optionalNullBool = o: b: optional (! isNull b) (boolOpt o b);
+  optionalNullInt = o: i: optional (! isNull i) (intOpt o i);
+  optionalEmptyList = o: l: optional ([] != l) (lstOpt o l);
+
+  mkEnableTrueOption = name: mkEnableOption name // { default = true; };
+
   mkEndpointOpt = name: addr: port: {
     enable = mkEnableOption name;
     name = mkOption {
@@ -18,42 +29,54 @@ let
     address = mkOption {
       type = types.str;
       default = addr;
-      description = "Bind address for ${name} endpoint. Default: " + addr;
+      description = "Bind address for ${name} endpoint.";
     };
     port = mkOption {
       type = types.int;
       default = port;
-      description = "Bind port for ${name} endoint. Default: " + toString port;
+      description = "Bind port for ${name} endoint.";
     };
   };
 
-  mkKeyedEndpointOpt = name: addr: port: keyFile:
+  i2cpOpts = name: {
+    length = mkOption {
+      type = types.int;
+      description = "Guaranteed minimum hops for ${name} tunnels.";
+      default = 3;
+    };
+    quantity = mkOption {
+      type = types.int;
+      description = "Number of simultaneous ${name} tunnels.";
+      default = 5;
+    };
+  };
+
+  mkKeyedEndpointOpt = name: addr: port: keyloc:
     (mkEndpointOpt name addr port) // {
       keys = mkOption {
-        type = types.str;
-        default = "";
+        type = with types; nullOr str;
+        default = keyloc;
         description = ''
           File to persist ${lib.toUpper name} keys.
         '';
       };
-    };
-
-  commonTunOpts = let
-    i2cpOpts = {
-      length = mkOption {
-        type = types.int;
-        description = "Guaranteed minimum hops.";
-        default = 3;
+      inbound = i2cpOpts name;
+      outbound = i2cpOpts name;
+      latency.min = mkOption {
+        type = with types; nullOr int;
+        description = "Min latency for tunnels.";
+        default = null;
       };
-      quantity = mkOption {
-        type = types.int;
-        description = "Number of simultaneous tunnels.";
-        default = 5;
+      latency.max = mkOption {
+        type = with types; nullOr int;
+        description = "Max latency for tunnels.";
+        default = null;
       };
     };
-  in name: {
-    outbound = i2cpOpts;
-    inbound = i2cpOpts;
+
+  commonTunOpts = name: {
+    outbound = i2cpOpts name;
+    inbound = i2cpOpts name;
     crypto.tagsToSend = mkOption {
       type = types.int;
       description = "Number of ElGamal/AES tags to send.";
@@ -70,94 +93,142 @@ let
     };
   } // mkEndpointOpt name "127.0.0.1" 0;
 
-  i2pdConf = pkgs.writeText "i2pd.conf" ''
-    # DO NOT EDIT -- this file has been generated automatically.
-    loglevel = ${cfg.logLevel}
-
-    ipv4 = ${boolToString cfg.enableIPv4}
-    ipv6 = ${boolToString cfg.enableIPv6}
-    notransit = ${boolToString cfg.notransit}
-    floodfill = ${boolToString cfg.floodfill}
-    netid = ${toString cfg.netid}
-    ${if isNull cfg.bandwidth then "" else "bandwidth = ${toString cfg.bandwidth}" }
-    ${if isNull cfg.port then "" else "port = ${toString cfg.port}"}
-
-    [limits]
-    transittunnels = ${toString cfg.limits.transittunnels}
-
-    [upnp]
-    enabled = ${boolToString cfg.upnp.enable}
-    name = ${cfg.upnp.name}
-
-    [precomputation]
-    elgamal = ${boolToString cfg.precomputation.elgamal}
-
-    [reseed]
-    verify = ${boolToString cfg.reseed.verify}
-    file = ${cfg.reseed.file}
-    urls = ${builtins.concatStringsSep "," cfg.reseed.urls}
-
-    [addressbook]
-    defaulturl = ${cfg.addressbook.defaulturl}
-    subscriptions = ${builtins.concatStringsSep "," cfg.addressbook.subscriptions}
-
-    ${flip concatMapStrings
+  sec = name: "\n[" + name + "]";
+  notice = "# DO NOT EDIT -- this file has been generated automatically.";
+  i2pdConf = let
+    opts = [
+      notice
+      (strOpt "loglevel" cfg.logLevel)
+      (boolOpt "logclftime" cfg.logCLFTime)
+      (boolOpt "ipv4" cfg.enableIPv4)
+      (boolOpt "ipv6" cfg.enableIPv6)
+      (boolOpt "notransit" cfg.notransit)
+      (boolOpt "floodfill" cfg.floodfill)
+      (intOpt "netid" cfg.netid)
+    ] ++ (optionalNullInt "bandwidth" cfg.bandwidth)
+      ++ (optionalNullInt "port" cfg.port)
+      ++ (optionalNullString "family" cfg.family)
+      ++ (optionalNullString "datadir" cfg.dataDir)
+      ++ (optionalNullInt "share" cfg.share)
+      ++ (optionalNullBool "ssu" cfg.ssu)
+      ++ (optionalNullBool "ntcp" cfg.ntcp)
+      ++ (optionalNullString "ntcpproxy" cfg.ntcpProxy)
+      ++ (optionalNullString "ifname" cfg.ifname)
+      ++ (optionalNullString "ifname4" cfg.ifname4)
+      ++ (optionalNullString "ifname6" cfg.ifname6)
+      ++ [
+      (sec "limits")
+      (intOpt "transittunnels" cfg.limits.transittunnels)
+      (intOpt "coresize" cfg.limits.coreSize)
+      (intOpt "openfiles" cfg.limits.openFiles)
+      (intOpt "ntcphard" cfg.limits.ntcpHard)
+      (intOpt "ntcpsoft" cfg.limits.ntcpSoft)
+      (intOpt "ntcpthreads" cfg.limits.ntcpThreads)
+      (sec "upnp")
+      (boolOpt "enabled" cfg.upnp.enable)
+      (sec "precomputation")
+      (boolOpt "elgamal" cfg.precomputation.elgamal)
+      (sec "reseed")
+      (boolOpt "verify" cfg.reseed.verify)
+    ] ++ (optionalNullString "file" cfg.reseed.file)
+      ++ (optionalEmptyList "urls" cfg.reseed.urls)
+      ++ (optionalNullString "floodfill" cfg.reseed.floodfill)
+      ++ (optionalNullString "zipfile" cfg.reseed.zipfile)
+      ++ (optionalNullString "proxy" cfg.reseed.proxy)
+      ++ [
+      (sec "trust")
+      (boolOpt "enabled" cfg.trust.enable)
+      (boolOpt "hidden" cfg.trust.hidden)
+    ] ++ (optionalEmptyList "routers" cfg.trust.routers)
+      ++ (optionalNullString "family" cfg.trust.family)
+      ++ [
+      (sec "websockets")
+      (boolOpt "enabled" cfg.websocket.enable)
+      (strOpt "address" cfg.websocket.address)
+      (intOpt "port" cfg.websocket.port)
+      (sec "exploratory")
+      (intOpt "inbound.length" cfg.exploratory.inbound.length)
+      (intOpt "inbound.quantity" cfg.exploratory.inbound.quantity)
+      (intOpt "outbound.length" cfg.exploratory.outbound.length)
+      (intOpt "outbound.quantity" cfg.exploratory.outbound.quantity)
+      (sec "ntcp2")
+      (boolOpt "enabled" cfg.ntcp2.enable)
+      (boolOpt "published" cfg.ntcp2.published)
+      (intOpt "port" cfg.ntcp2.port)
+      (sec "addressbook")
+      (strOpt "defaulturl" cfg.addressbook.defaulturl)
+    ] ++ (optionalEmptyList "subscriptions" cfg.addressbook.subscriptions)
+      ++ (flip map
       (collect (proto: proto ? port && proto ? address && proto ? name) cfg.proto)
-      (proto: ''
-        [${proto.name}]
-        enabled = ${boolToString proto.enable}
-        address = ${proto.address}
-        port = ${toString proto.port}
-        ${if proto ? keys then "keys = ${proto.keys}" else ""}
-        ${if proto ? auth then "auth = ${boolToString proto.auth}" else ""}
-        ${if proto ? user then "user = ${proto.user}" else ""}
-        ${if proto ? pass then "pass = ${proto.pass}" else ""}
-        ${if proto ? outproxy then "outproxy = ${proto.outproxy}" else ""}
-        ${if proto ? outproxyPort then "outproxyport = ${toString proto.outproxyPort}" else ""}
-      '')
-    }
-  '';
-
-  i2pdTunnelConf = pkgs.writeText "i2pd-tunnels.conf" ''
-    # DO NOT EDIT -- this file has been generated automatically.
-    ${flip concatMapStrings
+      (proto: let protoOpts = [
+        (sec proto.name)
+        (boolOpt "enabled" proto.enable)
+        (strOpt "address" proto.address)
+        (intOpt "port" proto.port)
+        ] ++ (if proto ? keys then optionalNullString "keys" proto.keys else [])
+        ++ (if proto ? auth then optionalNullBool "auth" proto.auth else [])
+        ++ (if proto ? user then optionalNullString "user" proto.user else [])
+        ++ (if proto ? pass then optionalNullString "pass" proto.pass else [])
+        ++ (if proto ? strictHeaders then optionalNullBool "strictheaders" proto.strictHeaders else [])
+        ++ (if proto ? hostname then optionalNullString "hostname" proto.hostname else [])
+        ++ (if proto ? outproxy then optionalNullString "outproxy" proto.outproxy else [])
+        ++ (if proto ? outproxyPort then optionalNullInt "outproxyport" proto.outproxyPort else [])
+        ++ (if proto ? outproxyEnable then optionalNullBool "outproxy.enabled" proto.outproxyEnable else []);
+        in (concatStringsSep "\n" protoOpts)
+      ));
+  in
+    pkgs.writeText "i2pd.conf" (concatStringsSep "\n" opts);
+
+  tunnelConf = let opts = [
+    notice
+    (flip map
       (collect (tun: tun ? port && tun ? destination) cfg.outTunnels)
-      (tun: ''
-        [${tun.name}]
-        type = client
-        destination = ${tun.destination}
-        destinationport = ${toString tun.destinationPort}
-        keys = ${tun.keys}
-        address = ${tun.address}
-        port = ${toString tun.port}
-        inbound.length = ${toString tun.inbound.length}
-        outbound.length = ${toString tun.outbound.length}
-        inbound.quantity = ${toString tun.inbound.quantity}
-        outbound.quantity = ${toString tun.outbound.quantity}
-        crypto.tagsToSend = ${toString tun.crypto.tagsToSend}
-      '')
-    }
-    ${flip concatMapStrings
+      (tun: let outTunOpts = [
+        (sec tun.name)
+        "type = client"
+        (intOpt "port" tun.port)
+        (strOpt "destination" tun.destination)
+        ] ++ (if tun ? destinationPort then optionalNullInt "destinationport" tun.destinationPort else [])
+        ++ (if tun ? keys then
+            optionalNullString "keys" tun.keys else [])
+        ++ (if tun ? address then
+            optionalNullString "address" tun.address else [])
+        ++ (if tun ? inbound.length then
+            optionalNullInt "inbound.length" tun.inbound.length else [])
+        ++ (if tun ? inbound.quantity then
+            optionalNullInt "inbound.quantity" tun.inbound.quantity else [])
+        ++ (if tun ? outbound.length then
+            optionalNullInt "outbound.length" tun.outbound.length else [])
+        ++ (if tun ? outbound.quantity then
+            optionalNullInt "outbound.quantity" tun.outbound.quantity else [])
+        ++ (if tun ? crypto.tagsToSend then
+            optionalNullInt "crypto.tagstosend" tun.crypto.tagsToSend else []);
+        in concatStringsSep "\n" outTunOpts))
+    (flip map
       (collect (tun: tun ? port && tun ? address) cfg.inTunnels)
-      (tun: ''
-        [${tun.name}]
-        type = server
-        destination = ${tun.destination}
-        keys = ${tun.keys}
-        host = ${tun.address}
-        port = ${toString tun.port}
-        inport = ${toString tun.inPort}
-        accesslist = ${builtins.concatStringsSep "," tun.accessList}
-      '')
-    }
-  '';
+      (tun: let inTunOpts = [
+        (sec tun.name)
+        "type = server"
+        (intOpt "port" tun.port)
+        (strOpt "host" tun.address)
+      ] ++ (if tun ? destination then
+            optionalNullString "destination" tun.destination else [])
+        ++ (if tun ? keys then
+            optionalNullString "keys" tun.keys else [])
+        ++ (if tun ? inPort then
+            optionalNullInt "inport" tun.inPort else [])
+        ++ (if tun ? accessList then
+            optionalEmptyList "accesslist" tun.accessList else []);
+        in concatStringsSep "\n" inTunOpts))];
+    in pkgs.writeText "i2pd-tunnels.conf" opts;
 
   i2pdSh = pkgs.writeScriptBin "i2pd" ''
     #!/bin/sh
     exec ${pkgs.i2pd}/bin/i2pd \
       ${if isNull cfg.address then "" else "--host="+cfg.address} \
+      --service \
       --conf=${i2pdConf} \
-      --tunconf=${i2pdTunnelConf}
+      --tunconf=${tunnelConf}
   '';
 
 in
@@ -170,9 +241,7 @@ in
 
     services.i2pd = {
 
-      enable = mkOption {
-        type = types.bool;
-        default = false;
+      enable = mkEnableOption "I2Pd daemon" // {
         description = ''
           Enables I2Pd as a running service upon activation.
           Please read http://i2pd.readthedocs.io/en/latest/ for further
@@ -192,6 +261,8 @@ in
         '';
       };
 
+      logCLFTime = mkEnableOption "Full CLF-formatted date and time to log";
+
       address = mkOption {
         type = with types; nullOr str;
         default = null;
@@ -200,17 +271,72 @@ in
         '';
       };
 
-      notransit = mkOption {
-        type = types.bool;
-        default = false;
+      family = mkOption {
+        type = with types; nullOr str;
+        default = null;
+        description = ''
+          Specify a family the router belongs to.
+        '';
+      };
+
+      dataDir = mkOption {
+        type = with types; nullOr str;
+        default = null;
+        description = ''
+          Alternative path to storage of i2pd data (RI, keys, peer profiles, ...)
+        '';
+      };
+
+      share = mkOption {
+        type = types.int;
+        default = 100;
+        description = ''
+          Limit of transit traffic from max bandwidth in percents.
+        '';
+      };
+
+      ifname = mkOption {
+        type = with types; nullOr str;
+        default = null;
+        description = ''
+          Network interface to bind to.
+        '';
+      };
+
+      ifname4 = mkOption {
+        type = with types; nullOr str;
+        default = null;
+        description = ''
+          IPv4 interface to bind to.
+        '';
+      };
+
+      ifname6 = mkOption {
+        type = with types; nullOr str;
+        default = null;
+        description = ''
+          IPv6 interface to bind to.
+        '';
+      };
+
+      ntcpProxy = mkOption {
+        type = with types; nullOr str;
+        default = null;
+        description = ''
+          Proxy URL for NTCP transport.
+        '';
+      };
+
+      ntcp = mkEnableTrueOption "ntcp";
+      ssu = mkEnableTrueOption "ssu";
+
+      notransit = mkEnableOption "notransit" // {
         description = ''
           Tells the router to not accept transit tunnels during startup.
         '';
       };
 
-      floodfill = mkOption {
-        type = types.bool;
-        default = false;
+      floodfill = mkEnableOption "floodfill" // {
         description = ''
           If the router is declared to be unreachable and needs introduction nodes.
         '';
@@ -241,131 +367,178 @@ in
         '';
       };
 
-      enableIPv4 = mkOption {
-        type = types.bool;
-        default = true;
+      enableIPv4 = mkEnableTrueOption "IPv4 connectivity";
+      enableIPv6 = mkEnableOption "IPv6 connectivity";
+      nat = mkEnableTrueOption "NAT bypass";
+
+      upnp.enable = mkEnableOption "UPnP service discovery";
+      upnp.name = mkOption {
+        type = types.str;
+        default = "I2Pd";
         description = ''
-          Enables IPv4 connectivity. Enabled by default.
+          Name i2pd appears in UPnP forwardings list.
         '';
       };
 
-      enableIPv6 = mkOption {
-        type = types.bool;
-        default = false;
+      precomputation.elgamal = mkEnableTrueOption "Precomputed ElGamal tables" // {
         description = ''
-          Enables IPv6 connectivity. Disabled by default.
+          Whenever to use precomputated tables for ElGamal.
+          <command>i2pd</command> defaults to <literal>false</literal>
+          to save 64M of memory (and looses some performance).
+
+          We default to <literal>true</literal> as that is what most
+          users want anyway.
         '';
       };
 
-      nat = mkOption {
-        type = types.bool;
-        default = true;
+      reseed.verify = mkEnableOption "SU3 signature verification";
+
+      reseed.file = mkOption {
+        type = with types; nullOr str;
+        default = null;
         description = ''
-          Assume router is NATed. Enabled by default.
+          Full path to SU3 file to reseed from.
         '';
       };
 
-      upnp = {
-        enable = mkOption {
-          type = types.bool;
-          default = false;
-          description = ''
-            Enables UPnP.
-          '';
-        };
+      reseed.urls = mkOption {
+        type = with types; listOf str;
+        default = [];
+        description = ''
+          Reseed URLs.
+        '';
+      };
 
-        name = mkOption {
-          type = types.str;
-          default = "I2Pd";
-          description = ''
-            Name i2pd appears in UPnP forwardings list.
-          '';
-        };
+      reseed.floodfill = mkOption {
+        type = with types; nullOr str;
+        default = null;
+        description = ''
+          Path to router info of floodfill to reseed from.
+        '';
       };
 
-      precomputation.elgamal = mkOption {
-        type = types.bool;
-        default = true;
+      reseed.zipfile = mkOption {
+        type = with types; nullOr str;
+        default = null;
         description = ''
-          Whenever to use precomputated tables for ElGamal.
-          <command>i2pd</command> defaults to <literal>false</literal>
-          to save 64M of memory (and looses some performance).
+          Path to local .zip file to reseed from.
+        '';
+      };
 
-          We default to <literal>true</literal> as that is what most
-          users want anyway.
+      reseed.proxy = mkOption {
+        type = with types; nullOr str;
+        default = null;
+        description = ''
+          URL for reseed proxy, supports http/socks.
         '';
       };
 
-      reseed = {
-        verify = mkOption {
-          type = types.bool;
-          default = false;
-          description = ''
-            Request SU3 signature verification
-          '';
-        };
+     addressbook.defaulturl = mkOption {
+        type = types.str;
+        default = "http://joajgazyztfssty4w2on5oaqksz6tqoxbduy553y34mf4byv6gpq.b32.i2p/export/alive-hosts.txt";
+        description = ''
+          AddressBook subscription URL for initial setup
+        '';
+      };
+     addressbook.subscriptions = mkOption {
+        type = with types; listOf str;
+        default = [
+          "http://inr.i2p/export/alive-hosts.txt"
+          "http://i2p-projekt.i2p/hosts.txt"
+          "http://stats.i2p/cgi-bin/newhosts.txt"
+        ];
+        description = ''
+          AddressBook subscription URLs
+        '';
+      };
 
-        file = mkOption {
-          type = types.str;
-          default = "";
-          description = ''
-            Full path to SU3 file to reseed from
-          '';
-        };
+      trust.enable = mkEnableOption "Explicit trust options";
 
-        urls = mkOption {
-          type = with types; listOf str;
-          default = [
-            "https://reseed.i2p-project.de/"
-            "https://i2p.mooo.com/netDb/"
-            "https://netdb.i2p2.no/"
-            "https://us.reseed.i2p2.no:444/"
-            "https://uk.reseed.i2p2.no:444/"
-            "https://i2p.manas.ca:8443/"
-          ];
-          description = ''
-            Reseed URLs
-          '';
-        };
+      trust.family = mkOption {
+        type = with types; nullOr str;
+        default = null;
+        description = ''
+          Router Familiy to trust for first hops.
+        '';
       };
 
-      addressbook = {
-       defaulturl = mkOption {
-          type = types.str;
-          default = "http://joajgazyztfssty4w2on5oaqksz6tqoxbduy553y34mf4byv6gpq.b32.i2p/export/alive-hosts.txt";
-          description = ''
-            AddressBook subscription URL for initial setup
-          '';
-        };
-       subscriptions = mkOption {
-          type = with types; listOf str;
-          default = [
-            "http://inr.i2p/export/alive-hosts.txt"
-            "http://i2p-projekt.i2p/hosts.txt"
-            "http://stats.i2p/cgi-bin/newhosts.txt"
-          ];
-          description = ''
-            AddressBook subscription URLs
-          '';
-        };
+      trust.routers = mkOption {
+        type = with types; listOf str;
+        default = [];
+        description = ''
+          Only connect to the listed routers.
+        '';
+      };
+
+      trust.hidden = mkEnableOption "Router concealment.";
+
+      websocket = mkEndpointOpt "websockets" "127.0.0.1" 7666;
+
+      exploratory.inbound = i2cpOpts "exploratory";
+      exploratory.outbound = i2cpOpts "exploratory";
+
+      ntcp2.enable = mkEnableTrueOption "NTCP2.";
+      ntcp2.published = mkEnableOption "NTCP2 publication.";
+      ntcp2.port = mkOption {
+        type = types.int;
+        default = 0;
+        description = ''
+          Port to listen for incoming NTCP2 connections (0=auto).
+        '';
       };
 
       limits.transittunnels = mkOption {
         type = types.int;
         default = 2500;
         description = ''
-          Maximum number of active transit sessions
+          Maximum number of active transit sessions.
+        '';
+      };
+
+      limits.coreSize = mkOption {
+        type = types.int;
+        default = 0;
+        description = ''
+          Maximum size of corefile in Kb (0 - use system limit).
+        '';
+      };
+
+      limits.openFiles = mkOption {
+        type = types.int;
+        default = 0;
+        description = ''
+          Maximum number of open files (0 - use system default).
+        '';
+      };
+
+      limits.ntcpHard = mkOption {
+        type = types.int;
+        default = 0;
+        description = ''
+          Maximum number of active transit sessions.
+        '';
+      };
+
+      limits.ntcpSoft = mkOption {
+        type = types.int;
+        default = 0;
+        description = ''
+          Threshold to start probabalistic backoff with ntcp sessions (default: use system limit).
+        '';
+      };
+
+      limits.ntcpThreads = mkOption {
+        type = types.int;
+        default = 1;
+        description = ''
+          Maximum number of threads used by NTCP DH worker.
         '';
       };
 
       proto.http = (mkEndpointOpt "http" "127.0.0.1" 7070) // {
-        auth = mkOption {
-          type = types.bool;
-          default = false;
-          description = ''
-            Enable authentication for webconsole.
-          '';
-        };
+
+        auth = mkEnableOption "Webconsole authentication";
+
         user = mkOption {
           type = types.str;
           default = "i2pd";
@@ -373,6 +546,7 @@ in
             Username for webconsole access
           '';
         };
+
         pass = mkOption {
           type = types.str;
           default = "i2pd";
@@ -380,11 +554,35 @@ in
             Password for webconsole access.
           '';
         };
+
+        strictHeaders = mkOption {
+          type = with types; nullOr bool;
+          default = null;
+          description = ''
+            Enable strict host checking on WebUI.
+          '';
+        };
+
+        hostname = mkOption {
+          type = with types; nullOr str;
+          default = null;
+          description = ''
+            Expected hostname for WebUI.
+          '';
+        };
       };
 
-      proto.httpProxy = mkKeyedEndpointOpt "httpproxy" "127.0.0.1" 4444 "";
-      proto.socksProxy = (mkKeyedEndpointOpt "socksproxy" "127.0.0.1" 4447 "")
+      proto.httpProxy = (mkKeyedEndpointOpt "httpproxy" "127.0.0.1" 4444 "httpproxy-keys.dat")
+      // {
+        outproxy = mkOption {
+          type = with types; nullOr str;
+          default = null;
+          description = "Upstream outproxy bind address.";
+        };
+      };
+      proto.socksProxy = (mkKeyedEndpointOpt "socksproxy" "127.0.0.1" 4447 "socksproxy-keys.dat")
       // {
+        outproxyEnable = mkEnableOption "SOCKS outproxy";
         outproxy = mkOption {
           type = types.str;
           default = "127.0.0.1";
@@ -408,8 +606,8 @@ in
           { name, ... }: {
             options = {
               destinationPort = mkOption {
-                type = types.int;
-                default = 0;
+                type = with types; nullOr int;
+                default = null;
                 description = "Connect to particular port at destination.";
               };
             } // commonTunOpts name;
diff --git a/nixos/modules/services/networking/iperf3.nix b/nixos/modules/services/networking/iperf3.nix
new file mode 100644
index 000000000000..742404a5692f
--- /dev/null
+++ b/nixos/modules/services/networking/iperf3.nix
@@ -0,0 +1,87 @@
+{ config, lib, pkgs, ... }: with lib;
+let
+  cfg = config.services.iperf3;
+
+  api = {
+    enable = mkEnableOption "iperf3 network throughput testing server";
+    port = mkOption {
+      type        = types.ints.u16;
+      default     = 5201;
+      description = "Server port to listen on for iperf3 client requsts.";
+    };
+    affinity = mkOption {
+      type        = types.nullOr types.ints.unsigned;
+      default     = null;
+      description = "CPU affinity for the process.";
+    };
+    bind = mkOption {
+      type        = types.nullOr types.str;
+      default     = null;
+      description = "Bind to the specific interface associated with the given address.";
+    };
+    verbose = mkOption {
+      type        = types.bool;
+      default     = false;
+      description = "Give more detailed output.";
+    };
+    forceFlush = mkOption {
+      type        = types.bool;
+      default     = false;
+      description = "Force flushing output at every interval.";
+    };
+    debug = mkOption {
+      type        = types.bool;
+      default     = false;
+      description = "Emit debugging output.";
+    };
+    rsaPrivateKey = mkOption {
+      type        = types.nullOr types.path;
+      default     = null;
+      description = "Path to the RSA private key (not password-protected) used to decrypt authentication credentials from the client.";
+    };
+    authorizedUsersFile = mkOption {
+      type        = types.nullOr types.path;
+      default     = null;
+      description = "Path to the configuration file containing authorized users credentials to run iperf tests.";
+    };
+    extraFlags = mkOption {
+      type        = types.listOf types.str;
+      default     = [ ];
+      description = "Extra flags to pass to iperf3(1).";
+    };
+  };
+
+  imp = {
+    systemd.services.iperf3 = {
+      description = "iperf3 daemon";
+      unitConfig.Documentation = "man:iperf3(1) https://iperf.fr/iperf-doc.php";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+
+      serviceConfig = {
+        Restart = "on-failure";
+        RestartSec = 2;
+        DynamicUser = true;
+        PrivateDevices = true;
+        CapabilityBoundingSet = "";
+        NoNewPrivileges = true;
+        ExecStart = ''
+          ${pkgs.iperf3}/bin/iperf \
+            --server \
+            --port ${toString cfg.port} \
+            ${optionalString (cfg.affinity != null) "--affinity ${toString cfg.affinity}"} \
+            ${optionalString (cfg.bind != null) "--bind ${cfg.bind}"} \
+            ${optionalString (cfg.rsaPrivateKey != null) "--rsa-private-key-path ${cfg.rsaPrivateKey}"} \
+            ${optionalString (cfg.authorizedUsersFile != null) "--authorized-users-path ${cfg.authorizedUsersFile}"} \
+            ${optionalString cfg.verbose "--verbose"} \
+            ${optionalString cfg.debug "--debug"} \
+            ${optionalString cfg.forceFlush "--forceflush"} \
+            ${escapeShellArgs cfg.extraFlags}
+        '';
+      };
+    };
+  };
+in {
+  options.services.iperf3 = api;
+  config = mkIf cfg.enable imp;
+}
diff --git a/nixos/modules/services/networking/iwd.nix b/nixos/modules/services/networking/iwd.nix
index eb03d2e1d632..18ed20e28886 100644
--- a/nixos/modules/services/networking/iwd.nix
+++ b/nixos/modules/services/networking/iwd.nix
@@ -22,6 +22,8 @@ in {
 
     systemd.packages = [ pkgs.iwd ];
 
+    systemd.services.iwd.wantedBy = [ "multi-user.target" ];
+
     systemd.tmpfiles.rules = [
       "d /var/lib/iwd 0700 root root -"
     ];
diff --git a/nixos/modules/services/networking/miniupnpd.nix b/nixos/modules/services/networking/miniupnpd.nix
index 19400edb68f9..ab714a6ac75e 100644
--- a/nixos/modules/services/networking/miniupnpd.nix
+++ b/nixos/modules/services/networking/miniupnpd.nix
@@ -57,32 +57,12 @@ in
   };
 
   config = mkIf cfg.enable {
-    # from miniupnpd/netfilter/iptables_init.sh
     networking.firewall.extraCommands = ''
-      iptables -t nat -N MINIUPNPD
-      iptables -t nat -A PREROUTING -i ${cfg.externalInterface} -j MINIUPNPD
-      iptables -t mangle -N MINIUPNPD
-      iptables -t mangle -A PREROUTING -i ${cfg.externalInterface} -j MINIUPNPD
-      iptables -t filter -N MINIUPNPD
-      iptables -t filter -A FORWARD -i ${cfg.externalInterface} ! -o ${cfg.externalInterface} -j MINIUPNPD
-      iptables -t nat -N MINIUPNPD-PCP-PEER
-      iptables -t nat -A POSTROUTING -o ${cfg.externalInterface} -j MINIUPNPD-PCP-PEER
+      ${pkgs.bash}/bin/bash -x ${pkgs.miniupnpd}/etc/miniupnpd/iptables_init.sh -i ${cfg.externalInterface}
     '';
 
-    # from miniupnpd/netfilter/iptables_removeall.sh
     networking.firewall.extraStopCommands = ''
-      iptables -t nat -F MINIUPNPD
-      iptables -t nat -D PREROUTING -i ${cfg.externalInterface} -j MINIUPNPD
-      iptables -t nat -X MINIUPNPD
-      iptables -t mangle -F MINIUPNPD
-      iptables -t mangle -D PREROUTING -i ${cfg.externalInterface} -j MINIUPNPD
-      iptables -t mangle -X MINIUPNPD
-      iptables -t filter -F MINIUPNPD
-      iptables -t filter -D FORWARD -i ${cfg.externalInterface} ! -o ${cfg.externalInterface} -j MINIUPNPD
-      iptables -t filter -X MINIUPNPD
-      iptables -t nat -F MINIUPNPD-PCP-PEER
-      iptables -t nat -D POSTROUTING -o ${cfg.externalInterface} -j MINIUPNPD-PCP-PEER
-      iptables -t nat -X MINIUPNPD-PCP-PEER
+      ${pkgs.bash}/bin/bash -x ${pkgs.miniupnpd}/etc/miniupnpd/iptables_removeall.sh -i ${cfg.externalInterface}
     '';
 
     systemd.services.miniupnpd = {
diff --git a/nixos/modules/services/networking/murmur.nix b/nixos/modules/services/networking/murmur.nix
index fcc813e6898f..a6e90feff7ea 100644
--- a/nixos/modules/services/networking/murmur.nix
+++ b/nixos/modules/services/networking/murmur.nix
@@ -50,7 +50,7 @@ in
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = "If enabled, start the Murmur Service.";
+        description = "If enabled, start the Murmur Mumble server.";
       };
 
       autobanAttempts = mkOption {
diff --git a/nixos/modules/services/networking/networkmanager.nix b/nixos/modules/services/networking/networkmanager.nix
index d5af4648e8f9..2d76e0676b24 100644
--- a/nixos/modules/services/networking/networkmanager.nix
+++ b/nixos/modules/services/networking/networkmanager.nix
@@ -406,25 +406,25 @@ in {
       { source = configFile;
         target = "NetworkManager/NetworkManager.conf";
       }
-      { source = "${networkmanager-openvpn}/etc/NetworkManager/VPN/nm-openvpn-service.name";
+      { source = "${networkmanager-openvpn}/lib/NetworkManager/VPN/nm-openvpn-service.name";
         target = "NetworkManager/VPN/nm-openvpn-service.name";
       }
-      { source = "${networkmanager-vpnc}/etc/NetworkManager/VPN/nm-vpnc-service.name";
+      { source = "${networkmanager-vpnc}/lib/NetworkManager/VPN/nm-vpnc-service.name";
         target = "NetworkManager/VPN/nm-vpnc-service.name";
       }
-      { source = "${networkmanager-openconnect}/etc/NetworkManager/VPN/nm-openconnect-service.name";
+      { source = "${networkmanager-openconnect}/lib/NetworkManager/VPN/nm-openconnect-service.name";
         target = "NetworkManager/VPN/nm-openconnect-service.name";
       }
-      { source = "${networkmanager-fortisslvpn}/etc/NetworkManager/VPN/nm-fortisslvpn-service.name";
+      { source = "${networkmanager-fortisslvpn}/lib/NetworkManager/VPN/nm-fortisslvpn-service.name";
         target = "NetworkManager/VPN/nm-fortisslvpn-service.name";
       }
-      { source = "${networkmanager-l2tp}/etc/NetworkManager/VPN/nm-l2tp-service.name";
+      { source = "${networkmanager-l2tp}/lib/NetworkManager/VPN/nm-l2tp-service.name";
         target = "NetworkManager/VPN/nm-l2tp-service.name";
       }
-      { source = "${networkmanager_strongswan}/etc/NetworkManager/VPN/nm-strongswan-service.name";
+      { source = "${networkmanager_strongswan}/lib/NetworkManager/VPN/nm-strongswan-service.name";
         target = "NetworkManager/VPN/nm-strongswan-service.name";
       }
-      { source = "${networkmanager-iodine}/etc/NetworkManager/VPN/nm-iodine-service.name";
+      { source = "${networkmanager-iodine}/lib/NetworkManager/VPN/nm-iodine-service.name";
         target = "NetworkManager/VPN/nm-iodine-service.name";
       }
     ] ++ optional (cfg.appendNameservers == [] || cfg.insertNameservers == [])
diff --git a/nixos/modules/services/networking/nullidentdmod.nix b/nixos/modules/services/networking/nullidentdmod.nix
new file mode 100644
index 000000000000..786b5227dbad
--- /dev/null
+++ b/nixos/modules/services/networking/nullidentdmod.nix
@@ -0,0 +1,34 @@
+{ config, lib, pkgs, ... }: with lib; let
+  cfg = config.services.nullidentdmod;
+
+in {
+  options.services.nullidentdmod = with types; {
+    enable = mkEnableOption "Enable the nullidentdmod identd daemon";
+
+    userid = mkOption {
+      type = nullOr str;
+      description = "User ID to return. Set to null to return a random string each time.";
+      default = null;
+      example = "alice";
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.sockets.nullidentdmod = {
+      description = "Socket for identd (NullidentdMod)";
+      listenStreams = [ "113" ];
+      socketConfig.Accept = true;
+      wantedBy = [ "sockets.target" ];
+    };
+
+    systemd.services."nullidentdmod@" = {
+      description = "NullidentdMod service";
+      serviceConfig = {
+        DynamicUser = true;
+        ExecStart = "${pkgs.nullidentdmod}/bin/nullidentdmod${optionalString (cfg.userid != null) " ${cfg.userid}"}";
+        StandardInput = "socket";
+        StandardOutput = "socket";
+      };
+    };
+  };
+}
diff --git a/nixos/modules/services/networking/pptpd.nix b/nixos/modules/services/networking/pptpd.nix
index 56a612b91052..d8b9e8f8341a 100644
--- a/nixos/modules/services/networking/pptpd.nix
+++ b/nixos/modules/services/networking/pptpd.nix
@@ -5,7 +5,7 @@ with lib;
 {
   options = {
     services.pptpd = {
-      enable = mkEnableOption "Whether pptpd should be run on startup.";
+      enable = mkEnableOption "pptpd, the Point-to-Point Tunneling Protocol daemon";
 
       serverIp = mkOption {
         type        = types.string;
diff --git a/nixos/modules/services/networking/redsocks.nix b/nixos/modules/services/networking/redsocks.nix
index a47a78f1005e..8481f9debf39 100644
--- a/nixos/modules/services/networking/redsocks.nix
+++ b/nixos/modules/services/networking/redsocks.nix
@@ -267,4 +267,6 @@ in
             "ip46tables -t nat -D OUTPUT -p tcp ${redCond block} -j ${chain} 2>/dev/null || true"
         ) cfg.redsocks;
     };
+
+  meta.maintainers = with lib.maintainers; [ ekleog ];
 }
diff --git a/nixos/modules/services/networking/shairport-sync.nix b/nixos/modules/services/networking/shairport-sync.nix
index 0b87140b0d8d..36ecb74ffc95 100644
--- a/nixos/modules/services/networking/shairport-sync.nix
+++ b/nixos/modules/services/networking/shairport-sync.nix
@@ -27,7 +27,7 @@ in
       };
 
       arguments = mkOption {
-        default = "-v -o pulse";
+        default = "-v -d pulse";
         description = ''
           Arguments to pass to the daemon. Defaults to a local pulseaudio
           server.
@@ -72,6 +72,7 @@ in
         serviceConfig = {
           User = cfg.user;
           ExecStart = "${pkgs.shairport-sync}/bin/shairport-sync ${cfg.arguments}";
+          RuntimeDirectory = "shairport-sync";
         };
       };
 
diff --git a/nixos/modules/services/networking/strongswan-swanctl/swanctl-params.nix b/nixos/modules/services/networking/strongswan-swanctl/swanctl-params.nix
index b16d299917fe..d4f7e95f859f 100644
--- a/nixos/modules/services/networking/strongswan-swanctl/swanctl-params.nix
+++ b/nixos/modules/services/networking/strongswan-swanctl/swanctl-params.nix
@@ -248,6 +248,14 @@ in {
       </itemizedlist>
     '';
 
+    ppk_id = mkOptionalStrParam ''
+       String identifying the Postquantum Preshared Key (PPK) to be used.
+    '';
+
+    ppk_required = mkYesNoParam no ''
+       Whether a Postquantum Preshared Key (PPK) is required for this connection.
+    '';
+
     keyingtries = mkIntParam 1 ''
       Number of retransmission sequences to perform during initial
       connect. Instead of giving up initiation after the first retransmission
@@ -922,6 +930,36 @@ in {
         <literal>0xffffffff</literal>.
       '';
 
+      set_mark_in = mkStrParam "0/0x00000000" ''
+        Netfilter mark applied to packets after the inbound IPsec SA processed
+        them. This way it's not necessary to mark packets via Netfilter before
+        decryption or right afterwards to match policies or process them
+        differently (e.g. via policy routing).
+
+        An additional mask may be appended to the mark, separated by
+        <literal>/</literal>. The default mask if omitted is 0xffffffff. The
+        special value <literal>%same</literal> uses the value (but not the mask)
+        from <option>mark_in</option> as mark value, which can be fixed,
+        <literal>%unique</literal> or <literal>%unique-dir</literal>.
+
+        Setting marks in XFRM input requires Linux 4.19 or higher.
+      '';
+
+      set_mark_out = mkStrParam "0/0x00000000" ''
+        Netfilter mark applied to packets after the outbound IPsec SA processed
+        them. This allows processing ESP packets differently than the original
+        traffic (e.g. via policy routing).
+
+        An additional mask may be appended to the mark, separated by
+        <literal>/</literal>. The default mask if omitted is 0xffffffff. The
+        special value <literal>%same</literal> uses the value (but not the mask)
+        from <option>mark_out</option> as mark value, which can be fixed,
+        <literal>%unique_</literal> or <literal>%unique-dir</literal>.
+
+        Setting marks in XFRM output is supported since Linux 4.14. Setting a
+        mask requires at least Linux 4.19.
+      '';
+
       tfc_padding = mkParamOfType (with lib.types; either int (enum ["mtu"])) 0 ''
         Pads ESP packets with additional data to have a consistent ESP packet
         size for improved Traffic Flow Confidentiality. The padding defines the
@@ -946,6 +984,33 @@ in {
         supported, but the installation does not fail otherwise.
       '';
 
+      copy_df = mkYesNoParam yes ''
+        Whether to copy the DF bit to the outer IPv4 header in tunnel mode. This
+        effectively disables Path MTU discovery (PMTUD). Controlling this
+        behavior is not supported by all kernel interfaces.
+      '';
+
+      copy_ecn = mkYesNoParam yes ''
+        Whether to copy the ECN (Explicit Congestion Notification) header field
+        to/from the outer IP header in tunnel mode. Controlling this behavior is
+        not supported by all kernel interfaces.
+      '';
+
+      copy_dscp = mkEnumParam [ "out" "in" "yes" "no" ] "out" ''
+        Whether to copy the DSCP (Differentiated Services Field Codepoint)
+        header field to/from the outer IP header in tunnel mode. The value
+        <literal>out</literal> only copies the field from the inner to the outer
+        header, the value <literal>in</literal> does the opposite and only
+        copies the field from the outer to the inner header when decapsulating,
+        the value <literal>yes</literal> copies the field in both directions,
+        and the value <literal>no</literal> disables copying the field
+        altogether. Setting this to <literal>yes</literal> or
+        <literal>in</literal> could allow an attacker to adversely affect other
+        traffic at the receiver, which is why the default is
+        <literal>out</literal>. Controlling this behavior is not supported by
+        all kernel interfaces.
+      '';
+
       start_action = mkEnumParam ["none" "trap" "start"] "none" ''
         Action to perform after loading the configuration.
         <itemizedlist>
@@ -1060,6 +1125,24 @@ in {
       defined in a unique section having the <literal>ike</literal> prefix.
     '';
 
+    ppk = mkPrefixedAttrsOfParams {
+      secret = mkOptionalStrParam ''
+	      Value of the PPK. It may either be an ASCII string, a hex encoded string
+	      if it has a <literal>0x</literal> prefix or a Base64 encoded string if
+	      it has a <literal>0s</literal> prefix in its value. Should have at least
+	      256 bits of entropy for 128-bit security.
+      '';
+
+      id = mkPrefixedAttrsOfParam (mkOptionalStrParam "") ''
+	      PPK identity the PPK belongs to. Multiple unique identities may be
+	      specified, each having an <literal>id</literal> prefix, if a secret is
+	      shared between multiple peers.
+      '';
+    } ''
+	    Postquantum Preshared Key (PPK) section for a specific secret. Each PPK is
+	    defined in a unique section having the <literal>ppk</literal> prefix.
+    '';
+
     private = mkPrefixedAttrsOfParams {
       file = mkOptionalStrParam ''
         File name in the private folder for which this passphrase should be used.
diff --git a/nixos/modules/services/networking/syncthing.nix b/nixos/modules/services/networking/syncthing.nix
index c610b3b66606..fd31b2a67687 100644
--- a/nixos/modules/services/networking/syncthing.nix
+++ b/nixos/modules/services/networking/syncthing.nix
@@ -16,6 +16,14 @@ in {
         available on http://127.0.0.1:8384/.
       '';
 
+      guiAddress = mkOption {
+        type = types.str;
+        default = "127.0.0.1:8384";
+        description = ''
+          Address to serve the GUI.
+        '';
+      };
+
       systemService = mkOption {
         type = types.bool;
         default = true;
@@ -23,7 +31,7 @@ in {
       };
 
       user = mkOption {
-        type = types.string;
+        type = types.str;
         default = defaultUser;
         description = ''
           Syncthing will be run under this user (user will be created if it doesn't exist.
@@ -32,7 +40,7 @@ in {
       };
 
       group = mkOption {
-        type = types.string;
+        type = types.str;
         default = "nogroup";
         description = ''
           Syncthing will be run under this group (group will not be created if it doesn't exist.
@@ -41,7 +49,7 @@ in {
       };
 
       all_proxy = mkOption {
-        type = types.nullOr types.string;
+        type = with types; nullOr str;
         default = null;
         example = "socks5://address.com:1234";
         description = ''
@@ -132,7 +140,12 @@ in {
           User = cfg.user;
           Group = cfg.group;
           PermissionsStartOnly = true;
-          ExecStart = "${cfg.package}/bin/syncthing -no-browser -home=${cfg.dataDir}";
+          ExecStart = ''
+            ${cfg.package}/bin/syncthing \
+              -no-browser \
+              -gui-address=${cfg.guiAddress} \
+              -home=${cfg.dataDir}
+          '';
         };
       };
 
diff --git a/nixos/modules/services/networking/teamspeak3.nix b/nixos/modules/services/networking/teamspeak3.nix
index 3703921ff703..410d650b1f64 100644
--- a/nixos/modules/services/networking/teamspeak3.nix
+++ b/nixos/modules/services/networking/teamspeak3.nix
@@ -124,7 +124,7 @@ in
             dbsqlpath=${ts3}/lib/teamspeak/sql/ logpath=${cfg.logPath} \
             voice_ip=${cfg.voiceIP} default_voice_port=${toString cfg.defaultVoicePort} \
             filetransfer_ip=${cfg.fileTransferIP} filetransfer_port=${toString cfg.fileTransferPort} \
-            query_ip=${cfg.queryIP} query_port=${toString cfg.queryPort}
+            query_ip=${cfg.queryIP} query_port=${toString cfg.queryPort} license_accepted=1
         '';
         WorkingDirectory = cfg.dataDir;
         User = user;
diff --git a/nixos/modules/services/networking/tinc.nix b/nixos/modules/services/networking/tinc.nix
index 35cdddc590b8..3379efd1afce 100644
--- a/nixos/modules/services/networking/tinc.nix
+++ b/nixos/modules/services/networking/tinc.nix
@@ -148,14 +148,6 @@ in
         }
       ));
 
-    networking.interfaces = flip mapAttrs' cfg.networks (network: data: nameValuePair
-      ("tinc.${network}")
-      ({
-        virtual = true;
-        virtualType = "${data.interfaceType}";
-      })
-    );
-
     systemd.services = flip mapAttrs' cfg.networks (network: data: nameValuePair
       ("tinc.${network}")
       ({
diff --git a/nixos/modules/services/networking/xl2tpd.nix b/nixos/modules/services/networking/xl2tpd.nix
index 46111a76af80..d0a3ed7bb5e0 100644
--- a/nixos/modules/services/networking/xl2tpd.nix
+++ b/nixos/modules/services/networking/xl2tpd.nix
@@ -5,7 +5,7 @@ with lib;
 {
   options = {
     services.xl2tpd = {
-      enable = mkEnableOption "Whether xl2tpd should be run on startup.";
+      enable = mkEnableOption "xl2tpd, the Layer 2 Tunnelling Protocol Daemon";
 
       serverIp = mkOption {
         type        = types.string;
diff --git a/nixos/modules/services/networking/xrdp.nix b/nixos/modules/services/networking/xrdp.nix
index 61f22a366a02..9ed3025e47d4 100644
--- a/nixos/modules/services/networking/xrdp.nix
+++ b/nixos/modules/services/networking/xrdp.nix
@@ -36,7 +36,7 @@ in
 
     services.xrdp = {
 
-      enable = mkEnableOption "Whether xrdp should be run on startup.";
+      enable = mkEnableOption "xrdp, the Remote Desktop Protocol server";
 
       package = mkOption {
         type = types.package;
diff --git a/nixos/modules/services/networking/zeronet.nix b/nixos/modules/services/networking/zeronet.nix
index 2377cb2c8f11..8b60799891ca 100644
--- a/nixos/modules/services/networking/zeronet.nix
+++ b/nixos/modules/services/networking/zeronet.nix
@@ -12,6 +12,8 @@ let
       log_dir = ${cfg.logDir}
     '' + lib.optionalString (cfg.port != null) ''
       ui_port = ${toString cfg.port}
+    '' + lib.optionalString (cfg.torAlways) ''
+      tor = always
     '' + cfg.extraConfig;
   };
 in with lib; {
@@ -35,13 +37,19 @@ in with lib; {
     port = mkOption {
       type = types.nullOr types.int;
       default = null;
-      example = 15441;
-      description = "Optional zeronet port.";
+      example = 43110;
+      description = "Optional zeronet web UI port.";
     };
 
     tor = mkOption {
       type = types.bool;
       default = false;
+      description = "Use TOR for zeronet traffic where possible.";
+    };
+
+    torAlways = mkOption {
+      type = types.bool;
+      default = false;
       description = "Use TOR for all zeronet traffic.";
     };
 
@@ -60,9 +68,13 @@ in with lib; {
     services.tor = mkIf cfg.tor {
       enable = true;
       controlPort = 9051;
-      extraConfig = "CookieAuthentication 1";
+      extraConfig = ''
+        CacheDirectoryGroupReadable 1
+        CookieAuthentication 1
+        CookieAuthFileGroupReadable 1
+      '';
     };
-    
+
     systemd.services.zeronet = {
       description = "zeronet";
       after = [ "network.target" (optionalString cfg.tor "tor.service") ];
diff --git a/nixos/modules/services/networking/znc.nix b/nixos/modules/services/networking/znc.nix
deleted file mode 100644
index f817db2ad000..000000000000
--- a/nixos/modules/services/networking/znc.nix
+++ /dev/null
@@ -1,431 +0,0 @@
-{ config, lib, pkgs, ...}:
-
-with lib;
-
-let
-  cfg = config.services.znc;
-
-  defaultUser = "znc"; # Default user to own process.
-
-  # Default user and pass:
-  # un=znc
-  # pw=nixospass
-
-  defaultUserName = "znc";
-  defaultPassBlock = "
-        <Pass password>
-                Method = sha256
-                Hash = e2ce303c7ea75c571d80d8540a8699b46535be6a085be3414947d638e48d9e93
-                Salt = l5Xryew4g*!oa(ECfX2o
-        </Pass>
-  ";
-
-  modules = pkgs.buildEnv {
-    name = "znc-modules";
-    paths = cfg.modulePackages;
-  };
-
-  # Keep znc.conf in nix store, then symlink or copy into `dataDir`, depending on `mutable`.
-  mkZncConf = confOpts: ''
-    Version = 1.6.3
-    ${concatMapStrings (n: "LoadModule = ${n}\n") confOpts.modules}
-
-    <Listener l>
-            Port = ${toString confOpts.port}
-            IPv4 = true
-            IPv6 = true
-            SSL = ${boolToString confOpts.useSSL}
-            ${lib.optionalString (confOpts.uriPrefix != null) "URIPrefix = ${confOpts.uriPrefix}"}
-    </Listener>
-
-    <User ${confOpts.userName}>
-            ${confOpts.passBlock}
-            Admin = true
-            Nick = ${confOpts.nick}
-            AltNick = ${confOpts.nick}_
-            Ident = ${confOpts.nick}
-            RealName = ${confOpts.nick}
-            ${concatMapStrings (n: "LoadModule = ${n}\n") confOpts.userModules}
-
-            ${ lib.concatStringsSep "\n" (lib.mapAttrsToList (name: net: ''
-              <Network ${name}>
-                  ${concatMapStrings (m: "LoadModule = ${m}\n") net.modules}
-                  Server = ${net.server} ${lib.optionalString net.useSSL "+"}${toString net.port} ${net.password}
-                  ${concatMapStrings (c: "<Chan #${c}>\n</Chan>\n") net.channels}
-                  ${lib.optionalString net.hasBitlbeeControlChannel ''
-                    <Chan &bitlbee>
-                    </Chan>
-                  ''}
-                  ${net.extraConf}
-              </Network>
-              '') confOpts.networks) }
-    </User>
-    ${confOpts.extraZncConf}
-  '';
-
-  zncConfFile = pkgs.writeTextFile {
-    name = "znc.conf";
-    text = if cfg.zncConf != ""
-      then cfg.zncConf
-      else mkZncConf cfg.confOptions;
-  };
-
-  networkOpts = { ... }: {
-    options = {
-      server = mkOption {
-        type = types.str;
-        example = "chat.freenode.net";
-        description = ''
-          IRC server address.
-        '';
-      };
-
-      port = mkOption {
-        type = types.int;
-        default = 6697;
-        example = 6697;
-        description = ''
-          IRC server port.
-        '';
-      };
-
-      userName = mkOption {
-        default = "";
-        example = "johntron";
-        type = types.string;
-        description = ''
-          A nick identity specific to the IRC server.
-        '';
-      };
-
-      password = mkOption {
-        type = types.str;
-        default = "";
-        description = ''
-          IRC server password, such as for a Slack gateway.
-        '';
-      };
-
-      useSSL = mkOption {
-        type = types.bool;
-        default = true;
-        description = ''
-          Whether to use SSL to connect to the IRC server.
-        '';
-      };
-
-      modulePackages = mkOption {
-        type = types.listOf types.package;
-        default = [];
-        example = [ "pkgs.zncModules.push" "pkgs.zncModules.fish" ];
-        description = ''
-          External ZNC modules to build.
-        '';
-      };
-
-      modules = mkOption {
-        type = types.listOf types.str;
-        default = [ "simple_away" ];
-        example = literalExample "[ simple_away sasl ]";
-        description = ''
-          ZNC modules to load.
-        '';
-      };
-
-      channels = mkOption {
-        type = types.listOf types.str;
-        default = [];
-        example = [ "nixos" ];
-        description = ''
-          IRC channels to join.
-        '';
-      };
-
-      hasBitlbeeControlChannel = mkOption {
-        type = types.bool;
-        default = false;
-        description = ''
-          Whether to add the special Bitlbee operations channel.
-        '';
-      };
-
-      extraConf = mkOption {
-        default = "";
-        type = types.lines;
-        example = ''
-          Encoding = ^UTF-8
-          FloodBurst = 4
-          FloodRate = 1.00
-          IRCConnectEnabled = true
-          Ident = johntron
-          JoinDelay = 0
-          Nick = johntron
-        '';
-        description = ''
-          Extra config for the network.
-        '';
-      };
-    };
-  };
-
-in
-
-{
-
-  ###### Interface
-
-  options = {
-    services.znc = {
-      enable = mkOption {
-        default = false;
-        type = types.bool;
-        description = ''
-          Enable a ZNC service for a user.
-        '';
-      };
-
-      user = mkOption {
-        default = "znc";
-        example = "john";
-        type = types.string;
-        description = ''
-          The name of an existing user account to use to own the ZNC server process.
-          If not specified, a default user will be created to own the process.
-        '';
-      };
-
-      group = mkOption {
-        default = "";
-        example = "users";
-        type = types.string;
-        description = ''
-          Group to own the ZNCserver process.
-        '';
-      };
-
-      dataDir = mkOption {
-        default = "/var/lib/znc/";
-        example = "/home/john/.znc/";
-        type = types.path;
-        description = ''
-          The data directory. Used for configuration files and modules.
-        '';
-      };
-
-      openFirewall = mkOption {
-        type = types.bool;
-        default = false;
-        description = ''
-          Whether to open ports in the firewall for ZNC.
-        '';
-      };
-
-      zncConf = mkOption {
-        default = "";
-        example = "See: http://wiki.znc.in/Configuration";
-        type = types.lines;
-        description = ''
-          Config file as generated with `znc --makeconf` to use for the whole ZNC configuration.
-          If specified, `confOptions` will be ignored, and this value, as-is, will be used.
-          If left empty, a conf file with default values will be used.
-        '';
-      };
-
-      confOptions = {
-        modules = mkOption {
-          type = types.listOf types.str;
-          default = [ "webadmin" "adminlog" ];
-          example = [ "partyline" "webadmin" "adminlog" "log" ];
-          description = ''
-            A list of modules to include in the `znc.conf` file.
-          '';
-        };
-
-        userModules = mkOption {
-          type = types.listOf types.str;
-          default = [ "chansaver" "controlpanel" ];
-          example = [ "chansaver" "controlpanel" "fish" "push" ];
-          description = ''
-            A list of user modules to include in the `znc.conf` file.
-          '';
-        };
-
-        userName = mkOption {
-          default = defaultUserName;
-          example = "johntron";
-          type = types.string;
-          description = ''
-            The user name used to log in to the ZNC web admin interface.
-          '';
-        };
-
-        networks = mkOption {
-          default = { };
-          type = with types; attrsOf (submodule networkOpts);
-          description = ''
-            IRC networks to connect the user to.
-          '';
-          example = {
-            "freenode" = {
-              server = "chat.freenode.net";
-              port = 6697;
-              useSSL = true;
-              modules = [ "simple_away" ];
-            };
-          };
-        };
-
-        nick = mkOption {
-          default = "znc-user";
-          example = "john";
-          type = types.string;
-          description = ''
-            The IRC nick.
-          '';
-        };
-
-        passBlock = mkOption {
-          example = defaultPassBlock;
-          type = types.string;
-          description = ''
-            Generate with `nix-shell -p znc --command "znc --makepass"`.
-            This is the password used to log in to the ZNC web admin interface.
-          '';
-        };
-
-        port = mkOption {
-          default = 5000;
-          example = 5000;
-          type = types.int;
-          description = ''
-            Specifies the port on which to listen.
-          '';
-        };
-
-        useSSL = mkOption {
-          default = true;
-          type = types.bool;
-          description = ''
-            Indicates whether the ZNC server should use SSL when listening on the specified port. A self-signed certificate will be generated.
-          '';
-        };
-
-        uriPrefix = mkOption {
-          type = types.nullOr types.str;
-          default = null;
-          example = "/znc/";
-          description = ''
-            An optional URI prefix for the ZNC web interface. Can be
-            used to make ZNC available behind a reverse proxy.
-          '';
-        };
-
-        extraZncConf = mkOption {
-          default = "";
-          type = types.lines;
-          description = ''
-            Extra config to `znc.conf` file.
-          '';
-        };
-      };
-
-      modulePackages = mkOption {
-        type = types.listOf types.package;
-        default = [ ];
-        example = literalExample "[ pkgs.zncModules.fish pkgs.zncModules.push ]";
-        description = ''
-          A list of global znc module packages to add to znc.
-        '';
-      };
-
-      mutable = mkOption {
-        default = true;
-        type = types.bool;
-        description = ''
-          Indicates whether to allow the contents of the `dataDir` directory to be changed
-          by the user at run-time.
-          If true, modifications to the ZNC configuration after its initial creation are not
-            overwritten by a NixOS system rebuild.
-          If false, the ZNC configuration is rebuilt by every system rebuild.
-          If the user wants to manage the ZNC service using the web admin interface, this value
-            should be set to true.
-        '';
-      };
-
-      extraFlags = mkOption {
-        default = [ ];
-        example = [ "--debug" ];
-        type = types.listOf types.str;
-        description = ''
-          Extra flags to use when executing znc command.
-        '';
-      };
-    };
-  };
-
-
-  ###### Implementation
-
-  config = mkIf cfg.enable {
-
-    networking.firewall = mkIf cfg.openFirewall {
-      allowedTCPPorts = [ cfg.confOptions.port ];
-    };
-
-    systemd.services.znc = {
-      description = "ZNC Server";
-      wantedBy = [ "multi-user.target" ];
-      after = [ "network.service" ];
-      serviceConfig = {
-        User = cfg.user;
-        Group = cfg.group;
-        Restart = "always";
-        ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
-        ExecStop   = "${pkgs.coreutils}/bin/kill -INT $MAINPID";
-      };
-      preStart = ''
-        ${pkgs.coreutils}/bin/mkdir -p ${cfg.dataDir}/configs
-
-        # If mutable, regenerate conf file every time.
-        ${optionalString (!cfg.mutable) ''
-          ${pkgs.coreutils}/bin/echo "znc is set to be system-managed. Now deleting old znc.conf file to be regenerated."
-          ${pkgs.coreutils}/bin/rm -f ${cfg.dataDir}/configs/znc.conf
-        ''}
-
-        # Ensure essential files exist.
-        if [[ ! -f ${cfg.dataDir}/configs/znc.conf ]]; then
-            ${pkgs.coreutils}/bin/echo "No znc.conf file found in ${cfg.dataDir}. Creating one now."
-            ${pkgs.coreutils}/bin/cp --no-clobber ${zncConfFile} ${cfg.dataDir}/configs/znc.conf
-            ${pkgs.coreutils}/bin/chmod u+rw ${cfg.dataDir}/configs/znc.conf
-            ${pkgs.coreutils}/bin/chown ${cfg.user} ${cfg.dataDir}/configs/znc.conf
-        fi
-
-        if [[ ! -f ${cfg.dataDir}/znc.pem ]]; then
-          ${pkgs.coreutils}/bin/echo "No znc.pem file found in ${cfg.dataDir}. Creating one now."
-          ${pkgs.znc}/bin/znc --makepem --datadir ${cfg.dataDir}
-        fi
-
-        # Symlink modules
-        rm ${cfg.dataDir}/modules || true
-        ln -fs ${modules}/lib/znc ${cfg.dataDir}/modules
-      '';
-      script = "${pkgs.znc}/bin/znc --foreground --datadir ${cfg.dataDir} ${toString cfg.extraFlags}";
-    };
-
-    users.users = optional (cfg.user == defaultUser)
-      { name = defaultUser;
-        description = "ZNC server daemon owner";
-        group = defaultUser;
-        uid = config.ids.uids.znc;
-        home = cfg.dataDir;
-        createHome = true;
-      };
-
-    users.groups = optional (cfg.user == defaultUser)
-      { name = defaultUser;
-        gid = config.ids.gids.znc;
-        members = [ defaultUser ];
-      };
-
-  };
-}
diff --git a/nixos/modules/services/networking/znc/default.nix b/nixos/modules/services/networking/znc/default.nix
new file mode 100644
index 000000000000..bce5b15a19ec
--- /dev/null
+++ b/nixos/modules/services/networking/znc/default.nix
@@ -0,0 +1,306 @@
+{ config, lib, pkgs, ...}:
+
+with lib;
+
+let
+
+  cfg = config.services.znc;
+
+  defaultUser = "znc";
+
+  modules = pkgs.buildEnv {
+    name = "znc-modules";
+    paths = cfg.modulePackages;
+  };
+
+  listenerPorts = concatMap (l: optional (l ? Port) l.Port)
+    (attrValues (cfg.config.Listener or {}));
+
+  # Converts the config option to a string
+  semanticString = let
+
+      sortedAttrs = set: sort (l: r:
+        if l == "extraConfig" then false # Always put extraConfig last
+        else if isAttrs set.${l} == isAttrs set.${r} then l < r
+        else isAttrs set.${r} # Attrsets should be last, makes for a nice config
+        # This last case occurs when any side (but not both) is an attrset
+        # The order of these is correct when the attrset is on the right
+        # which we're just returning
+      ) (attrNames set);
+
+      # Specifies an attrset that encodes the value according to its type
+      encode = name: value: {
+          null = [];
+          bool = [ "${name} = ${boolToString value}" ];
+          int = [ "${name} = ${toString value}" ];
+
+          # extraConfig should be inserted verbatim
+          string = [ (if name == "extraConfig" then value else "${name} = ${value}") ];
+
+          # Values like `Foo = [ "bar" "baz" ];` should be transformed into
+          #   Foo=bar
+          #   Foo=baz
+          list = concatMap (encode name) value;
+
+          # Values like `Foo = { bar = { Baz = "baz"; Qux = "qux"; Florps = null; }; };` should be transmed into
+          #   <Foo bar>
+          #     Baz=baz
+          #     Qux=qux
+          #   </Foo>
+          set = concatMap (subname: [
+              "<${name} ${subname}>"
+            ] ++ map (line: "\t${line}") (toLines value.${subname}) ++ [
+              "</${name}>"
+            ]) (filter (v: v != null) (attrNames value));
+
+        }.${builtins.typeOf value};
+
+      # One level "above" encode, acts upon a set and uses encode on each name,value pair
+      toLines = set: concatMap (name: encode name set.${name}) (sortedAttrs set);
+
+    in
+      concatStringsSep "\n" (toLines cfg.config);
+
+  semanticTypes = with types; rec {
+    zncAtom = nullOr (either (either int bool) str);
+    zncAttr = attrsOf (nullOr zncConf);
+    zncAll = either (either zncAtom (listOf zncAtom)) zncAttr;
+    zncConf = attrsOf (zncAll // {
+      # Since this is a recursive type and the description by default contains
+      # the description of its subtypes, infinite recursion would occur without
+      # explicitly breaking this cycle
+      description = "znc values (null, atoms (str, int, bool), list of atoms, or attrsets of znc values)";
+    });
+  };
+
+in
+
+{
+
+  imports = [ ./options.nix ];
+
+  options = {
+    services.znc = {
+      enable = mkEnableOption "ZNC";
+
+      user = mkOption {
+        default = "znc";
+        example = "john";
+        type = types.str;
+        description = ''
+          The name of an existing user account to use to own the ZNC server
+          process. If not specified, a default user will be created.
+        '';
+      };
+
+      group = mkOption {
+        default = defaultUser;
+        example = "users";
+        type = types.str;
+        description = ''
+          Group to own the ZNC process.
+        '';
+      };
+
+      dataDir = mkOption {
+        default = "/var/lib/znc/";
+        example = "/home/john/.znc/";
+        type = types.path;
+        description = ''
+          The state directory for ZNC. The config and the modules will be linked
+          to from this directory as well.
+        '';
+      };
+
+      openFirewall = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to open ports in the firewall for ZNC. Does work with
+          ports for listeners specified in
+          <option>services.znc.config.Listener</option>.
+        '';
+      };
+
+      config = mkOption {
+        type = semanticTypes.zncConf;
+        default = {};
+        example = literalExample ''
+          {
+            LoadModule = [ "webadmin" "adminlog" ];
+            User.paul = {
+              Admin = true;
+              Nick = "paul";
+              AltNick = "paul1";
+              LoadModule = [ "chansaver" "controlpanel" ];
+              Network.freenode = {
+                Server = "chat.freenode.net +6697";
+                LoadModule = [ "simple_away" ];
+                Chan = {
+                  "#nixos" = { Detached = false; };
+                  "##linux" = { Disabled = true; };
+                };
+              };
+              Pass.password = {
+                Method = "sha256";
+                Hash = "e2ce303c7ea75c571d80d8540a8699b46535be6a085be3414947d638e48d9e93";
+                Salt = "l5Xryew4g*!oa(ECfX2o";
+              };
+            };
+          }
+        '';
+        description = ''
+          Configuration for ZNC, see
+          <literal>https://wiki.znc.in/Configuration</literal> for details. The
+          Nix value declared here will be translated directly to the xml-like
+          format ZNC expects. This is much more flexible than the legacy options
+          under <option>services.znc.confOptions.*</option>, but also can't do
+          any type checking.
+          </para>
+          <para>
+          You can use <command>nix-instantiate --eval --strict '&lt;nixpkgs/nixos&gt;' -A config.services.znc.config</command>
+          to view the current value. By default it contains a listener for port
+          5000 with SSL enabled.
+          </para>
+          <para>
+          Nix attributes called <literal>extraConfig</literal> will be inserted
+          verbatim into the resulting config file.
+          </para>
+          <para>
+          If <option>services.znc.useLegacyConfig</option> is turned on, the
+          option values in <option>services.znc.confOptions.*</option> will be
+          gracefully be applied to this option.
+          </para>
+          <para>
+          If you intend to update the configuration through this option, be sure
+          to enable <option>services.znc.mutable</option>, otherwise none of the
+          changes here will be applied after the initial deploy.
+        '';
+      };
+
+      configFile = mkOption {
+        type = types.path;
+        example = "~/.znc/configs/znc.conf";
+        description = ''
+          Configuration file for ZNC. It is recommended to use the
+          <option>config</option> option instead.
+          </para>
+          <para>
+          Setting this option will override any auto-generated config file
+          through the <option>confOptions</option> or <option>config</option>
+          options.
+        '';
+      };
+
+      modulePackages = mkOption {
+        type = types.listOf types.package;
+        default = [ ];
+        example = literalExample "[ pkgs.zncModules.fish pkgs.zncModules.push ]";
+        description = ''
+          A list of global znc module packages to add to znc.
+        '';
+      };
+
+      mutable = mkOption {
+        default = true; # TODO: Default to true when config is set, make sure to not delete the old config if present
+        type = types.bool;
+        description = ''
+          Indicates whether to allow the contents of the
+          <literal>dataDir</literal> directory to be changed by the user at
+          run-time.
+          </para>
+          <para>
+          If enabled, modifications to the ZNC configuration after its initial
+          creation are not overwritten by a NixOS rebuild. If disabled, the
+          ZNC configuration is rebuilt on every NixOS rebuild.
+          </para>
+          <para>
+          If the user wants to manage the ZNC service using the web admin
+          interface, this option should be enabled.
+        '';
+      };
+
+      extraFlags = mkOption {
+        default = [ ];
+        example = [ "--debug" ];
+        type = types.listOf types.str;
+        description = ''
+          Extra arguments to use for executing znc.
+        '';
+      };
+    };
+  };
+
+
+  ###### Implementation
+
+  config = mkIf cfg.enable {
+
+    services.znc = {
+      configFile = mkDefault (pkgs.writeText "znc-generated.conf" semanticString);
+      config = {
+        Version = (builtins.parseDrvName pkgs.znc.name).version;
+        Listener.l.Port = mkDefault 5000;
+        Listener.l.SSL = mkDefault true;
+      };
+    };
+
+    networking.firewall.allowedTCPPorts = mkIf cfg.openFirewall listenerPorts;
+
+    systemd.services.znc = {
+      description = "ZNC Server";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network-online.target" ];
+      serviceConfig = {
+        User = cfg.user;
+        Group = cfg.group;
+        Restart = "always";
+        ExecStart = "${pkgs.znc}/bin/znc --foreground --datadir ${cfg.dataDir} ${escapeShellArgs cfg.extraFlags}";
+        ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+        ExecStop = "${pkgs.coreutils}/bin/kill -INT $MAINPID";
+      };
+      preStart = ''
+        mkdir -p ${cfg.dataDir}/configs
+
+        # If mutable, regenerate conf file every time.
+        ${optionalString (!cfg.mutable) ''
+          echo "znc is set to be system-managed. Now deleting old znc.conf file to be regenerated."
+          rm -f ${cfg.dataDir}/configs/znc.conf
+        ''}
+
+        # Ensure essential files exist.
+        if [[ ! -f ${cfg.dataDir}/configs/znc.conf ]]; then
+            echo "No znc.conf file found in ${cfg.dataDir}. Creating one now."
+            cp --no-clobber ${cfg.configFile} ${cfg.dataDir}/configs/znc.conf
+            chmod u+rw ${cfg.dataDir}/configs/znc.conf
+            chown ${cfg.user} ${cfg.dataDir}/configs/znc.conf
+        fi
+
+        if [[ ! -f ${cfg.dataDir}/znc.pem ]]; then
+          echo "No znc.pem file found in ${cfg.dataDir}. Creating one now."
+          ${pkgs.znc}/bin/znc --makepem --datadir ${cfg.dataDir}
+        fi
+
+        # Symlink modules
+        rm ${cfg.dataDir}/modules || true
+        ln -fs ${modules}/lib/znc ${cfg.dataDir}/modules
+      '';
+    };
+
+    users.users = optional (cfg.user == defaultUser)
+      { name = defaultUser;
+        description = "ZNC server daemon owner";
+        group = defaultUser;
+        uid = config.ids.uids.znc;
+        home = cfg.dataDir;
+        createHome = true;
+      };
+
+    users.groups = optional (cfg.user == defaultUser)
+      { name = defaultUser;
+        gid = config.ids.gids.znc;
+        members = [ defaultUser ];
+      };
+
+  };
+}
diff --git a/nixos/modules/services/networking/znc/options.nix b/nixos/modules/services/networking/znc/options.nix
new file mode 100644
index 000000000000..048dbd738630
--- /dev/null
+++ b/nixos/modules/services/networking/znc/options.nix
@@ -0,0 +1,270 @@
+{ lib, config, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.znc;
+
+  networkOpts = {
+    options = {
+
+      server = mkOption {
+        type = types.str;
+        example = "chat.freenode.net";
+        description = ''
+          IRC server address.
+        '';
+      };
+
+      port = mkOption {
+        type = types.ints.u16;
+        default = 6697;
+        description = ''
+          IRC server port.
+        '';
+      };
+
+      password = mkOption {
+        type = types.str;
+        default = "";
+        description = ''
+          IRC server password, such as for a Slack gateway.
+        '';
+      };
+
+      useSSL = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Whether to use SSL to connect to the IRC server.
+        '';
+      };
+
+      modules = mkOption {
+        type = types.listOf types.str;
+        default = [ "simple_away" ];
+        example = literalExample "[ simple_away sasl ]";
+        description = ''
+          ZNC network modules to load.
+        '';
+      };
+
+      channels = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        example = [ "nixos" ];
+        description = ''
+          IRC channels to join.
+        '';
+      };
+
+      hasBitlbeeControlChannel = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to add the special Bitlbee operations channel.
+        '';
+      };
+
+      extraConf = mkOption {
+        default = "";
+        type = types.lines;
+        example = ''
+          Encoding = ^UTF-8
+          FloodBurst = 4
+          FloodRate = 1.00
+          IRCConnectEnabled = true
+          Ident = johntron
+          JoinDelay = 0
+          Nick = johntron
+        '';
+        description = ''
+          Extra config for the network. Consider using
+          <option>services.znc.config</option> instead.
+        '';
+      };
+    };
+  };
+
+in
+
+{
+
+  options = {
+    services.znc = {
+
+      useLegacyConfig = mkOption {
+        default = true;
+        type = types.bool;
+        description = ''
+          Whether to propagate the legacy options under
+          <option>services.znc.confOptions.*</option> to the znc config. If this
+          is turned on, the znc config will contain a user with the default name
+          "znc", global modules "webadmin" and "adminlog" will be enabled by
+          default, and more, all controlled through the
+          <option>services.znc.confOptions.*</option> options.
+          You can use <command>nix-instantiate --eval --strict '&lt;nixpkgs/nixos&gt;' -A config.services.znc.config</command>
+          to view the current value of the config.
+          </para>
+          <para>
+          In any case, if you need more flexibility,
+          <option>services.znc.config</option> can be used to override/add to
+          all of the legacy options.
+        '';
+      };
+
+      confOptions = {
+        modules = mkOption {
+          type = types.listOf types.str;
+          default = [ "webadmin" "adminlog" ];
+          example = [ "partyline" "webadmin" "adminlog" "log" ];
+          description = ''
+            A list of modules to include in the `znc.conf` file.
+          '';
+        };
+
+        userModules = mkOption {
+          type = types.listOf types.str;
+          default = [ "chansaver" "controlpanel" ];
+          example = [ "chansaver" "controlpanel" "fish" "push" ];
+          description = ''
+            A list of user modules to include in the `znc.conf` file.
+          '';
+        };
+
+        userName = mkOption {
+          default = "znc";
+          example = "johntron";
+          type = types.str;
+          description = ''
+            The user name used to log in to the ZNC web admin interface.
+          '';
+        };
+
+        networks = mkOption {
+          default = { };
+          type = with types; attrsOf (submodule networkOpts);
+          description = ''
+            IRC networks to connect the user to.
+          '';
+          example = literalExample ''
+            {
+              "freenode" = {
+                server = "chat.freenode.net";
+                port = 6697;
+                useSSL = true;
+                modules = [ "simple_away" ];
+              };
+            };
+          '';
+        };
+
+        nick = mkOption {
+          default = "znc-user";
+          example = "john";
+          type = types.str;
+          description = ''
+            The IRC nick.
+          '';
+        };
+
+        passBlock = mkOption {
+          example = literalExample ''
+            &lt;Pass password&gt;
+               Method = sha256
+               Hash = e2ce303c7ea75c571d80d8540a8699b46535be6a085be3414947d638e48d9e93
+               Salt = l5Xryew4g*!oa(ECfX2o
+            &lt;/Pass&gt;
+          '';
+          type = types.str;
+          description = ''
+            Generate with `nix-shell -p znc --command "znc --makepass"`.
+            This is the password used to log in to the ZNC web admin interface.
+            You can also set this through
+            <option>services.znc.config.User.&lt;username&gt;.Pass.Method</option>
+            and co.
+          '';
+        };
+
+        port = mkOption {
+          default = 5000;
+          type = types.int;
+          description = ''
+            Specifies the port on which to listen.
+          '';
+        };
+
+        useSSL = mkOption {
+          default = true;
+          type = types.bool;
+          description = ''
+            Indicates whether the ZNC server should use SSL when listening on
+            the specified port. A self-signed certificate will be generated.
+          '';
+        };
+
+        uriPrefix = mkOption {
+          type = types.nullOr types.str;
+          default = null;
+          example = "/znc/";
+          description = ''
+            An optional URI prefix for the ZNC web interface. Can be
+            used to make ZNC available behind a reverse proxy.
+          '';
+        };
+
+        extraZncConf = mkOption {
+          default = "";
+          type = types.lines;
+          description = ''
+            Extra config to `znc.conf` file.
+          '';
+        };
+      };
+
+    };
+  };
+
+  config = mkIf cfg.useLegacyConfig {
+
+    services.znc.config = let
+      c = cfg.confOptions;
+      # defaults here should override defaults set in the non-legacy part
+      mkDefault = mkOverride 900;
+    in {
+      LoadModule = mkDefault c.modules;
+      Listener.l = {
+        Port = mkDefault c.port;
+        IPv4 = mkDefault true;
+        IPv6 = mkDefault true;
+        SSL = mkDefault c.useSSL;
+        URIPrefix = c.uriPrefix;
+      };
+      User.${c.userName} = {
+        Admin = mkDefault true;
+        Nick = mkDefault c.nick;
+        AltNick = mkDefault "${c.nick}_";
+        Ident = mkDefault c.nick;
+        RealName = mkDefault c.nick;
+        LoadModule = mkDefault c.userModules;
+        Network = mapAttrs (name: net: {
+          LoadModule = mkDefault net.modules;
+          Server = mkDefault "${net.server} ${optionalString net.useSSL "+"}${toString net.port} ${net.password}";
+          Chan = optionalAttrs net.hasBitlbeeControlChannel { "&bitlbee" = mkDefault {}; } //
+            listToAttrs (map (n: nameValuePair "#${n}" (mkDefault {})) net.channels);
+          extraConfig = if net.extraConf == "" then mkDefault null else net.extraConf;
+        }) c.networks;
+        extraConfig = [ c.passBlock ];
+      };
+      extraConfig = optional (c.extraZncConf != "") c.extraZncConf;
+    };
+  };
+
+  imports = [
+    (mkRemovedOptionModule ["services" "znc" "zncConf"] ''
+      Instead of `services.znc.zncConf = "... foo ...";`, use
+      `services.znc.configFile = pkgs.writeText "znc.conf" "... foo ...";`.
+    '')
+  ];
+}
diff --git a/nixos/modules/services/printing/cupsd.nix b/nixos/modules/services/printing/cupsd.nix
index dbf18ec1d114..1031d6f3d7e2 100644
--- a/nixos/modules/services/printing/cupsd.nix
+++ b/nixos/modules/services/printing/cupsd.nix
@@ -250,7 +250,7 @@ in
       drivers = mkOption {
         type = types.listOf types.path;
         default = [];
-        example = literalExample "[ pkgs.gutenprint pkgs.hplip pkgs.splix ]";
+        example = literalExample "with pkgs; [ gutenprint hplip splix cups-googlecloudprint ]";
         description = ''
           CUPS drivers to use. Drivers provided by CUPS, cups-filters,
           Ghostscript and Samba are added unconditionally. If this list contains
diff --git a/nixos/modules/services/security/clamav.nix b/nixos/modules/services/security/clamav.nix
index 9ad0095252de..04b433f8f2bf 100644
--- a/nixos/modules/services/security/clamav.nix
+++ b/nixos/modules/services/security/clamav.nix
@@ -95,7 +95,7 @@ in
     environment.etc."clamav/freshclam.conf".source = freshclamConfigFile;
     environment.etc."clamav/clamd.conf".source = clamdConfigFile;
 
-    systemd.services.clamav-daemon = optionalAttrs cfg.daemon.enable {
+    systemd.services.clamav-daemon = mkIf cfg.daemon.enable {
       description = "ClamAV daemon (clamd)";
       after = optional cfg.updater.enable "clamav-freshclam.service";
       requires = optional cfg.updater.enable "clamav-freshclam.service";
@@ -116,7 +116,7 @@ in
       };
     };
 
-    systemd.timers.clamav-freshclam = optionalAttrs cfg.updater.enable {
+    systemd.timers.clamav-freshclam = mkIf cfg.updater.enable {
       description = "Timer for ClamAV virus database updater (freshclam)";
       wantedBy = [ "timers.target" ];
       timerConfig = {
@@ -125,7 +125,7 @@ in
       };
     };
 
-    systemd.services.clamav-freshclam = optionalAttrs cfg.updater.enable {
+    systemd.services.clamav-freshclam = mkIf cfg.updater.enable {
       description = "ClamAV virus database updater (freshclam)";
       restartTriggers = [ freshclamConfigFile ];
 
@@ -137,6 +137,7 @@ in
       serviceConfig = {
         Type = "oneshot";
         ExecStart = "${pkg}/bin/freshclam";
+        SuccessExitStatus = "1"; # if databases are up to date
         PrivateTmp = "yes";
         PrivateDevices = "yes";
       };
diff --git a/nixos/modules/services/security/munge.nix b/nixos/modules/services/security/munge.nix
index 5bca15833544..fda864f2c30a 100644
--- a/nixos/modules/services/security/munge.nix
+++ b/nixos/modules/services/security/munge.nix
@@ -53,8 +53,6 @@ in
         chmod 0700 ${cfg.password}
         mkdir -p /var/lib/munge -m 0711
         chown -R munge:munge /var/lib/munge
-        mkdir -p /var/log/munge -m 0700
-        chown -R munge:munge /var/log/munge
         mkdir -p /run/munge -m 0755
         chown -R munge:munge /run/munge
       '';
diff --git a/nixos/modules/services/security/sks.nix b/nixos/modules/services/security/sks.nix
index 62308428f326..9f0261038d5b 100644
--- a/nixos/modules/services/security/sks.nix
+++ b/nixos/modules/services/security/sks.nix
@@ -3,78 +3,112 @@
 with lib;
 
 let
-
   cfg = config.services.sks;
-
   sksPkg = cfg.package;
 
-in
-
-{
+in {
+  meta.maintainers = with maintainers; [ primeos calbrecht jcumming ];
 
   options = {
 
     services.sks = {
 
-      enable = mkEnableOption "sks";
+      enable = mkEnableOption ''
+        SKS (synchronizing key server for OpenPGP) and start the database
+        server. You need to create "''${dataDir}/dump/*.gpg" for the initial
+        import'';
 
       package = mkOption {
         default = pkgs.sks;
         defaultText = "pkgs.sks";
         type = types.package;
-        description = "
-          Which sks derivation to use.
-        ";
+        description = "Which SKS derivation to use.";
+      };
+
+      dataDir = mkOption {
+        type = types.path;
+        default = "/var/db/sks";
+        example = "/var/lib/sks";
+        # TODO: The default might change to "/var/lib/sks" as this is more
+        # common. There's also https://github.com/NixOS/nixpkgs/issues/26256
+        # and "/var/db" is not FHS compliant (seems to come from BSD).
+        description = ''
+          Data directory (-basedir) for SKS, where the database and all
+          configuration files are located (e.g. KDB, PTree, membership and
+          sksconf).
+        '';
       };
 
       hkpAddress = mkOption {
         default = [ "127.0.0.1" "::1" ];
         type = types.listOf types.str;
-        description = "
-          Wich ip addresses the sks-keyserver is listening on.
-        ";
+        description = ''
+          Domain names, IPv4 and/or IPv6 addresses to listen on for HKP
+          requests.
+        '';
       };
 
       hkpPort = mkOption {
         default = 11371;
-        type = types.int;
-        description = "
-          Which port the sks-keyserver is listening on.
-        ";
+        type = types.ints.u16;
+        description = "HKP port to listen on.";
+      };
+
+      webroot = mkOption {
+        type = types.nullOr types.path;
+        default = "${sksPkg.webSamples}/OpenPKG";
+        defaultText = "\${pkgs.sks.webSamples}/OpenPKG";
+        description = ''
+          Source directory (will be symlinked, if not null) for the files the
+          built-in webserver should serve. SKS (''${pkgs.sks.webSamples})
+          provides the following examples: "HTML5", "OpenPKG", and "XHTML+ES".
+          The index file can be named index.html, index.htm, index.xhtm, or
+          index.xhtml. Files with the extensions .css, .es, .js, .jpg, .jpeg,
+          .png, or .gif are supported. Subdirectories and filenames with
+          anything other than alphanumeric characters and the '.' character
+          will be ignored.
+        '';
       };
     };
   };
 
   config = mkIf cfg.enable {
 
-    environment.systemPackages = [ sksPkg ];
-    
-    users.users.sks = {
-      createHome = true;
-      home = "/var/db/sks";
-      isSystemUser = true;
-      shell = "${pkgs.coreutils}/bin/true";
+    users = {
+      users.sks = {
+        isSystemUser = true;
+        description = "SKS user";
+        home = cfg.dataDir;
+        createHome = true;
+        group = "sks";
+        useDefaultShell = true;
+        packages = [ sksPkg pkgs.db ];
+      };
+      groups.sks = { };
     };
 
     systemd.services = let
       hkpAddress = "'" + (builtins.concatStringsSep " " cfg.hkpAddress) + "'" ;
       hkpPort = builtins.toString cfg.hkpPort;
-      home = config.users.users.sks.home;
-      user = config.users.users.sks.name;
     in {
-      sks-keyserver = {
+      "sks-db" = {
+        description = "SKS database server";
+        after = [ "network.target" ];
         wantedBy = [ "multi-user.target" ];
         preStart = ''
-          mkdir -p ${home}/dump
-          ${pkgs.sks}/bin/sks build ${home}/dump/*.gpg -n 10 -cache 100 || true #*/
-          ${pkgs.sks}/bin/sks cleandb || true
-          ${pkgs.sks}/bin/sks pbuild -cache 20 -ptree_cache 70 || true
+          ${lib.optionalString (cfg.webroot != null)
+            "ln -sfT \"${cfg.webroot}\" web"}
+          mkdir -p dump
+          ${sksPkg}/bin/sks build dump/*.gpg -n 10 -cache 100 || true #*/
+          ${sksPkg}/bin/sks cleandb || true
+          ${sksPkg}/bin/sks pbuild -cache 20 -ptree_cache 70 || true
         '';
         serviceConfig = {
-          WorkingDirectory = home;
-          User = user;
+          WorkingDirectory = "~";
+          User = "sks";
+          Group = "sks";
           Restart = "always";
-          ExecStart = "${pkgs.sks}/bin/sks db -hkp_address ${hkpAddress} -hkp_port ${hkpPort}";
+          ExecStart = "${sksPkg}/bin/sks db -hkp_address ${hkpAddress} -hkp_port ${hkpPort}";
         };
       };
     };
diff --git a/nixos/modules/services/security/tor.nix b/nixos/modules/services/security/tor.nix
index def77ba69e58..aca2cf8cdeaa 100644
--- a/nixos/modules/services/security/tor.nix
+++ b/nixos/modules/services/security/tor.nix
@@ -57,6 +57,11 @@ let
     AutomapHostsSuffixes ${concatStringsSep "," cfg.client.dns.automapHostsSuffixes}
     ''}
   ''
+  # Explicitly disable the SOCKS server if the client is disabled.  In
+  # particular, this makes non-anonymous hidden services possible.
+  + optionalString (! cfg.client.enable) ''
+  SOCKSPort 0
+  ''
   # Relay config
   + optionalString cfg.relay.enable ''
     ORPort ${toString cfg.relay.port}
@@ -208,7 +213,7 @@ in
           enable = mkOption {
             type = types.bool;
             default = false;
-            description = "Whether to enable tor transaprent proxy";
+            description = "Whether to enable tor transparent proxy";
           };
 
           listenAddress = mkOption {
diff --git a/nixos/modules/services/system/kerberos.nix b/nixos/modules/services/system/kerberos.nix
index d151385d2f9b..e2c45ed64ac0 100644
--- a/nixos/modules/services/system/kerberos.nix
+++ b/nixos/modules/services/system/kerberos.nix
@@ -42,7 +42,7 @@ in
         protocol = "tcp";
         user = "root";
         server = "${pkgs.tcp_wrappers}/bin/tcpd";
-        serverArgs = "${pkgs.heimdalFull}/bin/kadmind";
+        serverArgs = "${pkgs.heimdalFull}/libexec/heimdal/kadmind";
       };
 
     systemd.services.kdc = {
@@ -51,13 +51,13 @@ in
       preStart = ''
         mkdir -m 0755 -p ${stateDir}
       '';
-      script = "${heimdalFull}/bin/kdc";
+      script = "${heimdalFull}/libexec/heimdal/kdc";
     };
 
     systemd.services.kpasswdd = {
       description = "Kerberos Password Changing daemon";
       wantedBy = [ "multi-user.target" ];
-      script = "${heimdalFull}/bin/kpasswdd";
+      script = "${heimdalFull}/libexec/heimdal/kpasswdd";
     };
   };
 
diff --git a/nixos/modules/services/system/saslauthd.nix b/nixos/modules/services/system/saslauthd.nix
index c8ddca9a0db6..8fcf4fb91fc4 100644
--- a/nixos/modules/services/system/saslauthd.nix
+++ b/nixos/modules/services/system/saslauthd.nix
@@ -16,7 +16,7 @@ in
 
     services.saslauthd = {
 
-      enable = mkEnableOption "Whether to enable the Cyrus SASL authentication daemon.";
+      enable = mkEnableOption "saslauthd, the Cyrus SASL authentication daemon";
 
       package = mkOption {
         default = pkgs.cyrus_sasl.bin;
diff --git a/nixos/modules/services/ttys/kmscon.nix b/nixos/modules/services/ttys/kmscon.nix
index 59c45fcb44ee..82b6a51028e3 100644
--- a/nixos/modules/services/ttys/kmscon.nix
+++ b/nixos/modules/services/ttys/kmscon.nix
@@ -4,6 +4,8 @@ let
 
   cfg = config.services.kmscon;
 
+  autologinArg = lib.optionalString (cfg.autologinUser != null) "-f ${cfg.autologinUser}";
+
   configDir = pkgs.writeTextFile { name = "kmscon-config"; destination = "/kmscon.conf"; text = cfg.extraConfig; };
 in {
   options = {
@@ -39,6 +41,15 @@ in {
         default = "";
         example = "--term xterm-256color";
       };
+
+      autologinUser = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = ''
+          Username of the account that will be automatically logged in at the console.
+          If unspecified, a login prompt is shown as usual.
+        '';
+      };
     };
   };
 
@@ -61,7 +72,7 @@ in {
 
       [Service]
       ExecStart=
-      ExecStart=${pkgs.kmscon}/bin/kmscon "--vt=%I" ${cfg.extraOptions} --seats=seat0 --no-switchvt --configdir ${configDir} --login -- ${pkgs.shadow}/bin/login -p
+      ExecStart=${pkgs.kmscon}/bin/kmscon "--vt=%I" ${cfg.extraOptions} --seats=seat0 --no-switchvt --configdir ${configDir} --login -- ${pkgs.shadow}/bin/login -p ${autologinArg}
       UtmpIdentifier=%I
       TTYPath=/dev/%I
       TTYReset=yes
diff --git a/nixos/modules/services/web-apps/codimd.nix b/nixos/modules/services/web-apps/codimd.nix
new file mode 100644
index 000000000000..a0af28eac7cc
--- /dev/null
+++ b/nixos/modules/services/web-apps/codimd.nix
@@ -0,0 +1,920 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.codimd;
+
+  prettyJSON = conf:
+    pkgs.runCommand "codimd-config.json" { } ''
+      echo '${builtins.toJSON conf}' | ${pkgs.jq}/bin/jq \
+        '{production:del(.[]|nulls)|del(.[][]?|nulls)}' > $out
+    '';
+in
+{
+  options.services.codimd = {
+    enable = mkEnableOption "the CodiMD Markdown Editor";
+
+    groups = mkOption {
+      type = types.listOf types.str;
+      default = [];
+      description = ''
+        Groups to which the codimd user should be added.
+      '';
+    };
+
+    workDir = mkOption {
+      type = types.path;
+      default = "/var/lib/codimd";
+      description = ''
+        Working directory for the CodiMD service.
+      '';
+    };
+
+    configuration = {
+      debug = mkEnableOption "debug mode";
+      domain = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        example = "codimd.org";
+        description = ''
+          Domain name for the CodiMD instance.
+        '';
+      };
+      urlPath = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        example = "/url/path/to/codimd";
+        description = ''
+          Path under which CodiMD is accessible.
+        '';
+      };
+      host = mkOption {
+        type = types.str;
+        default = "localhost";
+        description = ''
+          Address to listen on.
+        '';
+      };
+      port = mkOption {
+        type = types.int;
+        default = 3000;
+        example = "80";
+        description = ''
+          Port to listen on.
+        '';
+      };
+      path = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        example = "/var/run/codimd.sock";
+        description = ''
+          Specify where a UNIX domain socket should be placed.
+        '';
+      };
+      allowOrigin = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        example = [ "localhost" "codimd.org" ];
+        description = ''
+          List of domains to whitelist.
+        '';
+      };
+      useSSL = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Enable to use SSL server. This will also enable
+          <option>protocolUseSSL</option>.
+        '';
+      };
+      hsts = {
+        enable = mkOption {
+          type = types.bool;
+          default = true;
+          description = ''
+            Wheter to enable HSTS if HTTPS is also enabled.
+          '';
+        };
+        maxAgeSeconds = mkOption {
+          type = types.int;
+          default = 31536000;
+          description = ''
+            Max duration for clients to keep the HSTS status.
+          '';
+        };
+        includeSubdomains = mkOption {
+          type = types.bool;
+          default = true;
+          description = ''
+            Whether to include subdomains in HSTS.
+          '';
+        };
+        preload = mkOption {
+          type = types.bool;
+          default = true;
+          description = ''
+            Whether to allow preloading of the site's HSTS status.
+          '';
+        };
+      };
+      csp = mkOption {
+        type = types.nullOr types.attrs;
+        default = null;
+        example = literalExample ''
+          {
+            enable = true;
+            directives = {
+              scriptSrc = "trustworthy.scripts.example.com";
+            };
+            upgradeInsecureRequest = "auto";
+            addDefaults = true;
+          }
+        '';
+        description = ''
+          Specify the Content Security Policy which is passed to Helmet.
+          For configuration details see <link xlink:href="https://helmetjs.github.io/docs/csp/"
+          >https://helmetjs.github.io/docs/csp/</link>.
+        '';
+      };
+      protocolUseSSL = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Enable to use TLS for resource paths.
+          This only applies when <option>domain</option> is set.
+        '';
+      };
+      urlAddPort = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Enable to add the port to callback URLs.
+          This only applies when <option>domain</option> is set
+          and only for ports other than 80 and 443.
+        '';
+      };
+      useCDN = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Whether to use CDN resources or not.
+        '';
+      };
+      allowAnonymous = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Whether to allow anonymous usage.
+        '';
+      };
+      allowAnonymousEdits = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to allow guests to edit existing notes with the `freely' permission,
+          when <option>allowAnonymous</option> is enabled.
+        '';
+      };
+      allowFreeURL = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to allow note creation by accessing a nonexistent note URL.
+        '';
+      };
+      defaultPermission = mkOption {
+        type = types.enum [ "freely" "editable" "limited" "locked" "private" ];
+        default = "editable";
+        description = ''
+          Default permissions for notes.
+          This only applies for signed-in users.
+        '';
+      };
+      dbURL = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        example = ''
+          postgres://user:pass@host:5432/dbname
+        '';
+        description = ''
+          Specify which database to use.
+          CodiMD supports mysql, postgres, sqlite and mssql.
+          See <link xlink:href="https://sequelize.readthedocs.io/en/v3/">
+          https://sequelize.readthedocs.io/en/v3/</link> for more information.
+          Note: This option overrides <option>db</option>.
+        '';
+      };
+      db = mkOption {
+        type = types.attrs;
+        default = {};
+        example = literalExample ''
+          {
+            dialect = "sqlite";
+            storage = "/var/lib/codimd/db.codimd.sqlite";
+          }
+        '';
+        description = ''
+          Specify the configuration for sequelize.
+          CodiMD supports mysql, postgres, sqlite and mssql.
+          See <link xlink:href="https://sequelize.readthedocs.io/en/v3/">
+          https://sequelize.readthedocs.io/en/v3/</link> for more information.
+          Note: This option overrides <option>db</option>.
+        '';
+      };
+      sslKeyPath= mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        example = "/var/lib/codimd/codimd.key";
+        description = ''
+          Path to the SSL key. Needed when <option>useSSL</option> is enabled.
+        '';
+      };
+      sslCertPath = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        example = "/var/lib/codimd/codimd.crt";
+        description = ''
+          Path to the SSL cert. Needed when <option>useSSL</option> is enabled.
+        '';
+      };
+      sslCAPath = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        example = [ "/var/lib/codimd/ca.crt" ];
+        description = ''
+          SSL ca chain. Needed when <option>useSSL</option> is enabled.
+        '';
+      };
+      dhParamPath = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        example = "/var/lib/codimd/dhparam.pem";
+        description = ''
+          Path to the SSL dh params. Needed when <option>useSSL</option> is enabled.
+        '';
+      };
+      tmpPath = mkOption {
+        type = types.str;
+        default = "/tmp";
+        description = ''
+          Path to the temp directory CodiMD should use.
+          Note that <option>serviceConfig.PrivateTmp</option> is enabled for
+          the CodiMD systemd service by default.
+          (Non-canonical paths are relative to CodiMD's base directory)
+        '';
+      };
+      defaultNotePath = mkOption {
+        type = types.nullOr types.str;
+        default = "./public/default.md";
+        description = ''
+          Path to the default Note file.
+          (Non-canonical paths are relative to CodiMD's base directory)
+        '';
+      };
+      docsPath = mkOption {
+        type = types.nullOr types.str;
+        default = "./public/docs";
+        description = ''
+          Path to the docs directory.
+          (Non-canonical paths are relative to CodiMD's base directory)
+        '';
+      };
+      indexPath = mkOption {
+        type = types.nullOr types.str;
+        default = "./public/views/index.ejs";
+        description = ''
+          Path to the index template file.
+          (Non-canonical paths are relative to CodiMD's base directory)
+        '';
+      };
+      hackmdPath = mkOption {
+        type = types.nullOr types.str;
+        default = "./public/views/hackmd.ejs";
+        description = ''
+          Path to the hackmd template file.
+          (Non-canonical paths are relative to CodiMD's base directory)
+        '';
+      };
+      errorPath = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        defaultText = "./public/views/error.ejs";
+        description = ''
+          Path to the error template file.
+          (Non-canonical paths are relative to CodiMD's base directory)
+        '';
+      };
+      prettyPath = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        defaultText = "./public/views/pretty.ejs";
+        description = ''
+          Path to the pretty template file.
+          (Non-canonical paths are relative to CodiMD's base directory)
+        '';
+      };
+      slidePath = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        defaultText = "./public/views/slide.hbs";
+        description = ''
+          Path to the slide template file.
+          (Non-canonical paths are relative to CodiMD's base directory)
+        '';
+      };
+      uploadsPath = mkOption {
+        type = types.str;
+        default = "${cfg.workDir}/uploads";
+        defaultText = "/var/lib/codimd/uploads";
+        description = ''
+          Path under which uploaded files are saved.
+        '';
+      };
+      sessionName = mkOption {
+        type = types.str;
+        default = "connect.sid";
+        description = ''
+          Specify the name of the session cookie.
+        '';
+      };
+      sessionSecret = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = ''
+          Specify the secret used to sign the session cookie.
+          If unset, one will be generated on startup.
+        '';
+      };
+      sessionLife = mkOption {
+        type = types.int;
+        default = 1209600000;
+        description = ''
+          Session life time in milliseconds.
+        '';
+      };
+      heartbeatInterval = mkOption {
+        type = types.int;
+        default = 5000;
+        description = ''
+          Specify the socket.io heartbeat interval.
+        '';
+      };
+      heartbeatTimeout = mkOption {
+        type = types.int;
+        default = 10000;
+        description = ''
+          Specify the socket.io heartbeat timeout.
+        '';
+      };
+      documentMaxLength = mkOption {
+        type = types.int;
+        default = 100000;
+        description = ''
+          Specify the maximum document length.
+        '';
+      };
+      email = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Whether to enable email sign-in.
+        '';
+      };
+      allowEmailRegister = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Wether to enable email registration.
+        '';
+      };
+      allowGravatar = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Whether to use gravatar as profile picture source.
+        '';
+      };
+      imageUploadType = mkOption {
+        type = types.enum [ "imgur" "s3" "minio" "filesystem" ];
+        default = "filesystem";
+        description = ''
+          Specify where to upload images.
+        '';
+      };
+      minio = mkOption {
+        type = types.nullOr (types.submodule {
+          options = {
+            accessKey = mkOption {
+              type = types.str;
+              description = ''
+                Minio access key.
+              '';
+            };
+            secretKey = mkOption {
+              type = types.str;
+              description = ''
+                Minio secret key.
+              '';
+            };
+            endpoint = mkOption {
+              type = types.str;
+              description = ''
+                Minio endpoint.
+              '';
+            };
+            port = mkOption {
+              type = types.int;
+              default = 9000;
+              description = ''
+                Minio listen port.
+              '';
+            };
+            secure = mkOption {
+              type = types.bool;
+              default = true;
+              description = ''
+                Whether to use HTTPS for Minio.
+              '';
+            };
+          };
+        });
+        default = null;
+        description = "Configure the minio third-party integration.";
+      };
+      s3 = mkOption {
+        type = types.nullOr (types.submodule {
+          options = {
+            accessKeyId = mkOption {
+              type = types.str;
+              description = ''
+                AWS access key id.
+              '';
+            };
+            secretAccessKey = mkOption {
+              type = types.str;
+              description = ''
+                AWS access key.
+              '';
+            };
+            region = mkOption {
+              type = types.str;
+              description = ''
+                AWS S3 region.
+              '';
+            };
+          };
+        });
+        default = null;
+        description = "Configure the s3 third-party integration.";
+      };
+      s3bucket = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = ''
+          Specify the bucket name for upload types <literal>s3</literal> and <literal>minio</literal>.
+        '';
+      };
+      allowPDFExport = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Whether to enable PDF exports.
+        '';
+      };
+      imgur.clientId = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = ''
+          Imgur API client ID.
+        '';
+      };
+      azure = mkOption {
+        type = types.nullOr (types.submodule {
+          options = {
+            connectionString = mkOption {
+              type = types.str;
+              description = ''
+                Azure Blob Storage connection string.
+              '';
+            };
+            container = mkOption {
+              type = types.str;
+              description = ''
+                Azure Blob Storage container name.
+                It will be created if non-existent.
+              '';
+            };
+          };
+        });
+        default = null;
+        description = "Configure the azure third-party integration.";
+      };
+      oauth2 = mkOption {
+        type = types.nullOr (types.submodule {
+          options = {
+            authorizationURL = mkOption {
+              type = types.str;
+              description = ''
+                Specify the OAuth authorization URL.
+              '';
+            };
+            tokenURL = mkOption {
+              type = types.str;
+              description = ''
+                Specify the OAuth token URL.
+              '';
+            };
+            clientID = mkOption {
+              type = types.str;
+              description = ''
+                Specify the OAuth client ID.
+              '';
+            };
+            clientSecret = mkOption {
+              type = types.str;
+              description = ''
+                Specify the OAuth client secret.
+              '';
+            };
+          };
+        });
+        default = null;
+        description = "Configure the OAuth integration.";
+      };
+      facebook = mkOption {
+        type = types.nullOr (types.submodule {
+          options = {
+            clientID = mkOption {
+              type = types.str;
+              description = ''
+                Facebook API client ID.
+              '';
+            };
+            clientSecret = mkOption {
+              type = types.str;
+              description = ''
+                Facebook API client secret.
+              '';
+            };
+          };
+        });
+        default = null;
+        description = "Configure the facebook third-party integration";
+      };
+      twitter = mkOption {
+        type = types.nullOr (types.submodule {
+          options = {
+            consumerKey = mkOption {
+              type = types.str;
+              description = ''
+                Twitter API consumer key.
+              '';
+            };
+            consumerSecret = mkOption {
+              type = types.str;
+              description = ''
+                Twitter API consumer secret.
+              '';
+            };
+          };
+        });
+        default = null;
+        description = "Configure the Twitter third-party integration.";
+      };
+      github = mkOption {
+        type = types.nullOr (types.submodule {
+          options = {
+            clientID = mkOption {
+              type = types.str;
+              description = ''
+                GitHub API client ID.
+              '';
+            };
+            clientSecret = mkOption {
+              type = types.str;
+              description = ''
+                Github API client secret.
+              '';
+            };
+          };
+        });
+        default = null;
+        description = "Configure the GitHub third-party integration.";
+      };
+      gitlab = mkOption {
+        type = types.nullOr (types.submodule {
+          options = {
+            baseURL = mkOption {
+              type = types.str;
+              default = "";
+              description = ''
+                GitLab API authentication endpoint.
+                Only needed for other endpoints than gitlab.com.
+              '';
+            };
+            clientID = mkOption {
+              type = types.str;
+              description = ''
+                GitLab API client ID.
+              '';
+            };
+            clientSecret = mkOption {
+              type = types.str;
+              description = ''
+                GitLab API client secret.
+              '';
+            };
+            scope = mkOption {
+              type = types.enum [ "api" "read_user" ];
+              default = "api";
+              description = ''
+                GitLab API requested scope.
+                GitLab snippet import/export requires api scope.
+              '';
+            };
+          };
+        });
+        default = null;
+        description = "Configure the GitLab third-party integration.";
+      };
+      mattermost = mkOption {
+        type = types.nullOr (types.submodule {
+          options = {
+            baseURL = mkOption {
+              type = types.str;
+              description = ''
+                Mattermost authentication endpoint.
+              '';
+            };
+            clientID = mkOption {
+              type = types.str;
+              description = ''
+                Mattermost API client ID.
+              '';
+            };
+            clientSecret = mkOption {
+              type = types.str;
+              description = ''
+                Mattermost API client secret.
+              '';
+            };
+          };
+        });
+        default = null;
+        description = "Configure the Mattermost third-party integration.";
+      };
+      dropbox = mkOption {
+        type = types.nullOr (types.submodule {
+          options = {
+            clientID = mkOption {
+              type = types.str;
+              description = ''
+                Dropbox API client ID.
+              '';
+            };
+            clientSecret = mkOption {
+              type = types.str;
+              description = ''
+                Dropbox API client secret.
+              '';
+            };
+            appKey = mkOption {
+              type = types.str;
+              description = ''
+                Dropbox app key.
+              '';
+            };
+          };
+        });
+        default = null;
+        description = "Configure the Dropbox third-party integration.";
+      };
+      google = mkOption {
+        type = types.nullOr (types.submodule {
+          options = {
+            clientID = mkOption {
+              type = types.str;
+              description = ''
+                Google API client ID.
+              '';
+            };
+            clientSecret = mkOption {
+              type = types.str;
+              description = ''
+                Google API client secret.
+              '';
+            };
+          };
+        });
+        default = null;
+        description = "Configure the Google third-party integration.";
+      };
+      ldap = mkOption {
+        type = types.nullOr (types.submodule {
+          options = {
+            providerName = mkOption {
+              type = types.str;
+              default = "";
+              description = ''
+                Optional name to be displayed at login form, indicating the LDAP provider.
+              '';
+            };
+            url = mkOption {
+              type = types.str;
+              example = "ldap://localhost";
+              description = ''
+                URL of LDAP server.
+              '';
+            };
+            bindDn = mkOption {
+              type = types.str;
+              description = ''
+                Bind DN for LDAP access.
+              '';
+            };
+            bindCredentials = mkOption {
+              type = types.str;
+              description = ''
+                Bind credentials for LDAP access.
+              '';
+            };
+            searchBase = mkOption {
+              type = types.str;
+              example = "o=users,dc=example,dc=com";
+              description = ''
+                LDAP directory to begin search from.
+              '';
+            };
+            searchFilter = mkOption {
+              type = types.str;
+              example = "(uid={{username}})";
+              description = ''
+                LDAP filter to search with.
+              '';
+            };
+            searchAttributes = mkOption {
+              type = types.listOf types.str;
+              example = [ "displayName" "mail" ];
+              description = ''
+                LDAP attributes to search with.
+              '';
+            };
+            userNameField = mkOption {
+              type = types.str;
+              default = "";
+              description = ''
+                LDAP field which is used as the username on CodiMD.
+                By default <option>useridField</option> is used.
+              '';
+            };
+            useridField = mkOption {
+              type = types.str;
+              example = "uid";
+              description = ''
+                LDAP field which is a unique identifier for users on CodiMD.
+              '';
+            };
+            tlsca = mkOption {
+              type = types.str;
+              example = "server-cert.pem,root.pem";
+              description = ''
+                Root CA for LDAP TLS in PEM format.
+              '';
+            };
+          };
+        });
+        default = null;
+        description = "Configure the LDAP integration.";
+      };
+      saml = mkOption {
+        type = types.nullOr (types.submodule {
+          options = {
+            idpSsoUrl = mkOption {
+              type = types.str;
+              example = "https://idp.example.com/sso";
+              description = ''
+                IdP authentication endpoint.
+              '';
+            };
+            idpCert = mkOption {
+              type = types.path;
+              example = "/path/to/cert.pem";
+              description = ''
+                Path to IdP certificate file in PEM format.
+              '';
+            };
+            issuer = mkOption {
+              type = types.str;
+              default = "";
+              description = ''
+                Optional identity of the service provider.
+                This defaults to the server URL.
+              '';
+            };
+            identifierFormat = mkOption {
+              type = types.str;
+              default = "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress";
+              description = ''
+                Optional name identifier format.
+              '';
+            };
+            groupAttribute = mkOption {
+              type = types.str;
+              default = "";
+              example = "memberOf";
+              description = ''
+                Optional attribute name for group list.
+              '';
+            };
+            externalGroups = mkOption {
+              type = types.listOf types.str;
+              default = [];
+              example = [ "Temporary-staff" "External-users" ];
+              description = ''
+                Excluded group names.
+              '';
+            };
+            requiredGroups = mkOption {
+              type = types.listOf types.str;
+              default = [];
+              example = [ "Hackmd-users" "Codimd-users" ];
+              description = ''
+                Required group names.
+              '';
+            };
+            attribute = {
+              id = mkOption {
+                type = types.str;
+                default = "";
+                description = ''
+                  Attribute map for `id'.
+                  Defaults to `NameID' of SAML response.
+                '';
+              };
+              username = mkOption {
+                type = types.str;
+                default = "";
+                description = ''
+                  Attribute map for `username'.
+                  Defaults to `NameID' of SAML response.
+                '';
+              };
+              email = mkOption {
+                type = types.str;
+                default = "";
+                description = ''
+                  Attribute map for `email'.
+                  Defaults to `NameID' of SAML response if
+                  <option>identifierFormat</option> has
+                  the default value.
+                '';
+              };
+            };
+          };
+        });
+        default = null;
+        description = "Configure the SAML integration.";
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    assertions = [
+      { assertion = cfg.configuration.db == {} -> (
+          cfg.configuration.dbURL != "" && cfg.configuration.dbURL != null
+        );
+        message = "Database configuration for CodiMD missing."; }
+    ];
+    users.groups.codimd = {};
+    users.users.codimd = {
+      description = "CodiMD service user";
+      group = "codimd";
+      extraGroups = cfg.groups;
+      home = cfg.workDir;
+      createHome = true;
+    };
+
+    systemd.services.codimd = {
+      description = "CodiMD Service";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "networking.target" ];
+      preStart = ''
+        mkdir -p ${cfg.workDir}
+        chown -R codimd: ${cfg.workDir}
+      '';
+      serviceConfig = {
+        WorkingDirectory = cfg.workDir;
+        ExecStart = "${pkgs.codimd}/bin/codimd";
+        Environment = [
+          "CMD_CONFIG_FILE=${prettyJSON cfg.configuration}"
+          "NODE_ENV=production"
+        ];
+        Restart = "always";
+        User = "codimd";
+        PermissionsStartOnly = true;
+        PrivateTmp = true;
+      };
+    };
+  };
+}
diff --git a/nixos/modules/services/web-apps/matomo-doc.xml b/nixos/modules/services/web-apps/matomo-doc.xml
index 6f878015c514..510a335edc3b 100644
--- a/nixos/modules/services/web-apps/matomo-doc.xml
+++ b/nixos/modules/services/web-apps/matomo-doc.xml
@@ -3,28 +3,24 @@
          xmlns:xi="http://www.w3.org/2001/XInclude"
          version="5.0"
          xml:id="module-services-matomo">
+ <title>Matomo</title>
+ <para>
+  Matomo is a real-time web analytics application. This module configures
+  php-fpm as backend for Matomo, optionally configuring an nginx vhost as well.
+ </para>
+ <para>
+  An automatic setup is not suported by Matomo, so you need to configure Matomo
+  itself in the browser-based Matomo setup.
+ </para>
+ <section xml:id="module-services-matomo-database-setup">
+  <title>Database Setup</title>
 
-  <title>Matomo</title>
   <para>
-    Matomo is a real-time web analytics application.
-    This module configures php-fpm as backend for Matomo, optionally configuring an nginx vhost as well.
-  </para>
-
-  <para>
-    An automatic setup is not suported by Matomo, so you need to configure Matomo itself in the browser-based Matomo setup.
-  </para>
-
-
-  <section xml:id="module-services-matomo-database-setup">
-    <title>Database Setup</title>
-
-    <para>
-      You also need to configure a MariaDB or MySQL database and -user for Matomo yourself,
-      and enter those credentials in your browser.
-      You can use passwordless database authentication via the UNIX_SOCKET authentication plugin
-      with the following SQL commands:
-
-      <programlisting>
+   You also need to configure a MariaDB or MySQL database and -user for Matomo
+   yourself, and enter those credentials in your browser. You can use
+   passwordless database authentication via the UNIX_SOCKET authentication
+   plugin with the following SQL commands:
+<programlisting>
         # For MariaDB
         INSTALL PLUGIN unix_socket SONAME 'auth_socket';
         CREATE DATABASE matomo;
@@ -37,59 +33,58 @@
         CREATE USER 'matomo'@'localhost' IDENTIFIED WITH auth_socket;
         GRANT ALL PRIVILEGES ON matomo.* TO 'matomo'@'localhost';
       </programlisting>
+   Then fill in <literal>matomo</literal> as database user and database name,
+   and leave the password field blank. This authentication works by allowing
+   only the <literal>matomo</literal> unix user to authenticate as the
+   <literal>matomo</literal> database user (without needing a password), but no
+   other users. For more information on passwordless login, see
+   <link xlink:href="https://mariadb.com/kb/en/mariadb/unix_socket-authentication-plugin/" />.
+  </para>
 
-      Then fill in <literal>matomo</literal> as database user and database name, and leave the password field blank.
-      This authentication works by allowing only the <literal>matomo</literal> unix user to authenticate as the
-      <literal>matomo</literal> database user (without needing a password), but no other users.
-      For more information on passwordless login, see
-      <link xlink:href="https://mariadb.com/kb/en/mariadb/unix_socket-authentication-plugin/" />.
-    </para>
-
-    <para>
-      Of course, you can use password based authentication as well, e.g. when the database is not on the same host.
-    </para>
-  </section>
+  <para>
+   Of course, you can use password based authentication as well, e.g. when the
+   database is not on the same host.
+  </para>
+ </section>
+ <section xml:id="module-services-matomo-backups">
+  <title>Backup</title>
 
+  <para>
+   You only need to take backups of your MySQL database and the
+   <filename>/var/lib/matomo/config/config.ini.php</filename> file. Use a user
+   in the <literal>matomo</literal> group or root to access the file. For more
+   information, see
+   <link xlink:href="https://matomo.org/faq/how-to-install/faq_138/" />.
+  </para>
+ </section>
+ <section xml:id="module-services-matomo-issues">
+  <title>Issues</title>
 
-  <section xml:id="module-services-matomo-backups">
-    <title>Backup</title>
+  <itemizedlist>
+   <listitem>
     <para>
-      You only need to take backups of your MySQL database and the
-      <filename>/var/lib/matomo/config/config.ini.php</filename> file.
-      Use a user in the <literal>matomo</literal> group or root to access the file.
-      For more information, see <link xlink:href="https://matomo.org/faq/how-to-install/faq_138/" />.
+     Matomo's file integrity check will warn you. This is due to the patches
+     necessary for NixOS, you can safely ignore this.
     </para>
-  </section>
-
-
-  <section xml:id="module-services-matomo-issues">
-    <title>Issues</title>
-    <itemizedlist>
-      <listitem>
-        <para>
-          Matomo's file integrity check will warn you.
-          This is due to the patches necessary for NixOS, you can safely ignore this.
-        </para>
-      </listitem>
-
-      <listitem>
-        <para>
-          Matomo will warn you that the JavaScript tracker is not writable.
-          This is because it's located in the read-only nix store.
-          You can safely ignore this, unless you need a plugin that needs JavaScript tracker access.
-        </para>
-      </listitem>
-    </itemizedlist>
-  </section>
-
-
-  <section xml:id="module-services-matomo-other-web-servers">
-    <title>Using other Web Servers than nginx</title>
-
+   </listitem>
+   <listitem>
     <para>
-      You can use other web servers by forwarding calls for <filename>index.php</filename> and
-      <filename>piwik.php</filename> to the <literal>/run/phpfpm-matomo.sock</literal> fastcgi unix socket.
-      You can use the nginx configuration in the module code as a reference to what else should be configured.
+     Matomo will warn you that the JavaScript tracker is not writable. This is
+     because it's located in the read-only nix store. You can safely ignore
+     this, unless you need a plugin that needs JavaScript tracker access.
     </para>
-  </section>
+   </listitem>
+  </itemizedlist>
+ </section>
+ <section xml:id="module-services-matomo-other-web-servers">
+  <title>Using other Web Servers than nginx</title>
+
+  <para>
+   You can use other web servers by forwarding calls for
+   <filename>index.php</filename> and <filename>piwik.php</filename> to the
+   <literal>/run/phpfpm-matomo.sock</literal> fastcgi unix socket. You can use
+   the nginx configuration in the module code as a reference to what else
+   should be configured.
+  </para>
+ </section>
 </chapter>
diff --git a/nixos/modules/services/web-apps/matomo.nix b/nixos/modules/services/web-apps/matomo.nix
index fbbd7715c6b3..9fddf8320748 100644
--- a/nixos/modules/services/web-apps/matomo.nix
+++ b/nixos/modules/services/web-apps/matomo.nix
@@ -34,6 +34,13 @@ in {
         '';
       };
 
+      package = mkOption {
+        type = types.package;
+        description = "Matomo package to use";
+        default = pkgs.matomo;
+        defaultText = "pkgs.matomo";
+      };
+
       webServerUser = mkOption {
         type = types.nullOr types.str;
         default = null;
@@ -124,7 +131,7 @@ in {
       # the update part of the script can only work if the database is already up and running
       requires = [ databaseService ];
       after = [ databaseService ];
-      path = [ pkgs.matomo ];
+      path = [ cfg.package ];
       serviceConfig = {
         Type = "oneshot";
         User = user;
@@ -151,7 +158,7 @@ in {
             # Use User-Private Group scheme to protect matomo data, but allow administration / backup via matomo group
             # Copy config folder
             chmod g+s "${dataDir}"
-            cp -r "${pkgs.matomo}/config" "${dataDir}/"
+            cp -r "${cfg.package}/config" "${dataDir}/"
             chmod -R u+rwX,g+rwX,o-rwx "${dataDir}"
 
             # check whether user setup has already been done
@@ -164,7 +171,7 @@ in {
 
     systemd.services.${phpExecutionUnit} = {
       # stop phpfpm on package upgrade, do database upgrade via matomo_setup_update, and then restart
-      restartTriggers = [ pkgs.matomo ];
+      restartTriggers = [ cfg.package ];
       # stop config.ini.php from getting written with read permission for others
       serviceConfig.UMask = "0007";
     };
@@ -195,7 +202,7 @@ in {
       "${user}.${fqdn}" = mkMerge [ cfg.nginx {
         # don't allow to override the root easily, as it will almost certainly break matomo.
         # disadvantage: not shown as default in docs.
-        root = mkForce "${pkgs.matomo}/share";
+        root = mkForce "${cfg.package}/share";
 
         # define locations here instead of as the submodule option's default
         # so that they can easily be extended with additional locations if required
diff --git a/nixos/modules/services/web-apps/nextcloud.nix b/nixos/modules/services/web-apps/nextcloud.nix
new file mode 100644
index 000000000000..c7e97bbeba9a
--- /dev/null
+++ b/nixos/modules/services/web-apps/nextcloud.nix
@@ -0,0 +1,482 @@
+{ config, lib, pkgs, ... }@args:
+
+with lib;
+
+let
+  cfg = config.services.nextcloud;
+
+  toKeyValue = generators.toKeyValue {
+    mkKeyValue = generators.mkKeyValueDefault {} " = ";
+  };
+
+  phpOptionsExtensions = ''
+    ${optionalString cfg.caching.apcu "extension=${cfg.phpPackages.apcu}/lib/php/extensions/apcu.so"}
+    ${optionalString cfg.caching.redis "extension=${cfg.phpPackages.redis}/lib/php/extensions/redis.so"}
+    ${optionalString cfg.caching.memcached "extension=${cfg.phpPackages.memcached}/lib/php/extensions/memcached.so"}
+    zend_extension = opcache.so
+    opcache.enable = 1
+  '';
+  phpOptions = {
+    upload_max_filesize = cfg.maxUploadSize;
+    post_max_size = cfg.maxUploadSize;
+    memory_limit = cfg.maxUploadSize;
+  } // cfg.phpOptions;
+  phpOptionsStr = phpOptionsExtensions + (toKeyValue phpOptions);
+
+  occ = pkgs.writeScriptBin "nextcloud-occ" ''
+    #! ${pkgs.stdenv.shell}
+    cd ${pkgs.nextcloud}
+    exec /run/wrappers/bin/sudo -u nextcloud \
+      NEXTCLOUD_CONFIG_DIR="${cfg.home}/config" \
+      ${config.services.phpfpm.phpPackage}/bin/php \
+      -c ${pkgs.writeText "php.ini" phpOptionsStr}\
+      occ $*
+  '';
+
+in {
+  options.services.nextcloud = {
+    enable = mkEnableOption "nextcloud";
+    hostName = mkOption {
+      type = types.str;
+      description = "FQDN for the nextcloud instance.";
+    };
+    home = mkOption {
+      type = types.str;
+      default = "/var/lib/nextcloud";
+      description = "Storage path of nextcloud.";
+    };
+    https = mkOption {
+      type = types.bool;
+      default = false;
+      description = "Enable if there is a TLS terminating proxy in front of nextcloud.";
+    };
+
+    maxUploadSize = mkOption {
+      default = "512M";
+      type = types.str;
+      description = ''
+        Defines the upload limit for files. This changes the relevant options
+        in php.ini and nginx if enabled.
+      '';
+    };
+
+    skeletonDirectory = mkOption {
+      default = "";
+      type = types.str;
+      description = ''
+        The directory where the skeleton files are located. These files will be
+        copied to the data directory of new users. Leave empty to not copy any
+        skeleton files.
+      '';
+    };
+
+    nginx.enable = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Whether to enable nginx virtual host management.
+        Further nginx configuration can be done by adapting <literal>services.nginx.virtualHosts.&lt;name&gt;</literal>.
+        See <xref linkend="opt-services.nginx.virtualHosts"/> for further information.
+      '';
+    };
+
+    webfinger = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Enable this option if you plan on using the webfinger plugin.
+        The appropriate nginx rewrite rules will be added to your configuration.
+      '';
+    };
+
+    phpPackages = mkOption {
+      type = types.attrs;
+      default = pkgs.php71Packages;
+      defaultText = "pkgs.php71Packages";
+      description = ''
+        Overridable attribute of the PHP packages set to use.  If any caching
+        module is enabled, it will be taken from here.  Therefore it should
+        match the version of PHP given to
+        <literal>services.phpfpm.phpPackage</literal>.
+      '';
+    };
+
+    phpOptions = mkOption {
+      type = types.attrsOf types.str;
+      default = {
+        "short_open_tag" = "Off";
+        "expose_php" = "Off";
+        "error_reporting" = "E_ALL & ~E_DEPRECATED & ~E_STRICT";
+        "display_errors" = "stderr";
+        "opcache.enable_cli" = "1";
+        "opcache.interned_strings_buffer" = "8";
+        "opcache.max_accelerated_files" = "10000";
+        "opcache.memory_consumption" = "128";
+        "opcache.revalidate_freq" = "1";
+        "opcache.fast_shutdown" = "1";
+        "openssl.cafile" = "/etc/ssl/certs/ca-certificates.crt";
+        "catch_workers_output" = "yes";
+      };
+      description = ''
+        Options for PHP's php.ini file for nextcloud.
+      '';
+    };
+
+    poolConfig = mkOption {
+      type = types.lines;
+      default = ''
+        pm = dynamic
+        pm.max_children = 32
+        pm.start_servers = 2
+        pm.min_spare_servers = 2
+        pm.max_spare_servers = 4
+        pm.max_requests = 500
+      '';
+      description = ''
+        Options for nextcloud's PHP pool. See the documentation on <literal>php-fpm.conf</literal> for details on configuration directives.
+      '';
+    };
+
+    config = {
+      dbtype = mkOption {
+        type = types.enum [ "sqlite" "pgsql" "mysql" ];
+        default = "sqlite";
+        description = "Database type.";
+      };
+      dbname = mkOption {
+        type = types.nullOr types.str;
+        default = "nextcloud";
+        description = "Database name.";
+      };
+      dbuser = mkOption {
+        type = types.nullOr types.str;
+        default = "nextcloud";
+        description = "Database user.";
+      };
+      dbpass = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = ''
+          Database password.  Use <literal>dbpassFile</literal> to avoid this
+          being world-readable in the <literal>/nix/store</literal>.
+        '';
+      };
+      dbpassFile = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = ''
+          The full path to a file that contains the database password.
+        '';
+      };
+      dbhost = mkOption {
+        type = types.nullOr types.str;
+        default = "localhost";
+        description = "Database host.";
+      };
+      dbport = mkOption {
+        type = with types; nullOr (either int str);
+        default = null;
+        description = "Database port.";
+      };
+      dbtableprefix = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = "Table prefix in Nextcloud database.";
+      };
+      adminuser = mkOption {
+        type = types.str;
+        default = "root";
+        description = "Admin username.";
+      };
+      adminpass = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = ''
+          Admin password.  Use <literal>adminpassFile</literal> to avoid this
+          being world-readable in the <literal>/nix/store</literal>.
+        '';
+      };
+      adminpassFile = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = ''
+          The full path to a file that contains the admin's password.
+        '';
+      };
+
+      extraTrustedDomains = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        description = ''
+          Trusted domains, from which the nextcloud installation will be
+          acessible.  You don't need to add
+          <literal>services.nextcloud.hostname</literal> here.
+        '';
+      };
+    };
+
+    caching = {
+      apcu = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Whether to load the APCu module into PHP.
+        '';
+      };
+      redis = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to load the Redis module into PHP.
+          You still need to enable Redis in your config.php.
+          See https://docs.nextcloud.com/server/14/admin_manual/configuration_server/caching_configuration.html
+        '';
+      };
+      memcached = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to load the Memcached module into PHP.
+          You still need to enable Memcached in your config.php.
+          See https://docs.nextcloud.com/server/14/admin_manual/configuration_server/caching_configuration.html
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable (mkMerge [
+    { assertions = let acfg = cfg.config; in [
+        { assertion = !(acfg.dbpass != null && acfg.dbpassFile != null);
+          message = "Please specify no more than one of dbpass or dbpassFile";
+        }
+        { assertion = ((acfg.adminpass != null || acfg.adminpassFile != null)
+            && !(acfg.adminpass != null && acfg.adminpassFile != null));
+          message = "Please specify exactly one of adminpass or adminpassFile";
+        }
+      ];
+    }
+
+    { systemd.timers."nextcloud-cron" = {
+        wantedBy = [ "timers.target" ];
+        timerConfig.OnBootSec = "5m";
+        timerConfig.OnUnitActiveSec = "15m";
+        timerConfig.Unit = "nextcloud-cron.service";
+      };
+
+      systemd.services = {
+        "nextcloud-setup" = let
+          overrideConfig = pkgs.writeText "nextcloud-config.php" ''
+            <?php
+            $CONFIG = [
+              'apps_paths' => [
+                [ 'path' => '${cfg.home}/apps', 'url' => '/apps', 'writable' => false ],
+                [ 'path' => '${cfg.home}/store-apps', 'url' => '/store-apps', 'writable' => true ],
+              ],
+              'datadirectory' => '${cfg.home}/data',
+              'skeletondirectory' => '${cfg.skeletonDirectory}',
+              ${optionalString cfg.caching.apcu "'memcache.local' => '\\OC\\Memcache\\APCu',"}
+              'log_type' => 'syslog',
+            ];
+          '';
+          occInstallCmd = let
+            c = cfg.config;
+            adminpass = if c.adminpassFile != null
+              then ''"$(<"${toString c.adminpassFile}")"''
+              else ''"${toString c.adminpass}"'';
+            dbpass = if c.dbpassFile != null
+              then ''"$(<"${toString c.dbpassFile}")"''
+              else if c.dbpass != null
+              then ''"${toString c.dbpass}"''
+              else null;
+            installFlags = concatStringsSep " \\\n    "
+              (mapAttrsToList (k: v: "${k} ${toString v}") {
+              "--database" = ''"${c.dbtype}"'';
+              # The following attributes are optional depending on the type of
+              # database.  Those that evaluate to null on the left hand side
+              # will be omitted.
+              ${if c.dbname != null then "--database-name" else null} = ''"${c.dbname}"'';
+              ${if c.dbhost != null then "--database-host" else null} = ''"${c.dbhost}"'';
+              ${if c.dbport != null then "--database-port" else null} = ''"${toString c.dbport}"'';
+              ${if c.dbuser != null then "--database-user" else null} = ''"${c.dbuser}"'';
+              ${if (any (x: x != null) [c.dbpass c.dbpassFile])
+                 then "--database-pass" else null} = dbpass;
+              ${if c.dbtableprefix != null
+                then "--database-table-prefix" else null} = ''"${toString c.dbtableprefix}"'';
+              "--admin-user" = ''"${c.adminuser}"'';
+              "--admin-pass" = adminpass;
+              "--data-dir" = ''"${cfg.home}/data"'';
+            });
+          in ''
+            ${occ}/bin/nextcloud-occ maintenance:install \
+                ${installFlags}
+          '';
+          occSetTrustedDomainsCmd = concatStringsSep "\n" (imap0
+            (i: v: ''
+              ${occ}/bin/nextcloud-occ config:system:set trusted_domains \
+                ${toString i} --value="${toString v}"
+            '') ([ cfg.hostName ] ++ cfg.config.extraTrustedDomains));
+
+        in {
+          wantedBy = [ "multi-user.target" ];
+          before = [ "phpfpm-nextcloud.service" ];
+          script = ''
+            chmod og+x ${cfg.home}
+            ln -sf ${pkgs.nextcloud}/apps ${cfg.home}/
+            mkdir -p ${cfg.home}/config ${cfg.home}/data ${cfg.home}/store-apps
+            ln -sf ${overrideConfig} ${cfg.home}/config/override.config.php
+
+            chown -R nextcloud:nginx ${cfg.home}/config ${cfg.home}/data ${cfg.home}/store-apps
+
+            # Do not install if already installed
+            if [[ ! -e ${cfg.home}/config/config.php ]]; then
+              ${occInstallCmd}
+            fi
+
+            ${occ}/bin/nextcloud-occ upgrade
+
+            ${occ}/bin/nextcloud-occ config:system:delete trusted_domains
+            ${occSetTrustedDomainsCmd}
+          '';
+          serviceConfig.Type = "oneshot";
+        };
+        "nextcloud-cron" = {
+          environment.NEXTCLOUD_CONFIG_DIR = "${cfg.home}/config";
+          serviceConfig.Type = "oneshot";
+          serviceConfig.User = "nextcloud";
+          serviceConfig.ExecStart = "${pkgs.php}/bin/php -f ${pkgs.nextcloud}/cron.php";
+        };
+      };
+
+      services.phpfpm = {
+        phpOptions = phpOptionsExtensions;
+        phpPackage = pkgs.php71;
+        pools.nextcloud = let
+          phpAdminValues = (toKeyValue
+            (foldr (a: b: a // b) {}
+              (mapAttrsToList (k: v: { "php_admin_value[${k}]" = v; })
+                phpOptions)));
+        in {
+          listen = "/run/phpfpm/nextcloud";
+          extraConfig = ''
+            listen.owner = nginx
+            listen.group = nginx
+            user = nextcloud
+            group = nginx
+            ${cfg.poolConfig}
+            env[NEXTCLOUD_CONFIG_DIR] = ${cfg.home}/config
+            env[PATH] = /run/wrappers/bin:/nix/var/nix/profiles/default/bin:/run/current-system/sw/bin:/usr/bin:/bin
+            ${phpAdminValues}
+          '';
+        };
+      };
+
+      users.extraUsers.nextcloud = {
+        home = "${cfg.home}";
+        group = "nginx";
+        createHome = true;
+      };
+
+      environment.systemPackages = [ occ ];
+    }
+
+    (mkIf cfg.nginx.enable {
+      services.nginx = {
+        enable = true;
+        virtualHosts = {
+          "${cfg.hostName}" = {
+            root = pkgs.nextcloud;
+            locations = {
+              "= /robots.txt" = {
+                priority = 100;
+                extraConfig = ''
+                  allow all;
+                  log_not_found off;
+                  access_log off;
+                '';
+              };
+              "/" = {
+                priority = 200;
+                extraConfig = "rewrite ^ /index.php$uri;";
+              };
+              "~ ^/store-apps" = {
+                priority = 201;
+                extraConfig = "root ${cfg.home};";
+              };
+              "= /.well-known/carddav" = {
+                priority = 210;
+                extraConfig = "return 301 $scheme://$host/remote.php/dav;";
+              };
+              "= /.well-known/caldav" = {
+                priority = 210;
+                extraConfig = "return 301 $scheme://$host/remote.php/dav;";
+              };
+              "~ ^/(?:build|tests|config|lib|3rdparty|templates|data)/" = {
+                priority = 300;
+                extraConfig = "deny all;";
+              };
+              "~ ^/(?:\\.|autotest|occ|issue|indie|db_|console)" = {
+                priority = 300;
+                extraConfig = "deny all;";
+              };
+              "~ ^/(?:index|remote|public|cron|core/ajax/update|status|ocs/v[12]|updater/.+|ocs-provider/.+)\\.php(?:$|/)" = {
+                priority = 500;
+                extraConfig = ''
+                  include ${pkgs.nginxMainline}/conf/fastcgi.conf;
+                  fastcgi_split_path_info ^(.+\.php)(/.*)$;
+                  fastcgi_param PATH_INFO $fastcgi_path_info;
+                  fastcgi_param HTTPS ${if cfg.https then "on" else "off"};
+                  fastcgi_param modHeadersAvailable true;
+                  fastcgi_param front_controller_active true;
+                  fastcgi_pass unix:/run/phpfpm/nextcloud;
+                  fastcgi_intercept_errors on;
+                  fastcgi_request_buffering off;
+                  fastcgi_read_timeout 120s;
+                '';
+              };
+              "~ ^/(?:updater|ocs-provider)(?:$|/)".extraConfig = ''
+                try_files $uri/ =404;
+                index index.php;
+              '';
+              "~ \\.(?:css|js|woff|svg|gif)$".extraConfig = ''
+                try_files $uri /index.php$uri$is_args$args;
+                add_header Cache-Control "public, max-age=15778463";
+                add_header X-Content-Type-Options nosniff;
+                add_header X-XSS-Protection "1; mode=block";
+                add_header X-Robots-Tag none;
+                add_header X-Download-Options noopen;
+                add_header X-Permitted-Cross-Domain-Policies none;
+                access_log off;
+              '';
+              "~ \\.(?:png|html|ttf|ico|jpg|jpeg)$".extraConfig = ''
+                try_files $uri /index.php$uri$is_args$args;
+                access_log off;
+              '';
+            };
+            extraConfig = ''
+              add_header X-Content-Type-Options nosniff;
+              add_header X-XSS-Protection "1; mode=block";
+              add_header X-Robots-Tag none;
+              add_header X-Download-Options noopen;
+              add_header X-Permitted-Cross-Domain-Policies none;
+              error_page 403 /core/templates/403.php;
+              error_page 404 /core/templates/404.php;
+              client_max_body_size ${cfg.maxUploadSize};
+              fastcgi_buffers 64 4K;
+              gzip on;
+              gzip_vary on;
+              gzip_comp_level 4;
+              gzip_min_length 256;
+              gzip_proxied expired no-cache no-store private no_last_modified no_etag auth;
+              gzip_types application/atom+xml application/javascript application/json application/ld+json application/manifest+json application/rss+xml application/vnd.geo+json application/vnd.ms-fontobject application/x-font-ttf application/x-web-app-manifest+json application/xhtml+xml application/xml font/opentype image/bmp image/svg+xml image/x-icon text/cache-manifest text/css text/plain text/vcard text/vnd.rim.location.xloc text/vtt text/x-component text/x-cross-domain-policy;
+
+              ${optionalString cfg.webfinger ''
+                rewrite ^/.well-known/host-meta /public.php?service=host-meta last;
+                rewrite ^/.well-known/host-meta.json /public.php?service=host-meta-json last;
+              ''}
+            '';
+          };
+        };
+      };
+    })
+  ]);
+}
diff --git a/nixos/modules/services/web-apps/tt-rss.nix b/nixos/modules/services/web-apps/tt-rss.nix
index 2b171aa1b2b2..90b35d19ea11 100644
--- a/nixos/modules/services/web-apps/tt-rss.nix
+++ b/nixos/modules/services/web-apps/tt-rss.nix
@@ -624,7 +624,11 @@ let
     };
 
     users = optionalAttrs (cfg.user == "tt_rss") {
-      users.tt_rss.group = "tt_rss";
+      users.tt_rss = {
+        description = "tt-rss service user";
+        isSystemUser = true;
+        group = "tt_rss";
+      };
       groups.tt_rss = {};
     };
   };
diff --git a/nixos/modules/services/web-servers/nginx/default.nix b/nixos/modules/services/web-servers/nginx/default.nix
index b231ee5a3f01..6c733f093ba8 100644
--- a/nixos/modules/services/web-servers/nginx/default.nix
+++ b/nixos/modules/services/web-servers/nginx/default.nix
@@ -46,7 +46,7 @@ let
 
   configFile = pkgs.writeText "nginx.conf" ''
     user ${cfg.user} ${cfg.group};
-    error_log stderr;
+    error_log ${cfg.logError};
     daemon off;
 
     ${cfg.config}
@@ -245,8 +245,8 @@ let
         }
       ''
   ) virtualHosts);
-  mkLocations = locations: concatStringsSep "\n" (mapAttrsToList (location: config: ''
-    location ${location} {
+  mkLocations = locations: concatStringsSep "\n" (map (config: ''
+    location ${config.location} {
       ${optionalString (config.proxyPass != null && !cfg.proxyResolveWhileRunning)
         "proxy_pass ${config.proxyPass};"
       }
@@ -266,7 +266,18 @@ let
       ${config.extraConfig}
       ${optionalString (config.proxyPass != null && cfg.recommendedProxySettings) "include ${recommendedProxyConfig};"}
     }
-  '') locations);
+  '') (sortProperties (mapAttrsToList (k: v: v // { location = k; }) locations)));
+  mkBasicAuth = vhostName: authDef: let
+    htpasswdFile = pkgs.writeText "${vhostName}.htpasswd" (
+      concatStringsSep "\n" (mapAttrsToList (user: password: ''
+        ${user}:{PLAIN}${password}
+      '') authDef)
+    );
+  in ''
+    auth_basic secured;
+    auth_basic_user_file ${htpasswdFile};
+  '';
+
   mkHtpasswd = vhostName: authDef: pkgs.writeText "${vhostName}.htpasswd" (
     concatStringsSep "\n" (mapAttrsToList (user: password: ''
       ${user}:{PLAIN}${password}
@@ -330,6 +341,35 @@ in
         ";
       };
 
+      logError = mkOption {
+        default = "stderr";
+        description = "
+          Configures logging.
+          The first parameter defines a file that will store the log. The
+          special value stderr selects the standard error file. Logging to
+          syslog can be configured by specifying the “syslog:” prefix.
+          The second parameter determines the level of logging, and can be
+          one of the following: debug, info, notice, warn, error, crit,
+          alert, or emerg. Log levels above are listed in the order of
+          increasing severity. Setting a certain log level will cause all
+          messages of the specified and more severe log levels to be logged.
+          If this parameter is omitted then error is used.
+        ";
+      };
+
+      preStart =  mkOption {
+        type = types.lines;
+        default = ''
+          test -d ${cfg.stateDir}/logs || mkdir -m 750 -p ${cfg.stateDir}/logs  
+          test `stat -c %a ${cfg.stateDir}` = "750" || chmod 750 ${cfg.stateDir}
+          test `stat -c %a ${cfg.stateDir}/logs` = "750" || chmod 750 ${cfg.stateDir}/logs
+          chown -R ${cfg.user}:${cfg.group} ${cfg.stateDir}
+        '';
+        description = "
+          Shell commands executed before the service's nginx is started.
+        ";
+      };
+
       config = mkOption {
         default = "";
         description = "
@@ -597,9 +637,7 @@ in
       stopIfChanged = false;
       preStart =
         ''
-        mkdir -p ${cfg.stateDir}/logs
-        chmod 700 ${cfg.stateDir}
-        chown -R ${cfg.user}:${cfg.group} ${cfg.stateDir}
+        ${cfg.preStart}
         ${cfg.package}/bin/nginx -c ${configFile} -p ${cfg.stateDir} -t
         '';
       serviceConfig = {
diff --git a/nixos/modules/services/web-servers/nginx/location-options.nix b/nixos/modules/services/web-servers/nginx/location-options.nix
index 4c772734a749..9b44433d3845 100644
--- a/nixos/modules/services/web-servers/nginx/location-options.nix
+++ b/nixos/modules/services/web-servers/nginx/location-options.nix
@@ -71,6 +71,16 @@ with lib;
         These lines go to the end of the location verbatim.
       '';
     };
+
+    priority = mkOption {
+      type = types.int;
+      default = 1000;
+      description = ''
+        Order of this location block in relation to the others in the vhost.
+        The semantics are the same as with `lib.mkOrder`. Smaller values have
+        a greater priority.
+      '';
+    };
   };
 }
 
diff --git a/nixos/modules/services/web-servers/tomcat.nix b/nixos/modules/services/web-servers/tomcat.nix
index d92ba72a8336..be54e9255c78 100644
--- a/nixos/modules/services/web-servers/tomcat.nix
+++ b/nixos/modules/services/web-servers/tomcat.nix
@@ -121,6 +121,11 @@ in
               type = types.str;
               description = "name of the virtualhost";
             };
+            aliases = mkOption {
+              type = types.listOf types.str;
+              description = "aliases of the virtualhost";
+              default = [];
+            };
             webapps = mkOption {
               type = types.listOf types.path;
               description = ''
@@ -220,10 +225,28 @@ in
 
         ${if cfg.serverXml != "" then ''
           cp -f ${pkgs.writeTextDir "server.xml" cfg.serverXml}/* ${cfg.baseDir}/conf/
-          '' else ''
-          # Create a modified server.xml which also includes all virtual hosts
-          sed -e "/<Engine name=\"Catalina\" defaultHost=\"localhost\">/a\  ${toString (map (virtualHost: ''<Host name=\"${virtualHost.name}\" appBase=\"virtualhosts/${virtualHost.name}/webapps\" unpackWARs=\"true\" autoDeploy=\"true\" xmlValidation=\"false\" xmlNamespaceAware=\"false\" >${if cfg.logPerVirtualHost then ''<Valve className=\"org.apache.catalina.valves.AccessLogValve\" directory=\"logs/${virtualHost.name}\"  prefix=\"${virtualHost.name}_access_log.\" pattern=\"combined\" resolveHosts=\"false\"/>'' else ""}</Host>'') cfg.virtualHosts)}" \
-                ${tomcat}/conf/server.xml > ${cfg.baseDir}/conf/server.xml
+        '' else
+          let
+            hostElementForVirtualHost = virtualHost: ''
+              <Host name="${virtualHost.name}" appBase="virtualhosts/${virtualHost.name}/webapps"
+                    unpackWARs="true" autoDeploy="true" xmlValidation="false" xmlNamespaceAware="false">
+            '' + concatStrings (innerElementsForVirtualHost virtualHost) + ''
+              </Host>
+            '';
+            innerElementsForVirtualHost = virtualHost:
+              (map (alias: ''
+                <Alias>${alias}</Alias>
+              '') virtualHost.aliases)
+              ++ (optional cfg.logPerVirtualHost ''
+                <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs/${virtualHost.name}"
+                       prefix="${virtualHost.name}_access_log." pattern="combined" resolveHosts="false"/>
+              '');
+            hostElementsString = concatMapStringsSep "\n" hostElementForVirtualHost cfg.virtualHosts;
+            hostElementsSedString = replaceStrings ["\n"] ["\\\n"] hostElementsString;
+          in ''
+            # Create a modified server.xml which also includes all virtual hosts
+            sed -e "/<Engine name=\"Catalina\" defaultHost=\"localhost\">/a\\"${escapeShellArg hostElementsSedString} \
+                  ${tomcat}/conf/server.xml > ${cfg.baseDir}/conf/server.xml
           ''
         }
         ${optionalString (cfg.logDirs != []) ''
diff --git a/nixos/modules/services/x11/compton.nix b/nixos/modules/services/x11/compton.nix
index 8641c05de52e..cafd8d88ec4d 100644
--- a/nixos/modules/services/x11/compton.nix
+++ b/nixos/modules/services/x11/compton.nix
@@ -238,6 +238,12 @@ in {
       description = "Compton composite manager";
       wantedBy = [ "graphical-session.target" ];
       partOf = [ "graphical-session.target" ];
+
+      # Temporarily fixes corrupt colours with Mesa 18
+      environment = mkIf (cfg.backend == "glx") {
+        allow_rgb10_configs = "false";
+      };
+
       serviceConfig = {
         ExecStart = "${cfg.package}/bin/compton --config ${configFile}";
         RestartSec = 3;
diff --git a/nixos/modules/services/x11/desktop-managers/enlightenment.nix b/nixos/modules/services/x11/desktop-managers/enlightenment.nix
index 6fa3ec3b9255..04e380b61530 100644
--- a/nixos/modules/services/x11/desktop-managers/enlightenment.nix
+++ b/nixos/modules/services/x11/desktop-managers/enlightenment.nix
@@ -66,7 +66,7 @@ in
       '';
     }];
 
-    security.wrappers = (import (builtins.toPath "${e.enlightenment}/e-wrappers.nix")).security.wrappers;
+    security.wrappers = (import "${e.enlightenment}/e-wrappers.nix").security.wrappers;
 
     environment.etc = singleton
       { source = xcfg.xkbDir;
diff --git a/nixos/modules/services/x11/desktop-managers/gnome3.nix b/nixos/modules/services/x11/desktop-managers/gnome3.nix
index faf5214130db..0d5b860d4617 100644
--- a/nixos/modules/services/x11/desktop-managers/gnome3.nix
+++ b/nixos/modules/services/x11/desktop-managers/gnome3.nix
@@ -110,6 +110,7 @@ in {
     services.gnome3.gnome-terminal-server.enable = mkDefault true;
     services.gnome3.gnome-user-share.enable = mkDefault true;
     services.gnome3.gvfs.enable = true;
+    services.gnome3.rygel.enable = mkDefault true;
     services.gnome3.seahorse.enable = mkDefault true;
     services.gnome3.sushi.enable = mkDefault true;
     services.gnome3.tracker.enable = mkDefault true;
@@ -132,7 +133,6 @@ in {
 
     fonts.fonts = [ pkgs.dejavu_fonts pkgs.cantarell-fonts ];
 
-    services.xserver.displayManager.gdm.enable = mkDefault true;
     services.xserver.displayManager.extraSessionFilePackages = [ pkgs.gnome3.gnome-session ];
 
     services.xserver.displayManager.sessionCommands = ''
diff --git a/nixos/modules/services/x11/desktop-managers/plasma5.nix b/nixos/modules/services/x11/desktop-managers/plasma5.nix
index d1cb962f6ff8..704cc78c1528 100644
--- a/nixos/modules/services/x11/desktop-managers/plasma5.nix
+++ b/nixos/modules/services/x11/desktop-managers/plasma5.nix
@@ -64,7 +64,7 @@ in
       };
 
       security.wrappers = {
-        kcheckpass.source = "${lib.getBin plasma5.plasma-workspace}/lib/libexec/kcheckpass";
+        kcheckpass.source = "${lib.getBin plasma5.kscreenlocker}/lib/libexec/kcheckpass";
         "start_kdeinit".source = "${lib.getBin pkgs.kinit}/lib/libexec/kf5/start_kdeinit";
         kwin_wayland = {
           source = "${lib.getBin plasma5.kwin}/bin/kwin_wayland";
@@ -81,6 +81,7 @@ in
           kconfig
           kconfigwidgets
           kcoreaddons
+          kdoctools
           kdbusaddons
           kdeclarative
           kded
@@ -184,10 +185,8 @@ in
         target = "X11/xkb";
       };
 
-      environment.variables = {
-        # Enable GTK applications to load SVG icons
-        GDK_PIXBUF_MODULE_FILE = "${pkgs.librsvg.out}/lib/gdk-pixbuf-2.0/2.10.0/loaders.cache";
-      };
+      # Enable GTK applications to load SVG icons
+      services.xserver.gdk-pixbuf.modulePackages = [ pkgs.librsvg ];
 
       fonts.fonts = with pkgs; [ noto-fonts hack-font ];
       fonts.fontconfig.defaultFonts = {
@@ -224,11 +223,8 @@ in
       security.pam.services.sddm.enableKwallet = true;
       security.pam.services.slim.enableKwallet = true;
 
-      # Update the start menu for each user that has `isNormalUser` set.
-      system.activationScripts.plasmaSetup = stringAfter [ "users" "groups" ]
-        (concatStringsSep "\n"
-          (mapAttrsToList (name: value: "${pkgs.su}/bin/su ${name} -c ${pkgs.libsForQt5.kservice}/bin/kbuildsycoca5")
-            (filterAttrs (n: v: v.isNormalUser) config.users.users)));
+      # Update the start menu for each user that is currently logged in
+      system.userActivationScripts.plasmaSetup = "${pkgs.libsForQt5.kservice}/bin/kbuildsycoca5";
     })
   ];
 
diff --git a/nixos/modules/services/x11/desktop-managers/xfce.nix b/nixos/modules/services/x11/desktop-managers/xfce.nix
index 75b9a76e1924..dabf09418da7 100644
--- a/nixos/modules/services/x11/desktop-managers/xfce.nix
+++ b/nixos/modules/services/x11/desktop-managers/xfce.nix
@@ -101,10 +101,11 @@ in
     ];
 
     environment.variables = {
-      GDK_PIXBUF_MODULE_FILE = "${pkgs.librsvg.out}/lib/gdk-pixbuf-2.0/2.10.0/loaders.cache";
       GIO_EXTRA_MODULES = [ "${pkgs.xfce.gvfs}/lib/gio/modules" ];
     };
 
+    services.xserver.gdk-pixbuf.modulePackages = [ pkgs.librsvg ];
+
     services.xserver.desktopManager.session = [{
       name = "xfce";
       bgSupport = true;
diff --git a/nixos/modules/services/x11/display-managers/default.nix b/nixos/modules/services/x11/display-managers/default.nix
index f561c5f8b7a9..26b79730dd38 100644
--- a/nixos/modules/services/x11/display-managers/default.nix
+++ b/nixos/modules/services/x11/display-managers/default.nix
@@ -222,6 +222,17 @@ in
         description = "List of arguments for the X server.";
       };
 
+      setupCommands = mkOption {
+        type = types.lines;
+        default = "";
+        description = ''
+          Shell commands executed just after the X server has started.
+
+          This option is only effective for display managers for which this feature
+          is supported; currently these are LightDM, GDM and SDDM.
+        '';
+      };
+
       sessionCommands = mkOption {
         type = types.lines;
         default = "";
@@ -266,7 +277,7 @@ in
           session.  Each session script can set the
           <varname>waitPID</varname> shell variable to make this script
           wait until the end of the user session.  Each script is used
-          to define either a windows manager or a desktop manager.  These
+          to define either a window manager or a desktop manager.  These
           can be differentiated by setting the attribute
           <varname>manage</varname> either to <literal>"window"</literal>
           or <literal>"desktop"</literal>.
diff --git a/nixos/modules/services/x11/display-managers/gdm.nix b/nixos/modules/services/x11/display-managers/gdm.nix
index a775dd0f0e04..6cc30b218f4a 100644
--- a/nixos/modules/services/x11/display-managers/gdm.nix
+++ b/nixos/modules/services/x11/display-managers/gdm.nix
@@ -7,6 +7,13 @@ let
   cfg = config.services.xserver.displayManager;
   gdm = pkgs.gnome3.gdm;
 
+  xSessionWrapper = if (cfg.setupCommands == "") then null else
+    pkgs.writeScript "gdm-x-session-wrapper" ''
+      #!${pkgs.bash}/bin/bash
+      ${cfg.setupCommands}
+      exec "$@"
+    '';
+
 in
 
 {
@@ -112,6 +119,11 @@ in
           GDM_SESSIONS_DIR = "${cfg.session.desktops}/share/xsessions";
           # Find the mouse
           XCURSOR_PATH = "~/.icons:${pkgs.gnome3.adwaita-icon-theme}/share/icons";
+        } // optionalAttrs (xSessionWrapper != null) {
+          # Make GDM use this wrapper before running the session, which runs the
+          # configured setupCommands. This relies on a patched GDM which supports
+          # this environment variable.
+          GDM_X_SESSION_WRAPPER = "${xSessionWrapper}";
         };
         execCmd = "exec ${gdm}/bin/gdm";
       };
@@ -142,7 +154,10 @@ in
 
     systemd.user.services.dbus.wantedBy = [ "default.target" ];
 
-    programs.dconf.profiles.gdm = "${gdm}/share/dconf/profile/gdm";
+    programs.dconf.profiles.gdm = pkgs.writeText "dconf-gdm-profile" ''
+      system-db:local
+      ${gdm}/share/dconf/profile/gdm
+    '';
 
     # Use AutomaticLogin if delay is zero, because it's immediate.
     # Otherwise with TimedLogin with zero seconds the prompt is still
diff --git a/nixos/modules/services/x11/display-managers/lightdm-greeters/enso-os.nix b/nixos/modules/services/x11/display-managers/lightdm-greeters/enso-os.nix
new file mode 100644
index 000000000000..7c794b1ba175
--- /dev/null
+++ b/nixos/modules/services/x11/display-managers/lightdm-greeters/enso-os.nix
@@ -0,0 +1,159 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+let
+  dmcfg = config.services.xserver.displayManager;
+  ldmcfg = dmcfg.lightdm;
+  cfg = ldmcfg.greeters.enso;
+
+  theme = cfg.theme.package;
+  icons = cfg.iconTheme.package;
+  cursors = cfg.cursorTheme.package;
+
+  # We need a few things in the environment for the greeter to run with
+  # fonts/icons.
+  wrappedEnsoGreeter = pkgs.runCommand "lightdm-enso-os-greeter"
+    { buildInputs = [ pkgs.makeWrapper ]; }
+    ''
+      # This wrapper ensures that we actually get themes
+      makeWrapper ${pkgs.lightdm-enso-os-greeter}/bin/pantheon-greeter \
+        $out/greeter \
+        --prefix PATH : "${pkgs.glibc.bin}/bin" \
+        --set GDK_PIXBUF_MODULE_FILE "${pkgs.librsvg.out}/lib/gdk-pixbuf-2.0/2.10.0/loaders.cache" \
+        --set GTK_PATH "${theme}:${pkgs.gtk3.out}" \
+        --set GTK_EXE_PREFIX "${theme}" \
+        --set GTK_DATA_PREFIX "${theme}" \
+        --set XDG_DATA_DIRS "${theme}/share:${icons}/share:${cursors}/share" \
+        --set XDG_CONFIG_HOME "${theme}/share"
+
+      cat - > $out/lightdm-enso-os-greeter.desktop << EOF
+      [Desktop Entry]
+      Name=LightDM Greeter
+      Comment=This runs the LightDM Greeter
+      Exec=$out/greeter
+      Type=Application
+      EOF
+    '';
+
+  ensoGreeterConf = pkgs.writeText "lightdm-enso-os-greeter.conf" ''
+    [greeter]
+    default-wallpaper=${ldmcfg.background}
+    gtk-theme=${cfg.theme.name}
+    icon-theme=${cfg.iconTheme.name}
+    cursor-theme=${cfg.cursorTheme.name}
+    blur=${toString cfg.blur}
+    brightness=${toString cfg.brightness}
+    ${cfg.extraConfig}
+  '';
+in {
+  options = {
+    services.xserver.displayManager.lightdm.greeters.enso = {
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to enable enso-os-greeter as the lightdm greeter
+        '';
+      };
+
+      theme = {
+        package = mkOption {
+          type = types.package;
+          default = pkgs.gnome3.gnome-themes-extra;
+          defaultText = "pkgs.gnome3.gnome-themes-extra";
+          description = ''
+            The package path that contains the theme given in the name option.
+          '';
+        };
+
+        name = mkOption {
+          type = types.str;
+          default = "Adwaita";
+          description = ''
+            Name of the theme to use for the lightdm-enso-os-greeter
+          '';
+        };
+      };
+
+      iconTheme = {
+        package = mkOption {
+          type = types.package;
+          default = pkgs.papirus-icon-theme;
+          defaultText = "pkgs.papirus-icon-theme";
+          description = ''
+            The package path that contains the icon theme given in the name option.
+          '';
+        };
+
+        name = mkOption {
+          type = types.str;
+          default = "ePapirus";
+          description = ''
+            Name of the icon theme to use for the lightdm-enso-os-greeter
+          '';
+        };
+      };
+
+      cursorTheme = {
+        package = mkOption {
+          type = types.package;
+          default = pkgs.capitaine-cursors;
+          defaultText = "pkgs.capitaine-cursors";
+          description = ''
+            The package path that contains the cursor theme given in the name option.
+          '';
+        };
+
+        name = mkOption {
+          type = types.str;
+          default = "capitane-cursors";
+          description = ''
+            Name of the cursor theme to use for the lightdm-enso-os-greeter
+          '';
+        };
+      };
+
+      blur = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether or not to enable blur
+        '';
+      };
+
+      brightness = mkOption {
+        type = types.int;
+        default = 7;
+        description = ''
+          Brightness
+        '';
+      };
+
+      extraConfig = mkOption {
+        type = types.lines;
+        default = "";
+        description = ''
+          Extra configuration that should be put in the greeter.conf
+          configuration file
+        '';
+      };
+    };
+  };
+
+  config = mkIf (ldmcfg.enable && cfg.enable) {
+    environment.etc."lightdm/greeter.conf".source = ensoGreeterConf;
+
+    services.xserver.displayManager.lightdm = {
+      greeter = mkDefault {
+        package = wrappedEnsoGreeter;
+        name = "lightdm-enso-os-greeter";
+      };
+
+      greeters = {
+        gtk = {
+          enable = mkDefault false;
+        };
+      };
+    };
+  };
+}
diff --git a/nixos/modules/services/x11/display-managers/lightdm-greeters/gtk.nix b/nixos/modules/services/x11/display-managers/lightdm-greeters/gtk.nix
index 013956c05466..d1ee076e9185 100644
--- a/nixos/modules/services/x11/display-managers/lightdm-greeters/gtk.nix
+++ b/nixos/modules/services/x11/display-managers/lightdm-greeters/gtk.nix
@@ -22,7 +22,7 @@ let
       # This wrapper ensures that we actually get themes
       makeWrapper ${pkgs.lightdm_gtk_greeter}/sbin/lightdm-gtk-greeter \
         $out/greeter \
-        --prefix PATH : "${pkgs.glibc.bin}/bin" \
+        --prefix PATH : "${lib.getBin pkgs.stdenv.cc.libc}/bin" \
         --set GDK_PIXBUF_MODULE_FILE "${pkgs.librsvg.out}/lib/gdk-pixbuf-2.0/2.10.0/loaders.cache" \
         --set GTK_PATH "${theme}:${pkgs.gtk3.out}" \
         --set GTK_EXE_PREFIX "${theme}" \
diff --git a/nixos/modules/services/x11/display-managers/lightdm.nix b/nixos/modules/services/x11/display-managers/lightdm.nix
index cd9c3d81a0fb..a685dbfff2a0 100644
--- a/nixos/modules/services/x11/display-managers/lightdm.nix
+++ b/nixos/modules/services/x11/display-managers/lightdm.nix
@@ -46,6 +46,7 @@ let
         greeters-directory = ${cfg.greeter.package}
       ''}
       sessions-directory = ${dmcfg.session.desktops}/share/xsessions
+      ${cfg.extraConfig}
 
       [Seat:*]
       xserver-command = ${xserverWrapper}
@@ -61,6 +62,12 @@ let
       ${optionalString hasDefaultUserSession ''
         user-session=${defaultSessionName}
       ''}
+      ${optionalString (dmcfg.setupCommands != "") ''
+        display-setup-script=${pkgs.writeScript "lightdm-display-setup" ''
+          #!${pkgs.bash}/bin/bash
+          ${dmcfg.setupCommands}
+        ''}
+      ''}
       ${cfg.extraSeatDefaults}
     '';
 
@@ -73,6 +80,7 @@ in
   imports = [
     ./lightdm-greeters/gtk.nix
     ./lightdm-greeters/mini.nix
+    ./lightdm-greeters/enso-os.nix
   ];
 
   options = {
@@ -113,6 +121,15 @@ in
         };
       };
 
+      extraConfig = mkOption {
+        type = types.lines;
+        default = "";
+        example = ''
+          user-authority-in-system-dir = true
+        '';
+        description = "Extra lines to append to LightDM section.";
+      };
+
       background = mkOption {
         type = types.str;
         default = "${pkgs.nixos-artwork.wallpapers.simple-dark-gray-bottom}/share/artwork/gnome/nix-wallpaper-simple-dark-gray_bottom.png";
@@ -197,7 +214,7 @@ in
       # lightdm relaunches itself via just `lightdm`, so needs to be on the PATH
       execCmd = ''
         export PATH=${lightdm}/sbin:$PATH
-        exec ${lightdm}/sbin/lightdm --log-dir=/var/log --run-dir=/run
+        exec ${lightdm}/sbin/lightdm
       '';
     };
 
@@ -246,12 +263,19 @@ in
     '';
 
     users.users.lightdm = {
-      createHome = true;
-      home = "/var/lib/lightdm-data";
+      home = "/var/lib/lightdm";
       group = "lightdm";
       uid = config.ids.uids.lightdm;
     };
 
+    systemd.tmpfiles.rules = [
+      "d /run/lightdm 0711 lightdm lightdm 0"
+      "d /var/cache/lightdm 0711 root lightdm -"
+      "d /var/lib/lightdm 1770 lightdm lightdm -"
+      "d /var/lib/lightdm-data 1775 lightdm lightdm -"
+      "d /var/log/lightdm 0711 root lightdm -"
+    ];
+
     users.groups.lightdm.gid = config.ids.gids.lightdm;
     services.xserver.tty     = null; # We might start multiple X servers so let the tty increment themselves..
     services.xserver.display = null; # We specify our own display (and logfile) in xserver-wrapper up there
diff --git a/nixos/modules/services/x11/display-managers/sddm.nix b/nixos/modules/services/x11/display-managers/sddm.nix
index 1635c0f9acc5..522a0dc92d6f 100644
--- a/nixos/modules/services/x11/display-managers/sddm.nix
+++ b/nixos/modules/services/x11/display-managers/sddm.nix
@@ -20,6 +20,7 @@ let
   Xsetup = pkgs.writeScript "Xsetup" ''
     #!/bin/sh
     ${cfg.setupScript}
+    ${dmcfg.setupCommands}
   '';
 
   Xstop = pkgs.writeScript "Xstop" ''
@@ -137,7 +138,8 @@ in
           xrandr --auto
         '';
         description = ''
-          A script to execute when starting the display server.
+          A script to execute when starting the display server. DEPRECATED, please
+          use <option>services.xserver.displayManager.setupCommands</option>.
         '';
       };
 
@@ -263,7 +265,9 @@ in
     };
 
     environment.etc."sddm.conf".source = cfgFile;
-    environment.pathsToLink = [ "/share/sddm/themes" ];
+    environment.pathsToLink = [ 
+      "/share/sddm" 
+    ];
 
     users.groups.sddm.gid = config.ids.gids.sddm;
 
diff --git a/nixos/modules/services/x11/display-managers/startx.nix b/nixos/modules/services/x11/display-managers/startx.nix
new file mode 100644
index 000000000000..15609540a6e7
--- /dev/null
+++ b/nixos/modules/services/x11/display-managers/startx.nix
@@ -0,0 +1,44 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.xserver.displayManager.startx;
+
+in
+
+{
+
+  ###### interface
+
+  options = {
+    services.xserver.displayManager.startx = {
+      enable = mkOption {
+        default = false;
+        description = ''
+          Whether to enable the dummy "startx" pseudo-display manager,
+          which allows users to start X manually via the "startx" command
+          from a vt shell. The X server runs under the user's id, not as root.
+          The user must provide a ~/.xinintrc file containing session startup
+          commands, see startx(1). This is not autmatically generated
+          from the desktopManager and windowManager settings.
+        '';
+      };
+    };
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+    services.xserver = {
+      exportConfiguration = true;
+      displayManager.job.execCmd = "";
+      displayManager.lightdm.enable = lib.mkForce false;
+    };
+    systemd.services.display-manager.enable = false;
+    environment.systemPackages =  with pkgs; [ xorg.xinit ];
+  };
+
+}
diff --git a/nixos/modules/services/x11/gdk-pixbuf.nix b/nixos/modules/services/x11/gdk-pixbuf.nix
new file mode 100644
index 000000000000..58faa8e2f9df
--- /dev/null
+++ b/nixos/modules/services/x11/gdk-pixbuf.nix
@@ -0,0 +1,45 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.xserver.gdk-pixbuf;
+
+  # Get packages to generate the cache for. We always include gdk_pixbuf.
+  effectivePackages = unique ([pkgs.gdk_pixbuf] ++ cfg.modulePackages);
+
+  # Generate the cache file by running gdk-pixbuf-query-loaders for each
+  # package and concatenating the results.
+  loadersCache = pkgs.runCommand "gdk-pixbuf-loaders.cache" {} ''
+    (
+      for package in ${concatStringsSep " " effectivePackages}; do
+        module_dir="$package/${pkgs.gdk_pixbuf.moduleDir}"
+        if [[ ! -d $module_dir ]]; then
+          echo "Warning (services.xserver.gdk-pixbuf): missing module directory $module_dir" 1>&2
+          continue
+        fi
+        GDK_PIXBUF_MODULEDIR="$module_dir" \
+          ${pkgs.gdk_pixbuf.dev}/bin/gdk-pixbuf-query-loaders
+      done
+    ) > "$out"
+  '';
+in
+
+{
+  options = {
+    services.xserver.gdk-pixbuf.modulePackages = mkOption {
+      type = types.listOf types.package;
+      default = [ ];
+      description = "Packages providing GDK-Pixbuf modules, for cache generation.";
+    };
+  };
+
+  # If there is any package configured in modulePackages, we generate the
+  # loaders.cache based on that and set the environment variable
+  # GDK_PIXBUF_MODULE_FILE to point to it.
+  config = mkIf (cfg.modulePackages != []) {
+    environment.variables = {
+      GDK_PIXBUF_MODULE_FILE = "${loadersCache}";
+    };
+  };
+}
diff --git a/nixos/modules/services/x11/hardware/libinput.nix b/nixos/modules/services/x11/hardware/libinput.nix
index 072004d5dd91..58fe702d4969 100644
--- a/nixos/modules/services/x11/hardware/libinput.nix
+++ b/nixos/modules/services/x11/hardware/libinput.nix
@@ -205,7 +205,7 @@ in {
       })
     ];
 
-    services.udev.packages = [ pkgs.libinput ];
+    services.udev.packages = [ pkgs.libinput.out ];
 
     services.xserver.config =
       ''
diff --git a/nixos/modules/services/x11/xserver.nix b/nixos/modules/services/x11/xserver.nix
index 75bfeaac1fa3..070a02473437 100644
--- a/nixos/modules/services/x11/xserver.nix
+++ b/nixos/modules/services/x11/xserver.nix
@@ -374,6 +374,12 @@ in
         description = "Contents of the first Monitor section of the X server configuration file.";
       };
 
+      extraConfig = mkOption {
+        type = types.lines;
+        default = "";
+        description = "Additional contents (sections) included in the X server configuration file";
+      };
+
       xrandrHeads = mkOption {
         default = [];
         example = [
@@ -625,6 +631,8 @@ in
       ]
       ++ optional (elem "virtualbox" cfg.videoDrivers) xorg.xrefresh;
 
+    environment.pathsToLink = [ "/share/X11" ];
+
     xdg = { 
       autostart.enable = true;
       menus.enable = true;
@@ -754,6 +762,7 @@ in
             Driver "${driver.driverName or driver.name}"
             ${if cfg.useGlamor then ''Option "AccelMethod" "glamor"'' else ""}
             ${cfg.deviceSection}
+            ${driver.deviceSection or ""}
             ${xrandrDeviceSection}
           EndSection
 
@@ -765,6 +774,7 @@ in
             ''}
 
             ${cfg.screenSection}
+            ${driver.screenSection or ""}
 
             ${optionalString (cfg.defaultDepth != 0) ''
               DefaultDepth ${toString cfg.defaultDepth}
@@ -794,6 +804,8 @@ in
         '')}
 
         ${xrandrMonitorSections}
+
+        ${cfg.extraConfig}
       '';
 
     fonts.enableDefaultFonts = mkDefault true;
diff --git a/nixos/modules/system/activation/activation-script.nix b/nixos/modules/system/activation/activation-script.nix
index 93a1b13a81dd..0ed7c0f53eb9 100644
--- a/nixos/modules/system/activation/activation-script.nix
+++ b/nixos/modules/system/activation/activation-script.nix
@@ -21,7 +21,7 @@ let
     [ coreutils
       gnugrep
       findutils
-      glibc # needed for getent
+      getent
       shadow
       nettools # needed for hostname
       utillinux # needed for mount and mountpoint
@@ -100,6 +100,52 @@ in
             exit $_status
           '';
       };
+    };
+
+    system.userActivationScripts = mkOption {
+      default = {};
+
+      example = literalExample ''
+        { plasmaSetup = {
+            text = '''
+              ${pkgs.libsForQt5.kservice}/bin/kbuildsycoca5"
+            ''';
+            deps = [];
+          };
+        }
+      '';
+
+      description = ''
+        A set of shell script fragments that are executed by a systemd user
+        service when a NixOS system configuration is activated. Examples are
+        rebuilding the .desktop file cache for showing applications in the menu.
+        Since these are executed every time you run
+        <command>nixos-rebuild</command>, it's important that they are
+        idempotent and fast.
+      '';
+
+      type = types.attrsOf types.unspecified;
+
+      apply = set: {
+        script = ''
+          unset PATH
+          for i in ${toString path}; do
+            PATH=$PATH:$i/bin:$i/sbin
+          done
+
+          _status=0
+          trap "_status=1 _localstatus=\$?" ERR
+
+          ${
+            let
+              set' = mapAttrs (n: v: if isString v then noDepEntry v else v) set;
+              withHeadlines = addAttributeName set';
+            in textClosureMap id (withHeadlines) (attrNames withHeadlines)
+          }
+
+          exit $_status
+        '';
+      };
 
     };
 
@@ -128,14 +174,6 @@ in
       ''
         # Various log/runtime directories.
 
-        mkdir -m 0755 -p /run/nix/current-load # for distributed builds
-        mkdir -m 0700 -p /run/nix/remote-stores
-
-        mkdir -m 0755 -p /var/log
-
-        touch /var/log/wtmp /var/log/lastlog # must exist
-        chmod 644 /var/log/wtmp /var/log/lastlog
-
         mkdir -m 1777 -p /var/tmp
 
         # Empty, immutable home directory of many system accounts.
@@ -177,6 +215,14 @@ in
         source ${config.system.build.earlyMountScript}
       '';
 
+    systemd.user = {
+      services.nixos-activation = {
+        description = "Run user specific NixOS activation";
+        script = config.system.userActivationScripts.script;
+        unitConfig.ConditionUser = "!@system";
+        serviceConfig.Type = "oneshot";
+      };
+    };
   };
 
 }
diff --git a/nixos/modules/system/activation/switch-to-configuration.pl b/nixos/modules/system/activation/switch-to-configuration.pl
index b3fe6caf62dc..397b308b7311 100644
--- a/nixos/modules/system/activation/switch-to-configuration.pl
+++ b/nixos/modules/system/activation/switch-to-configuration.pl
@@ -419,7 +419,8 @@ while (my $f = <$listActiveUsers>) {
     my ($uid, $name) = ($+{uid}, $+{user});
     print STDERR "reloading user units for $name...\n";
 
-    system("su", "-l", $name, "-c", "XDG_RUNTIME_DIR=/run/user/$uid @systemd@/bin/systemctl --user daemon-reload");
+    system("@su@", "-s", "@shell@", "-l", $name, "-c", "XDG_RUNTIME_DIR=/run/user/$uid @systemd@/bin/systemctl --user daemon-reload");
+    system("@su@", "-s", "@shell@", "-l", $name, "-c", "XDG_RUNTIME_DIR=/run/user/$uid @systemd@/bin/systemctl --user start nixos-activation.service");
 }
 
 close $listActiveUsers;
diff --git a/nixos/modules/system/activation/top-level.nix b/nixos/modules/system/activation/top-level.nix
index fff88e2c2bf3..a560af5ce96d 100644
--- a/nixos/modules/system/activation/top-level.nix
+++ b/nixos/modules/system/activation/top-level.nix
@@ -93,48 +93,53 @@ let
       ${config.system.extraSystemBuilderCmds}
     '';
 
-  # Handle assertions
-
-  failed = map (x: x.message) (filter (x: !x.assertion) config.assertions);
-
-  showWarnings = res: fold (w: x: builtins.trace "warning: ${w}" x) res config.warnings;
-
   # Putting it all together.  This builds a store path containing
   # symlinks to the various parts of the built configuration (the
   # kernel, systemd units, init scripts, etc.) as well as a script
   # `switch-to-configuration' that activates the configuration and
   # makes it bootable.
-  baseSystem = showWarnings (
-    if [] == failed then pkgs.stdenvNoCC.mkDerivation {
-      name = let hn = config.networking.hostName;
-                 nn = if (hn != "") then hn else "unnamed";
-          in "nixos-system-${nn}-${config.system.nixos.label}";
-      preferLocalBuild = true;
-      allowSubstitutes = false;
-      buildCommand = systemBuilder;
-
-      inherit (pkgs) utillinux coreutils;
-      systemd = config.systemd.package;
+  baseSystem = pkgs.stdenvNoCC.mkDerivation {
+    name = let hn = config.networking.hostName;
+               nn = if (hn != "") then hn else "unnamed";
+        in "nixos-system-${nn}-${config.system.nixos.label}";
+    preferLocalBuild = true;
+    allowSubstitutes = false;
+    buildCommand = systemBuilder;
+
+    inherit (pkgs) utillinux coreutils;
+    systemd = config.systemd.package;
+    shell = "${pkgs.bash}/bin/sh";
+    su = "${pkgs.shadow.su}/bin/su";
+
+    inherit children;
+    kernelParams = config.boot.kernelParams;
+    installBootLoader =
+      config.system.build.installBootLoader
+      or "echo 'Warning: do not know how to make this configuration bootable; please enable a boot loader.' 1>&2; true";
+    activationScript = config.system.activationScripts.script;
+    nixosLabel = config.system.nixos.label;
+
+    configurationName = config.boot.loader.grub.configurationName;
+
+    # Needed by switch-to-configuration.
+
+    perl = "${pkgs.perl}/bin/perl " + (concatMapStringsSep " " (lib: "-I${lib}/${pkgs.perl.libPrefix}") (with pkgs.perlPackages; [ FileSlurp NetDBus XMLParser XMLTwig ]));
+  };
 
-      inherit children;
-      kernelParams = config.boot.kernelParams;
-      installBootLoader =
-        config.system.build.installBootLoader
-        or "echo 'Warning: do not know how to make this configuration bootable; please enable a boot loader.' 1>&2; true";
-      activationScript = config.system.activationScripts.script;
-      nixosLabel = config.system.nixos.label;
+  # Handle assertions and warnings
 
-      configurationName = config.boot.loader.grub.configurationName;
+  failedAssertions = map (x: x.message) (filter (x: !x.assertion) config.assertions);
 
-      # Needed by switch-to-configuration.
+  showWarnings = res: fold (w: x: builtins.trace "warning: ${w}" x) res config.warnings;
 
-      perl = "${pkgs.perl}/bin/perl " + (concatMapStringsSep " " (lib: "-I${lib}/${pkgs.perl.libPrefix}") (with pkgs.perlPackages; [ FileSlurp NetDBus XMLParser XMLTwig ]));
-  } else throw "\nFailed assertions:\n${concatStringsSep "\n" (map (x: "- ${x}") failed)}");
+  baseSystemAssertWarn = if failedAssertions != []
+    then throw "\nFailed assertions:\n${concatStringsSep "\n" (map (x: "- ${x}") failedAssertions)}"
+    else showWarnings baseSystem;
 
   # Replace runtime dependencies
   system = fold ({ oldDependency, newDependency }: drv:
       pkgs.replaceDependency { inherit oldDependency newDependency drv; }
-    ) baseSystem config.system.replaceRuntimeDependencies;
+    ) baseSystemAssertWarn config.system.replaceRuntimeDependencies;
 
 in
 
diff --git a/nixos/modules/system/boot/initrd-network.nix b/nixos/modules/system/boot/initrd-network.nix
index 384ae909b701..dd0ea69e9685 100644
--- a/nixos/modules/system/boot/initrd-network.nix
+++ b/nixos/modules/system/boot/initrd-network.nix
@@ -6,11 +6,22 @@ let
 
   cfg = config.boot.initrd.network;
 
+  dhcpinterfaces = lib.attrNames (lib.filterAttrs (iface: v: v.useDHCP == true) (config.networking.interfaces or {}));
+
   udhcpcScript = pkgs.writeScript "udhcp-script"
     ''
       #! /bin/sh
       if [ "$1" = bound ]; then
         ip address add "$ip/$mask" dev "$interface"
+        if [ -n "$mtu" ]; then
+          ip link set mtu "$mtu" dev "$interface"
+        fi
+        if [ -n "$staticroutes" ]; then
+          echo "$staticroutes" \
+            | sed -r "s@(\S+) (\S+)@ ip route add \"\1\" via \"\2\" dev \"$interface\" ; @g" \
+            | sed -r "s@ via \"0\.0\.0\.0\"@@g" \
+            | /bin/sh
+        fi
         if [ -n "$router" ]; then
           ip route add "$router" dev "$interface" # just in case if "$router" is not within "$ip/$mask" (e.g. Hetzner Cloud)
           ip route add default via "$router" dev "$interface"
@@ -93,18 +104,24 @@ in
       ''
 
       # Otherwise, use DHCP.
-      + optionalString config.networking.useDHCP ''
+      + optionalString (config.networking.useDHCP || dhcpinterfaces != []) ''
         if [ -z "$hasNetwork" ]; then
 
           # Bring up all interfaces.
-          for iface in $(cd /sys/class/net && ls); do
+          for iface in $(ls /sys/class/net/); do
             echo "bringing up network interface $iface..."
             ip link set "$iface" up
           done
 
-          # Acquire a DHCP lease.
-          echo "acquiring IP address via DHCP..."
-          udhcpc --quit --now --script ${udhcpcScript} ${udhcpcArgs} && hasNetwork=1
+          # Acquire DHCP leases.
+          for iface in ${ if config.networking.useDHCP then
+                            "$(ls /sys/class/net/ | grep -v ^lo$)"
+                          else
+                            lib.concatMapStringsSep " " lib.escapeShellArg dhcpinterfaces
+                        }; do
+            echo "acquiring IP address via DHCP on $iface..."
+            udhcpc --quit --now -i $iface -O staticroutes --script ${udhcpcScript} ${udhcpcArgs} && hasNetwork=1
+          done
         fi
       ''
 
diff --git a/nixos/modules/system/boot/kexec.nix b/nixos/modules/system/boot/kexec.nix
index 3e5d7b40f2c5..61f9c6d0e7eb 100644
--- a/nixos/modules/system/boot/kexec.nix
+++ b/nixos/modules/system/boot/kexec.nix
@@ -1,7 +1,7 @@
 { pkgs, lib, ... }:
 
 {
-  config = lib.mkIf (pkgs.kexectools.meta.available) {
+  config = lib.mkIf (lib.any (lib.meta.platformMatch pkgs.stdenv.hostPlatform) pkgs.kexectools.meta.platforms) {
     environment.systemPackages = [ pkgs.kexectools ];
 
     systemd.services."prepare-kexec" =
diff --git a/nixos/modules/system/boot/loader/raspberrypi/builder_uboot.sh b/nixos/modules/system/boot/loader/raspberrypi/builder_uboot.sh
deleted file mode 100644
index 36bf15066274..000000000000
--- a/nixos/modules/system/boot/loader/raspberrypi/builder_uboot.sh
+++ /dev/null
@@ -1,29 +0,0 @@
-#! @bash@/bin/sh -e
-
-copyForced() {
-    local src="$1"
-    local dst="$2"
-    cp $src $dst.tmp
-    mv $dst.tmp $dst
-}
-
-# Call the extlinux builder
-"@extlinuxConfBuilder@" "$@"
-
-# Add the firmware files
-fwdir=@firmware@/share/raspberrypi/boot/
-copyForced $fwdir/bootcode.bin  /boot/bootcode.bin
-copyForced $fwdir/fixup.dat     /boot/fixup.dat
-copyForced $fwdir/fixup_cd.dat  /boot/fixup_cd.dat
-copyForced $fwdir/fixup_db.dat  /boot/fixup_db.dat
-copyForced $fwdir/fixup_x.dat   /boot/fixup_x.dat
-copyForced $fwdir/start.elf     /boot/start.elf
-copyForced $fwdir/start_cd.elf  /boot/start_cd.elf
-copyForced $fwdir/start_db.elf  /boot/start_db.elf
-copyForced $fwdir/start_x.elf   /boot/start_x.elf
-
-# Add the uboot file
-copyForced @uboot@/u-boot.bin /boot/u-boot-rpi.bin
-
-# Add the config.txt
-copyForced @configTxt@ /boot/config.txt
diff --git a/nixos/modules/system/boot/loader/raspberrypi/raspberrypi-builder.nix b/nixos/modules/system/boot/loader/raspberrypi/raspberrypi-builder.nix
new file mode 100644
index 000000000000..7eb52e3d021f
--- /dev/null
+++ b/nixos/modules/system/boot/loader/raspberrypi/raspberrypi-builder.nix
@@ -0,0 +1,10 @@
+{ pkgs, configTxt }:
+
+pkgs.substituteAll {
+  src = ./raspberrypi-builder.sh;
+  isExecutable = true;
+  inherit (pkgs) bash;
+  path = [pkgs.coreutils pkgs.gnused pkgs.gnugrep];
+  firmware = pkgs.raspberrypifw;
+  inherit configTxt;
+}
diff --git a/nixos/modules/system/boot/loader/raspberrypi/builder.sh b/nixos/modules/system/boot/loader/raspberrypi/raspberrypi-builder.sh
index 8adc8a6a7e11..0fb07de10c04 100644
--- a/nixos/modules/system/boot/loader/raspberrypi/builder.sh
+++ b/nixos/modules/system/boot/loader/raspberrypi/raspberrypi-builder.sh
@@ -5,15 +5,25 @@ shopt -s nullglob
 export PATH=/empty
 for i in @path@; do PATH=$PATH:$i/bin; done
 
-default=$1
-if test -z "$1"; then
-    echo "Syntax: builder.sh <DEFAULT-CONFIG>"
+usage() {
+    echo "usage: $0 -c <path-to-default-configuration> [-d <boot-dir>]" >&2
     exit 1
-fi
+}
+
+default=                # Default configuration
+target=/boot            # Target directory
+
+while getopts "c:d:" opt; do
+    case "$opt" in
+        c) default="$OPTARG" ;;
+        d) target="$OPTARG" ;;
+        \?) usage ;;
+    esac
+done
 
 echo "updating the boot generations directory..."
 
-mkdir -p /boot/old
+mkdir -p $target/old
 
 # Convert a path to a file in the Nix store such as
 # /nix/store/<hash>-<name>/file to <hash>-<name>-<file>.
@@ -22,12 +32,12 @@ cleanName() {
     echo "$path" | sed 's|^/nix/store/||' | sed 's|/|-|g'
 }
 
-# Copy a file from the Nix store to /boot/kernels.
+# Copy a file from the Nix store to $target/kernels.
 declare -A filesCopied
 
 copyToKernelsDir() {
     local src="$1"
-    local dst="/boot/old/$(cleanName $src)"
+    local dst="$target/old/$(cleanName $src)"
     # Don't copy the file if $dst already exists.  This means that we
     # have to create $dst atomically to prevent partially copied
     # kernels or initrd if this script is ever interrupted.
@@ -47,10 +57,10 @@ copyForced() {
     mv $dst.tmp $dst
 }
 
-outdir=/boot/old
+outdir=$target/old
 mkdir -p $outdir || true
 
-# Copy its kernel and initrd to /boot/kernels.
+# Copy its kernel and initrd to $target/old.
 addEntry() {
     local path="$1"
     local generation="$2"
@@ -74,25 +84,21 @@ addEntry() {
     echo $initrd > $outdir/$generation-initrd
     echo $kernel > $outdir/$generation-kernel
 
-    if test $(readlink -f "$path") = "$default"; then
-      if [ @version@ -eq 1 ]; then
-        copyForced $kernel /boot/kernel.img
-      else
-        copyForced $kernel /boot/kernel7.img
-      fi
-      copyForced $initrd /boot/initrd
-      for dtb in $dtb_path/bcm*.dtb; do
-        dst="/boot/$(basename $dtb)"
+    if test "$generation" = "default"; then
+      copyForced $kernel $target/kernel.img
+      copyForced $initrd $target/initrd
+      for dtb in $dtb_path/{broadcom,}/bcm*.dtb; do
+        dst="$target/$(basename $dtb)"
         copyForced $dtb "$dst"
         filesCopied[$dst]=1
       done
-      cp "$(readlink -f "$path/init")" /boot/nixos-init
-      echo "`cat $path/kernel-params` init=$path/init" >/boot/cmdline.txt
-
-      echo "$2" > /boot/defaultgeneration
+      cp "$(readlink -f "$path/init")" $target/nixos-init
+      echo "`cat $path/kernel-params` init=$path/init" >$target/cmdline.txt
     fi
 }
 
+addEntry $default default
+
 # Add all generations of the system profile to the menu, in reverse
 # (most recent to least recent) order.
 for generation in $(
@@ -105,21 +111,21 @@ done
 
 # Add the firmware files
 fwdir=@firmware@/share/raspberrypi/boot/
-copyForced $fwdir/bootcode.bin  /boot/bootcode.bin
-copyForced $fwdir/fixup.dat     /boot/fixup.dat
-copyForced $fwdir/fixup_cd.dat  /boot/fixup_cd.dat
-copyForced $fwdir/fixup_db.dat  /boot/fixup_db.dat
-copyForced $fwdir/fixup_x.dat   /boot/fixup_x.dat
-copyForced $fwdir/start.elf     /boot/start.elf
-copyForced $fwdir/start_cd.elf  /boot/start_cd.elf
-copyForced $fwdir/start_db.elf  /boot/start_db.elf
-copyForced $fwdir/start_x.elf   /boot/start_x.elf
+copyForced $fwdir/bootcode.bin  $target/bootcode.bin
+copyForced $fwdir/fixup.dat     $target/fixup.dat
+copyForced $fwdir/fixup_cd.dat  $target/fixup_cd.dat
+copyForced $fwdir/fixup_db.dat  $target/fixup_db.dat
+copyForced $fwdir/fixup_x.dat   $target/fixup_x.dat
+copyForced $fwdir/start.elf     $target/start.elf
+copyForced $fwdir/start_cd.elf  $target/start_cd.elf
+copyForced $fwdir/start_db.elf  $target/start_db.elf
+copyForced $fwdir/start_x.elf   $target/start_x.elf
 
 # Add the config.txt
-copyForced @configTxt@ /boot/config.txt
+copyForced @configTxt@ $target/config.txt
 
-# Remove obsolete files from /boot and /boot/old.
-for fn in /boot/old/*linux* /boot/old/*initrd-initrd* /boot/bcm*.dtb; do
+# Remove obsolete files from $target and $target/old.
+for fn in $target/old/*linux* $target/old/*initrd-initrd* $target/bcm*.dtb; do
     if ! test "${filesCopied[$fn]}" = 1; then
         rm -vf -- "$fn"
     fi
diff --git a/nixos/modules/system/boot/loader/raspberrypi/raspberrypi.nix b/nixos/modules/system/boot/loader/raspberrypi/raspberrypi.nix
index 9bec24c53f5b..7e089507ff20 100644
--- a/nixos/modules/system/boot/loader/raspberrypi/raspberrypi.nix
+++ b/nixos/modules/system/boot/loader/raspberrypi/raspberrypi.nix
@@ -5,25 +5,16 @@ with lib;
 let
   cfg = config.boot.loader.raspberryPi;
 
-  builderGeneric = pkgs.substituteAll {
-    src = ./builder.sh;
-    isExecutable = true;
-    inherit (pkgs) bash;
-    path = [pkgs.coreutils pkgs.gnused pkgs.gnugrep];
-    firmware = pkgs.raspberrypifw;
-    version = cfg.version;
-    inherit configTxt;
-  };
-
   inherit (pkgs.stdenv.hostPlatform) platform;
 
-  builderUboot = import ./builder_uboot.nix { inherit config; inherit pkgs; inherit configTxt; };
+  builderUboot = import ./uboot-builder.nix { inherit pkgs configTxt; inherit (cfg) version; };
+  builderGeneric = import ./raspberrypi-builder.nix { inherit pkgs configTxt; };
 
   builder = 
     if cfg.uboot.enable then
       "${builderUboot} -g ${toString cfg.uboot.configurationLimit} -t ${timeoutStr} -c"
     else
-      builderGeneric;
+      "${builderGeneric} -c";
 
   blCfg = config.boot.loader;
   timeoutStr = if blCfg.timeout == null then "-1" else toString blCfg.timeout;
@@ -43,9 +34,12 @@ let
     '' + optional isAarch64 ''
       # Boot in 64-bit mode.
       arm_control=0x200
-    '' + optional cfg.uboot.enable ''
+    '' + (if cfg.uboot.enable then ''
       kernel=u-boot-rpi.bin
-    '' + optional (cfg.firmwareConfig != null) cfg.firmwareConfig);
+    '' else ''
+      kernel=kernel.img
+      initramfs initrd followkernel
+    '') + optional (cfg.firmwareConfig != null) cfg.firmwareConfig);
 
 in
 
@@ -65,7 +59,7 @@ in
 
       version = mkOption {
         default = 2;
-        type = types.enum [ 1 2 3 ];
+        type = types.enum [ 0 1 2 3 ];
         description = ''
         '';
       };
diff --git a/nixos/modules/system/boot/loader/raspberrypi/builder_uboot.nix b/nixos/modules/system/boot/loader/raspberrypi/uboot-builder.nix
index 47f25a9c2b1b..e929c33c6ee3 100644
--- a/nixos/modules/system/boot/loader/raspberrypi/builder_uboot.nix
+++ b/nixos/modules/system/boot/loader/raspberrypi/uboot-builder.nix
@@ -1,13 +1,14 @@
-{ config, pkgs, configTxt }:
+{ pkgs, version, configTxt }:
 
 let
-  cfg = config.boot.loader.raspberryPi;
   isAarch64 = pkgs.stdenv.isAarch64;
 
   uboot =
-    if cfg.version == 1 then
+    if version == 0 then
+      pkgs.ubootRaspberryPiZero
+    else if version == 1 then
       pkgs.ubootRaspberryPi
-    else if cfg.version == 2 then
+    else if version == 2 then
       pkgs.ubootRaspberryPi2
     else
       if isAarch64 then
@@ -21,7 +22,7 @@ let
     };
 in
 pkgs.substituteAll {
-  src = ./builder_uboot.sh;
+  src = ./uboot-builder.sh;
   isExecutable = true;
   inherit (pkgs) bash;
   path = [pkgs.coreutils pkgs.gnused pkgs.gnugrep];
@@ -29,6 +30,6 @@ pkgs.substituteAll {
   inherit uboot;
   inherit configTxt;
   inherit extlinuxConfBuilder;
-  version = cfg.version;
+  inherit version;
 }
 
diff --git a/nixos/modules/system/boot/loader/raspberrypi/uboot-builder.sh b/nixos/modules/system/boot/loader/raspberrypi/uboot-builder.sh
new file mode 100644
index 000000000000..ea591427179f
--- /dev/null
+++ b/nixos/modules/system/boot/loader/raspberrypi/uboot-builder.sh
@@ -0,0 +1,38 @@
+#! @bash@/bin/sh -e
+
+target=/boot # Target directory
+
+while getopts "t:c:d:g:" opt; do
+    case "$opt" in
+        d) target="$OPTARG" ;;
+        *) ;;
+    esac
+done
+
+copyForced() {
+    local src="$1"
+    local dst="$2"
+    cp $src $dst.tmp
+    mv $dst.tmp $dst
+}
+
+# Call the extlinux builder
+"@extlinuxConfBuilder@" "$@"
+
+# Add the firmware files
+fwdir=@firmware@/share/raspberrypi/boot/
+copyForced $fwdir/bootcode.bin  $target/bootcode.bin
+copyForced $fwdir/fixup.dat     $target/fixup.dat
+copyForced $fwdir/fixup_cd.dat  $target/fixup_cd.dat
+copyForced $fwdir/fixup_db.dat  $target/fixup_db.dat
+copyForced $fwdir/fixup_x.dat   $target/fixup_x.dat
+copyForced $fwdir/start.elf     $target/start.elf
+copyForced $fwdir/start_cd.elf  $target/start_cd.elf
+copyForced $fwdir/start_db.elf  $target/start_db.elf
+copyForced $fwdir/start_x.elf   $target/start_x.elf
+
+# Add the uboot file
+copyForced @uboot@/u-boot.bin $target/u-boot-rpi.bin
+
+# Add the config.txt
+copyForced @configTxt@ $target/config.txt
diff --git a/nixos/modules/system/boot/luksroot.nix b/nixos/modules/system/boot/luksroot.nix
index ed8b9f01e275..018e7b2e7f89 100644
--- a/nixos/modules/system/boot/luksroot.nix
+++ b/nixos/modules/system/boot/luksroot.nix
@@ -7,8 +7,19 @@ let
 
   commonFunctions = ''
     die() {
-      echo "$@" >&2
-      exit 1
+        echo "$@" >&2
+        exit 1
+    }
+
+    dev_exist() {
+        local target="$1"
+        if [ -e $target ]; then
+            return 0
+        else
+            local uuid=$(echo -n $target | sed -e 's,UUID=\(.*\),\1,g')
+            blkid --uuid $uuid >/dev/null
+            return $?
+        fi
     }
 
     wait_target() {
@@ -17,13 +28,13 @@ let
         local secs="''${3:-10}"
         local desc="''${4:-$name $target to appear}"
 
-        if [ ! -e $target ]; then
+        if ! dev_exist $target; then
             echo -n "Waiting $secs seconds for $desc..."
             local success=false;
             for try in $(seq $secs); do
                 echo -n "."
                 sleep 1
-                if [ -e $target ]; then
+                if dev_exist $target; then
                     success=true
                     break
                 fi
@@ -40,30 +51,30 @@ let
     }
 
     wait_yubikey() {
-      local secs="''${1:-10}"
-
-      ykinfo -v 1>/dev/null 2>&1
-      if [ $? != 0 ]; then
-          echo -n "Waiting $secs seconds for Yubikey to appear..."
-          local success=false
-          for try in $(seq $secs); do
-              echo -n .
-              sleep 1
-              ykinfo -v 1>/dev/null 2>&1
-              if [ $? == 0 ]; then
-                  success=true
-                  break
-              fi
-          done
-          if [ $success == true ]; then
-              echo " - success";
-              return 0
-          else
-              echo " - failure";
-              return 1
-          fi
-      fi
-      return 0
+        local secs="''${1:-10}"
+
+        ykinfo -v 1>/dev/null 2>&1
+        if [ $? != 0 ]; then
+            echo -n "Waiting $secs seconds for Yubikey to appear..."
+            local success=false
+            for try in $(seq $secs); do
+                echo -n .
+                sleep 1
+                ykinfo -v 1>/dev/null 2>&1
+                if [ $? == 0 ]; then
+                    success=true
+                    break
+                fi
+            done
+            if [ $success == true ]; then
+                echo " - success";
+                return 0
+            else
+                echo " - failure";
+                return 1
+            fi
+        fi
+        return 0
     }
   '';
 
diff --git a/nixos/modules/system/boot/networkd.nix b/nixos/modules/system/boot/networkd.nix
index 4bacf0f126a4..63a6f7fbe099 100644
--- a/nixos/modules/system/boot/networkd.nix
+++ b/nixos/modules/system/boot/networkd.nix
@@ -208,7 +208,6 @@ let
       "InitialCongestionWindow" "InitialAdvertisedReceiveWindow" "QuickAck"
       "MTUBytes"
     ])
-    (assertHasField "Gateway")
   ];
 
   checkDhcp = checkUnitConfig "DHCP" [
@@ -249,13 +248,14 @@ let
   # .network files have a [Link] section with different options than in .netlink files
   checkNetworkLink = checkUnitConfig "Link" [
     (assertOnlyFields [
-      "MACAddress" "MTUBytes" "ARP" "Unmanaged" "RequiredForOnline"
+      "MACAddress" "MTUBytes" "ARP" "Multicast" "Unmanaged" "RequiredForOnline"
     ])
     (assertMacAddress "MACAddress")
     (assertByteFormat "MTUBytes")
     (assertValueOneOf "ARP" boolValues)
+    (assertValueOneOf "Multicast" boolValues)
     (assertValueOneOf "Unmanaged" boolValues)
-    (assertValueOneOf "RquiredForOnline" boolValues)
+    (assertValueOneOf "RequiredForOnline" boolValues)
   ];
 
 
diff --git a/nixos/modules/system/boot/stage-1.nix b/nixos/modules/system/boot/stage-1.nix
index f58b68cb3353..e7167999a6f8 100644
--- a/nixos/modules/system/boot/stage-1.nix
+++ b/nixos/modules/system/boot/stage-1.nix
@@ -147,7 +147,7 @@ let
       ${config.boot.initrd.extraUtilsCommands}
 
       # Copy ld manually since it isn't detected correctly
-      cp -pv ${pkgs.glibc.out}/lib/ld*.so.? $out/lib
+      cp -pv ${pkgs.stdenv.cc.libc.out}/lib/ld*.so.? $out/lib
 
       # Copy all of the needed libraries
       find $out/bin $out/lib -type f | while read BIN; do
@@ -251,9 +251,9 @@ let
     postInstall = ''
       echo checking syntax
       # check both with bash
-      ${pkgs.bash}/bin/sh -n $target
+      ${pkgs.buildPackages.bash}/bin/sh -n $target
       # and with ash shell, just in case
-      ${extraUtils}/bin/ash -n $target
+      ${pkgs.buildPackages.busybox}/bin/ash -n $target
     '';
 
     inherit udevRules extraUtils modulesClosure;
diff --git a/nixos/modules/system/boot/stage-2-init.sh b/nixos/modules/system/boot/stage-2-init.sh
index 49764b75a557..03daafa1ce4f 100644
--- a/nixos/modules/system/boot/stage-2-init.sh
+++ b/nixos/modules/system/boot/stage-2-init.sh
@@ -152,6 +152,14 @@ ln -sfn /run/booted-system /nix/var/nix/gcroots/booted-system
 @shell@ @postBootCommands@
 
 
+# Ensure systemd doesn't try to populate /etc, by forcing its first-boot
+# heuristic off. It doesn't matter what's in /etc/machine-id for this purpose,
+# and systemd will immediately fill in the file when it starts, so just
+# creating it is enough. This `: >>` pattern avoids forking and avoids changing
+# the mtime if the file already exists.
+: >> /etc/machine-id
+
+
 # Reset the logging file descriptors.
 exec 1>&$logOutFd 2>&$logErrFd
 exec {logOutFd}>&- {logErrFd}>&-
diff --git a/nixos/modules/system/boot/systemd-lib.nix b/nixos/modules/system/boot/systemd-lib.nix
index 9c8d4a026b4a..68a40377ee13 100644
--- a/nixos/modules/system/boot/systemd-lib.nix
+++ b/nixos/modules/system/boot/systemd-lib.nix
@@ -63,7 +63,7 @@ in rec {
 
   assertValueOneOf = name: values: group: attr:
     optional (attr ? ${name} && !elem attr.${name} values)
-      "Systemd ${group} field `${name}' cannot have value `${attr.${name}}'.";
+      "Systemd ${group} field `${name}' cannot have value `${toString attr.${name}}'.";
 
   assertHasField = name: group: attr:
     optional (!(attr ? ${name}))
diff --git a/nixos/modules/system/boot/systemd-nspawn.nix b/nixos/modules/system/boot/systemd-nspawn.nix
index 83fef8354360..4f538ccdbbe1 100644
--- a/nixos/modules/system/boot/systemd-nspawn.nix
+++ b/nixos/modules/system/boot/systemd-nspawn.nix
@@ -112,9 +112,7 @@ in {
 
       environment.etc."systemd/nspawn".source = generateUnits "nspawn" units [] [];
 
-      systemd.services."systemd-nspawn@" = {
-        wantedBy = [ "machine.target" ];
-      };
+      systemd.targets."multi-user".wants = [ "machines.target "];
   };
 
 }
diff --git a/nixos/modules/system/boot/systemd-unit-options.nix b/nixos/modules/system/boot/systemd-unit-options.nix
index 2cff25a8c854..5f2bec5c34ae 100644
--- a/nixos/modules/system/boot/systemd-unit-options.nix
+++ b/nixos/modules/system/boot/systemd-unit-options.nix
@@ -394,7 +394,7 @@ in rec {
         Each attribute in this set specifies an option in the
         <literal>[Timer]</literal> section of the unit.  See
         <citerefentry><refentrytitle>systemd.timer</refentrytitle>
-        <manvolnum>7</manvolnum></citerefentry> and
+        <manvolnum>5</manvolnum></citerefentry> and
         <citerefentry><refentrytitle>systemd.time</refentrytitle>
         <manvolnum>7</manvolnum></citerefentry> for details.
       '';
diff --git a/nixos/modules/system/boot/systemd.nix b/nixos/modules/system/boot/systemd.nix
index 12e029ae57f8..89f8e8153550 100644
--- a/nixos/modules/system/boot/systemd.nix
+++ b/nixos/modules/system/boot/systemd.nix
@@ -387,7 +387,7 @@ let
 
   logindHandlerType = types.enum [
     "ignore" "poweroff" "reboot" "halt" "kexec" "suspend"
-    "hibernate" "hybrid-sleep" "lock"
+    "hibernate" "hybrid-sleep" "suspend-then-hibernate" "lock"
   ];
 
 in
@@ -587,6 +587,15 @@ in
       '';
     };
 
+    services.journald.forwardToSyslog = mkOption {
+      default = config.services.rsyslogd.enable || config.services.syslog-ng.enable;
+      defaultText = "config.services.rsyslogd.enable || config.services.syslog-ng.enable";
+      type = types.bool;
+      description = ''
+        Whether to forward log messages to syslog.
+      '';
+    };
+
     services.logind.extraConfig = mkOption {
       default = "";
       type = types.lines;
@@ -747,12 +756,16 @@ in
 
       "systemd/journald.conf".text = ''
         [Journal]
+        Storage=persistent
         RateLimitInterval=${config.services.journald.rateLimitInterval}
         RateLimitBurst=${toString config.services.journald.rateLimitBurst}
         ${optionalString (config.services.journald.console != "") ''
           ForwardToConsole=yes
           TTYPath=${config.services.journald.console}
         ''}
+        ${optionalString (config.services.journald.forwardToSyslog) ''
+          ForwardToSyslog=yes
+        ''}
         ${config.services.journald.extraConfig}
       '';
 
@@ -783,19 +796,6 @@ in
 
     services.dbus.enable = true;
 
-    system.activationScripts.systemd = stringAfter [ "groups" ]
-      ''
-        mkdir -m 0755 -p /var/lib/udev
-
-        if ! [ -e /etc/machine-id ]; then
-          ${systemd}/bin/systemd-machine-id-setup
-        fi
-
-        # Keep a persistent journal. Note that systemd-tmpfiles will
-        # set proper ownership/permissions.
-        mkdir -m 0700 -p /var/log/journal
-      '';
-
     users.users.systemd-network.uid = config.ids.uids.systemd-network;
     users.groups.systemd-network.gid = config.ids.gids.systemd-network;
     users.users.systemd-resolve.uid = config.ids.uids.systemd-resolve;
@@ -886,6 +886,9 @@ in
     #systemd.services.systemd-logind.restartTriggers = [ config.environment.etc."systemd/logind.conf".source ];
     systemd.services.systemd-logind.restartIfChanged = false;
     systemd.services.systemd-logind.stopIfChanged = false;
+    # The user-runtime-dir@ service is managed by systemd-logind we should not touch it or else we break the users' sessions.
+    systemd.services."user-runtime-dir@".stopIfChanged = false;
+    systemd.services."user-runtime-dir@".restartIfChanged = false;
     systemd.services.systemd-journald.restartTriggers = [ config.environment.etc."systemd/journald.conf".source ];
     systemd.services.systemd-journald.stopIfChanged = false;
     systemd.targets.local-fs.unitConfig.X-StopOnReconfiguration = true;
diff --git a/nixos/modules/tasks/filesystems/zfs.nix b/nixos/modules/tasks/filesystems/zfs.nix
index 2b3b09d725c7..8f8c9e23e13e 100644
--- a/nixos/modules/tasks/filesystems/zfs.nix
+++ b/nixos/modules/tasks/filesystems/zfs.nix
@@ -74,7 +74,7 @@ let
   importLib = {zpoolCmd, awkCmd, cfgZfs}: ''
     poolReady() {
       pool="$1"
-      state="$("${zpoolCmd}" import | "${awkCmd}" "/pool: $pool/ { found = 1 }; /state:/ { if (found == 1) { print \$2; exit } }; END { if (found == 0) { print \"MISSING\" } }")"
+      state="$("${zpoolCmd}" import 2>/dev/null | "${awkCmd}" "/pool: $pool/ { found = 1 }; /state:/ { if (found == 1) { print \$2; exit } }; END { if (found == 0) { print \"MISSING\" } }")"
       if [[ "$state" = "ONLINE" ]]; then
         return 0
       else
diff --git a/nixos/modules/tasks/network-interfaces-scripted.nix b/nixos/modules/tasks/network-interfaces-scripted.nix
index af61c95da0af..93dfefdce902 100644
--- a/nixos/modules/tasks/network-interfaces-scripted.nix
+++ b/nixos/modules/tasks/network-interfaces-scripted.nix
@@ -85,7 +85,8 @@ let
             after = [ "network-pre.target" "systemd-udevd.service" "systemd-sysctl.service" ];
             before = [ "network.target" "shutdown.target" ];
             wants = [ "network.target" ];
-            partOf = map (i: "network-addresses-${i.name}.service") interfaces;
+            # exclude bridges from the partOf relationship to fix container networking bug #47210
+            partOf = map (i: "network-addresses-${i.name}.service") (filter (i: !(hasAttr i.name cfg.bridges)) interfaces);
             conflicts = [ "shutdown.target" ];
             wantedBy = [ "multi-user.target" ] ++ optional hasDefaultGatewaySet "network-online.target";
 
diff --git a/nixos/modules/tasks/network-interfaces.nix b/nixos/modules/tasks/network-interfaces.nix
index 20a740ce1f0c..815523093dde 100644
--- a/nixos/modules/tasks/network-interfaces.nix
+++ b/nixos/modules/tasks/network-interfaces.nix
@@ -341,7 +341,7 @@ in
         You should try to make this ID unique among your machines. You can
         generate a random 32-bit ID using the following commands:
 
-        <literal>cksum /etc/machine-id | while read c rest; do printf "%x" $c; done</literal>
+        <literal>head -c 8 /etc/machine-id</literal>
 
         (this derives it from the machine-id that systemd generates) or
 
diff --git a/nixos/modules/testing/test-instrumentation.nix b/nixos/modules/testing/test-instrumentation.nix
index ed4cfa7805e2..aa0db4afd978 100644
--- a/nixos/modules/testing/test-instrumentation.nix
+++ b/nixos/modules/testing/test-instrumentation.nix
@@ -55,7 +55,8 @@ with import ../../lib/qemu-flags.nix { inherit pkgs; };
     systemd.services."serial-getty@hvc0".enable = false;
 
     # Only use a serial console, no TTY.
-    virtualisation.qemu.consoles = [ qemuSerialDevice ];
+    # hvc1: socket backdoor, see "Debugging NixOS tests" section in NixOS manual
+    virtualisation.qemu.consoles = [ "hvc1" qemuSerialDevice ];
 
     boot.initrd.preDeviceCommands =
       ''
diff --git a/nixos/modules/virtualisation/amazon-image.nix b/nixos/modules/virtualisation/amazon-image.nix
index e9e935e90202..c92570582f20 100644
--- a/nixos/modules/virtualisation/amazon-image.nix
+++ b/nixos/modules/virtualisation/amazon-image.nix
@@ -145,8 +145,12 @@ let cfg = config.ec2; in
     environment.systemPackages = [ pkgs.cryptsetup ];
 
     boot.initrd.supportedFilesystems = [ "unionfs-fuse" ];
-    
+
     # EC2 has its own NTP server provided by the hypervisor
     networking.timeServers = [ "169.254.169.123" ];
+
+    # udisks has become too bloated to have in a headless system
+    # (e.g. it depends on GTK+).
+    services.udisks2.enable = false;
   };
 }
diff --git a/nixos/modules/virtualisation/container-config.nix b/nixos/modules/virtualisation/container-config.nix
index 5e368acd6d8b..561db7cabcfb 100644
--- a/nixos/modules/virtualisation/container-config.nix
+++ b/nixos/modules/virtualisation/container-config.nix
@@ -22,6 +22,13 @@ with lib;
     # Not supported in systemd-nspawn containers.
     security.audit.enable = false;
 
+    # Make sure that root user in container will talk to host nix-daemon
+    environment.etc."profile".text = ''
+    export NIX_REMOTE=daemon
+    '';
+
+
+
   };
 
 }
diff --git a/nixos/modules/virtualisation/containers.nix b/nixos/modules/virtualisation/containers.nix
index b91165ce3b82..2fcc0f254256 100644
--- a/nixos/modules/virtualisation/containers.nix
+++ b/nixos/modules/virtualisation/containers.nix
@@ -130,6 +130,7 @@ let
         --bind-ro=/nix/var/nix/daemon-socket \
         --bind="/nix/var/nix/profiles/per-container/$INSTANCE:/nix/var/nix/profiles" \
         --bind="/nix/var/nix/gcroots/per-container/$INSTANCE:/nix/var/nix/gcroots" \
+        --link-journal=try-guest \
         --setenv PRIVATE_NETWORK="$PRIVATE_NETWORK" \
         --setenv HOST_BRIDGE="$HOST_BRIDGE" \
         --setenv HOST_ADDRESS="$HOST_ADDRESS" \
@@ -242,6 +243,9 @@ let
 
     Restart = "on-failure";
 
+    Slice = "machine.slice";
+    Delegate = true;
+
     # Hack: we don't want to kill systemd-nspawn, since we call
     # "machinectl poweroff" in preStop to shut down the
     # container cleanly. But systemd requires sending a signal
@@ -605,7 +609,7 @@ in
               { config =
                   { config, pkgs, ... }:
                   { services.postgresql.enable = true;
-                    services.postgresql.package = pkgs.postgresql96;
+                    services.postgresql.package = pkgs.postgresql_9_6;
 
                     system.stateVersion = "17.03";
                   };
@@ -656,6 +660,8 @@ in
       serviceConfig = serviceDirectives dummyConfig;
     };
   in {
+    systemd.targets."multi-user".wants = [ "machines.target" ];
+
     systemd.services = listToAttrs (filter (x: x.value != null) (
       # The generic container template used by imperative containers
       [{ name = "container@"; value = unit; }]
@@ -679,7 +685,7 @@ in
           } // (
           if config.autoStart then
             {
-              wantedBy = [ "multi-user.target" ];
+              wantedBy = [ "machines.target" ];
               wants = [ "network.target" ];
               after = [ "network.target" ];
               restartTriggers = [ config.path ];
diff --git a/nixos/modules/virtualisation/ec2-amis.nix b/nixos/modules/virtualisation/ec2-amis.nix
index 76facac39fc6..aaea06bb9a63 100644
--- a/nixos/modules/virtualisation/ec2-amis.nix
+++ b/nixos/modules/virtualisation/ec2-amis.nix
@@ -257,5 +257,22 @@ let self = {
   "18.03".sa-east-1.hvm-ebs = "ami-163e1f7a";
   "18.03".ap-south-1.hvm-ebs = "ami-6a390b05";
 
-  latest = self."18.03";
+  # 18.09.910.c15e342304a
+  "18.09".eu-west-1.hvm-ebs = "ami-0f412186fb8a0ec97";
+  "18.09".eu-west-2.hvm-ebs = "ami-0dada3805ce43c55e";
+  "18.09".eu-west-3.hvm-ebs = "ami-074df85565f2e02e2";
+  "18.09".eu-central-1.hvm-ebs = "ami-07c9b884e679df4f8";
+  "18.09".us-east-1.hvm-ebs = "ami-009c9c3f1af480ff3";
+  "18.09".us-east-2.hvm-ebs = "ami-08199961085ea8bc6";
+  "18.09".us-west-1.hvm-ebs = "ami-07aa7f56d612ddd38";
+  "18.09".us-west-2.hvm-ebs = "ami-01c84b7c368ac24d1";
+  "18.09".ca-central-1.hvm-ebs = "ami-04f66113f76198f6c";
+  "18.09".ap-southeast-1.hvm-ebs = "ami-0892c7e24ebf2194f";
+  "18.09".ap-southeast-2.hvm-ebs = "ami-010730f36424b0a2c";
+  "18.09".ap-northeast-1.hvm-ebs = "ami-0cdba8e998f076547";
+  "18.09".ap-northeast-2.hvm-ebs = "ami-0400a698e6a9f4a15";
+  "18.09".sa-east-1.hvm-ebs = "ami-0e4a8a47fd6db6112";
+  "18.09".ap-south-1.hvm-ebs = "ami-0880a678d3f555313";
+
+  latest = self."18.09";
 }; in self
diff --git a/nixos/modules/virtualisation/google-compute-image.nix b/nixos/modules/virtualisation/google-compute-image.nix
index 4c7cffcf4557..caaf6c0aa59d 100644
--- a/nixos/modules/virtualisation/google-compute-image.nix
+++ b/nixos/modules/virtualisation/google-compute-image.nix
@@ -97,8 +97,8 @@ in
       "google-instance-setup.service"
       "google-network-setup.service"
     ];
-    wantedBy = [ "multi-user.target" ];
     requires = ["network.target"];
+    wantedBy = ["multi-user.target"];
     path = with pkgs; [ shadow ];
     serviceConfig = {
       Type = "simple";
@@ -113,8 +113,8 @@ in
       "google-instance-setup.service"
       "google-network-setup.service"
     ];
-    requires = [ "network.target" ];
-    wantedBy = [ "multi-user.target" ];
+    requires = ["network.target"];
+    wantedBy = ["multi-user.target"];
     serviceConfig = {
       Type = "simple";
       ExecStart = "${gce}/bin/google_clock_skew_daemon --debug";
@@ -123,7 +123,7 @@ in
 
   systemd.services.google-instance-setup = {
     description = "Google Compute Engine Instance Setup";
-    after = ["fs.target" "network-online.target" "network.target" "rsyslog.service"];
+    after = ["local-fs.target" "network-online.target" "network.target" "rsyslog.service"];
     before = ["sshd.service"];
     wants = ["local-fs.target" "network-online.target" "network.target"];
     wantedBy = [ "sshd.service" "multi-user.target" ];
@@ -134,15 +134,17 @@ in
     };
   };
 
-  systemd.services.google-ip-forwarding-daemon = {
-    description = "Google Compute Engine IP Forwarding Daemon";
-    after = ["network.target" "google-instance-setup.service" "google-network-setup.service"];
+  systemd.services.google-network-daemon = {
+    description = "Google Compute Engine Network Daemon";
+    after = ["local-fs.target" "network-online.target" "network.target" "rsyslog.service" "google-instance-setup.service"];
+    wants = ["local-fs.target" "network-online.target" "network.target"];
     requires = ["network.target"];
+    partOf = ["network.target"];
     wantedBy = [ "multi-user.target" ];
     path = with pkgs; [ iproute ];
     serviceConfig = {
-      Type = "simple";
-      ExecStart = "${gce}/bin/google_ip_forwarding_daemon --debug";
+      ExecStart = "${gce}/bin/google_network_daemon --debug";
+      Type = "oneshot";
     };
   };
 
@@ -153,8 +155,9 @@ in
       "network-online.target"
       "network.target"
       "rsyslog.service"
+      "systemd-resolved.service"
       "google-instance-setup.service"
-      "google-network-setup.service"
+      "google-network-daemon.service"
     ];
     wants = [ "local-fs.target" "network-online.target" "network.target"];
     wantedBy = [ "multi-user.target" ];
@@ -167,23 +170,6 @@ in
     };
   };
 
-  systemd.services.google-network-setup = {
-    description = "Google Compute Engine Network Setup";
-    after = [
-      "local-fs.target"
-      "network-online.target"
-      "network.target"
-      "rsyslog.service"
-    ];
-    wants = [ "local-fs.target" "network-online.target" "network.target"];
-    wantedBy = [ "multi-user.target" ];
-    serviceConfig = {
-      ExecStart = "${gce}/bin/google_network_setup --debug";
-      KillMode = "process";
-      Type = "oneshot";
-    };
-  };
-
   systemd.services.google-startup-scripts = {
     description = "Google Compute Engine Startup Scripts";
     after = [
@@ -192,9 +178,9 @@ in
       "network.target"
       "rsyslog.service"
       "google-instance-setup.service"
-      "google-network-setup.service"
+      "google-network-daemon.service"
     ];
-    wants = [ "local-fs.target" "network-online.target" "network.target"];
+    wants = ["local-fs.target" "network-online.target" "network.target"];
     wantedBy = [ "multi-user.target" ];
     serviceConfig = {
       ExecStart = "${gce}/bin/google_metadata_script_runner --debug --script-type startup";
diff --git a/nixos/modules/virtualisation/hyperv-guest.nix b/nixos/modules/virtualisation/hyperv-guest.nix
index ecd2a8117710..0f1f052880c5 100644
--- a/nixos/modules/virtualisation/hyperv-guest.nix
+++ b/nixos/modules/virtualisation/hyperv-guest.nix
@@ -9,20 +9,47 @@ in {
   options = {
     virtualisation.hypervGuest = {
       enable = mkEnableOption "Hyper-V Guest Support";
+
+      videoMode = mkOption {
+        type = types.str;
+        default = "1152x864";
+        example = "1024x768";
+        description = ''
+          Resolution at which to initialize the video adapter.
+
+          Supports screen resolution up to Full HD 1920x1080 with 32 bit color
+          on Windows Server 2012, and 1600x1200 with 16 bit color on Windows
+          Server 2008 R2 or earlier.
+        '';
+      };
     };
   };
 
   config = mkIf cfg.enable {
+    boot = {
+      initrd.kernelModules = [
+        "hv_balloon" "hv_netvsc" "hv_storvsc" "hv_utils" "hv_vmbus"
+      ];
+
+      kernelParams = [
+        "video=hyperv_fb:${cfg.videoMode}"
+      ];
+    };
+
     environment.systemPackages = [ config.boot.kernelPackages.hyperv-daemons.bin ];
 
     security.rngd.enable = false;
 
-    # enable hotadding memory
+    # enable hotadding cpu/memory
     services.udev.packages = lib.singleton (pkgs.writeTextFile {
-      name = "hyperv-memory-hotadd-udev-rules";
-      destination = "/etc/udev/rules.d/99-hyperv-memory-hotadd.rules";
+      name = "hyperv-cpu-and-memory-hotadd-udev-rules";
+      destination = "/etc/udev/rules.d/99-hyperv-cpu-and-memory-hotadd.rules";
       text = ''
-        ACTION="add", SUBSYSTEM=="memory", ATTR{state}="online"
+        # Memory hotadd
+        SUBSYSTEM=="memory", ACTION=="add", DEVPATH=="/devices/system/memory/memory[0-9]*", TEST=="state", ATTR{state}="online"
+
+        # CPU hotadd
+        SUBSYSTEM=="cpu", ACTION=="add", DEVPATH=="/devices/system/cpu/cpu[0-9]*", TEST=="online", ATTR{online}="1"
       '';
     });
 
diff --git a/nixos/modules/virtualisation/kvmgt.nix b/nixos/modules/virtualisation/kvmgt.nix
index fc0bedb68bd0..132815a0ad63 100644
--- a/nixos/modules/virtualisation/kvmgt.nix
+++ b/nixos/modules/virtualisation/kvmgt.nix
@@ -50,11 +50,17 @@ in {
       nameValuePair "kvmgt-${name}" {
         description = "KVMGT VGPU ${name}";
         serviceConfig = {
-          Type = "oneshot";
+          Type = "forking";
           RemainAfterExit = true;
+          Restart = "on-failure";
+          RestartSec = 5;
           ExecStart = "${pkgs.runtimeShell} -c 'echo ${value.uuid} > /sys/bus/pci/devices/${cfg.device}/mdev_supported_types/${name}/create'";
           ExecStop = "${pkgs.runtimeShell} -c 'echo 1 > /sys/bus/pci/devices/${cfg.device}/${value.uuid}/remove'";
         };
+        unitConfig = {
+          StartLimitBurst = 5;
+          StartLimitIntervalSec = 30;
+        };
         wantedBy = [ "multi-user.target" ];
       }
     ) cfg.vgpus;
diff --git a/nixos/modules/virtualisation/libvirtd.nix b/nixos/modules/virtualisation/libvirtd.nix
index 3e38662f5b0f..f4d7af1664af 100644
--- a/nixos/modules/virtualisation/libvirtd.nix
+++ b/nixos/modules/virtualisation/libvirtd.nix
@@ -196,6 +196,8 @@ in {
       wantedBy = [ "multi-user.target" ];
       path = with pkgs; [ coreutils libvirt gawk ];
       restartIfChanged = false;
+
+      environment.ON_SHUTDOWN = "${cfg.onShutdown}";
     };
 
     systemd.sockets.virtlogd = {
diff --git a/nixos/modules/virtualisation/qemu-guest-agent.nix b/nixos/modules/virtualisation/qemu-guest-agent.nix
index e0d2b3dc509d..665224e35d8c 100644
--- a/nixos/modules/virtualisation/qemu-guest-agent.nix
+++ b/nixos/modules/virtualisation/qemu-guest-agent.nix
@@ -25,7 +25,7 @@ in {
       systemd.services.qemu-guest-agent = {
         description = "Run the QEMU Guest Agent";
         serviceConfig = {
-          ExecStart = "${pkgs.kvm.ga}/bin/qemu-ga";
+          ExecStart = "${pkgs.qemu.ga}/bin/qemu-ga";
           Restart = "always";
           RestartSec = 0;
         };
diff --git a/nixos/modules/virtualisation/virtualbox-image.nix b/nixos/modules/virtualisation/virtualbox-image.nix
index 60048911658c..037c0d2f0d82 100644
--- a/nixos/modules/virtualisation/virtualbox-image.nix
+++ b/nixos/modules/virtualisation/virtualbox-image.nix
@@ -12,7 +12,7 @@ in {
     virtualbox = {
       baseImageSize = mkOption {
         type = types.int;
-        default = 10 * 1024;
+        default = 50 * 1024;
         description = ''
           The size of the VirtualBox base image in MiB.
         '';
@@ -61,7 +61,7 @@ in {
           export HOME=$PWD
           export PATH=${pkgs.virtualbox}/bin:$PATH
 
-          echo "creating VirtualBox pass-through disk wrapper (no copying invovled)..."
+          echo "creating VirtualBox pass-through disk wrapper (no copying involved)..."
           VBoxManage internalcommands createrawvmdk -filename disk.vmdk -rawdisk $diskImage
 
           echo "creating VirtualBox VM..."
@@ -72,9 +72,9 @@ in {
             --memory ${toString cfg.memorySize} --acpi on --vram 32 \
             ${optionalString (pkgs.stdenv.hostPlatform.system == "i686-linux") "--pae on"} \
             --nictype1 virtio --nic1 nat \
-            --audiocontroller ac97 --audio alsa \
+            --audiocontroller ac97 --audio alsa --audioout on \
             --rtcuseutc on \
-            --usb on --mouse usbtablet
+            --usb on --usbehci on --mouse usbtablet
           VBoxManage storagectl "$vmName" --name SATA --add sata --portcount 4 --bootable on --hostiocache on
           VBoxManage storageattach "$vmName" --storagectl SATA --port 0 --device 0 --type hdd \
             --medium disk.vmdk
@@ -82,7 +82,7 @@ in {
           echo "exporting VirtualBox VM..."
           mkdir -p $out
           fn="$out/${cfg.vmFileName}"
-          VBoxManage export "$vmName" --output "$fn"
+          VBoxManage export "$vmName" --output "$fn" --options manifest
 
           rm -v $diskImage
 
diff --git a/nixos/modules/virtualisation/xe-guest-utilities.nix b/nixos/modules/virtualisation/xe-guest-utilities.nix
index d703353858c0..675cf9297371 100644
--- a/nixos/modules/virtualisation/xe-guest-utilities.nix
+++ b/nixos/modules/virtualisation/xe-guest-utilities.nix
@@ -5,7 +5,7 @@ let
 in {
   options = {
     services.xe-guest-utilities = {
-      enable = mkEnableOption "Whether to enable the Xen guest utilities daemon.";
+      enable = mkEnableOption "the Xen guest utilities daemon";
     };
   };
   config = mkIf cfg.enable {
diff --git a/nixos/release.nix b/nixos/release.nix
index 1013053b5b3b..51505d6aab9d 100644
--- a/nixos/release.nix
+++ b/nixos/release.nix
@@ -1,10 +1,12 @@
-{ nixpkgs ? { outPath = (import ../lib).cleanSource ./..; revCount = 130979; shortRev = "gfedcba"; }
+with import ../lib;
+
+{ nixpkgs ? { outPath = cleanSource ./..; revCount = 130979; shortRev = "gfedcba"; }
 , stableBranch ? false
 , supportedSystems ? [ "x86_64-linux" "aarch64-linux" ]
+, configuration ? {}
 }:
 
 with import ../pkgs/top-level/release-lib.nix { inherit supportedSystems; };
-with import ../lib;
 
 let
 
@@ -51,7 +53,7 @@ let
 
     hydraJob ((import lib/eval-config.nix {
       inherit system;
-      modules = [ module versionModule { isoImage.isoBaseName = "nixos-${type}"; } ];
+      modules = [ configuration module versionModule { isoImage.isoBaseName = "nixos-${type}"; } ];
     }).config.system.build.isoImage);
 
 
@@ -62,7 +64,7 @@ let
 
     hydraJob ((import lib/eval-config.nix {
       inherit system;
-      modules = [ module versionModule ];
+      modules = [ configuration module versionModule ];
     }).config.system.build.sdImage);
 
 
@@ -75,7 +77,7 @@ let
 
       config = (import lib/eval-config.nix {
         inherit system;
-        modules = [ module versionModule ];
+        modules = [ configuration module versionModule ];
       }).config;
 
       tarball = config.system.build.tarball;
@@ -95,16 +97,19 @@ let
 
   buildFromConfig = module: sel: forAllSystems (system: hydraJob (sel (import ./lib/eval-config.nix {
     inherit system;
-    modules = [ module versionModule ] ++ singleton
+    modules = [ configuration module versionModule ] ++ singleton
       ({ ... }:
       { fileSystems."/".device  = mkDefault "/dev/sda1";
         boot.loader.grub.device = mkDefault "/dev/sda";
       });
   }).config));
 
-  makeNetboot = config:
+  makeNetboot = { module, system, ... }:
     let
-      configEvaled = import lib/eval-config.nix config;
+      configEvaled = import lib/eval-config.nix {
+        inherit system;
+        modules = [ module versionModule ];
+      };
       build = configEvaled.config.system.build;
       kernelTarget = configEvaled.pkgs.stdenv.hostPlatform.platform.kernelTarget;
     in
@@ -128,7 +133,8 @@ in rec {
 
   channel = import lib/make-channel.nix { inherit pkgs nixpkgs version versionSuffix; };
 
-  manual = buildFromConfig ({ ... }: { }) (config: config.system.build.manual.manual);
+  manualHTML = buildFromConfig ({ ... }: { }) (config: config.system.build.manual.manualHTML);
+  manual = manualHTML; # TODO(@oxij): remove eventually
   manualEpub = (buildFromConfig ({ ... }: { }) (config: config.system.build.manual.manualEpub));
   manpages = buildFromConfig ({ ... }: { }) (config: config.system.build.manual.manpages);
   manualGeneratedSources = buildFromConfig ({ ... }: { }) (config: config.system.build.manual.generatedSources);
@@ -139,11 +145,8 @@ in rec {
   initialRamdisk = buildFromConfig ({ ... }: { }) (config: config.system.build.initialRamdisk);
 
   netboot = forMatchingSystems [ "x86_64-linux" "aarch64-linux" ] (system: makeNetboot {
+    module = ./modules/installer/netboot/netboot-minimal.nix;
     inherit system;
-    modules = [
-      ./modules/installer/netboot/netboot-minimal.nix
-      versionModule
-    ];
   });
 
   iso_minimal = forAllSystems (system: makeIso {
@@ -247,13 +250,14 @@ in rec {
   tests.acme = callTest tests/acme.nix {};
   tests.avahi = callTest tests/avahi.nix {};
   tests.beegfs = callTest tests/beegfs.nix {};
+  tests.upnp = callTest tests/upnp.nix {};
   tests.bittorrent = callTest tests/bittorrent.nix {};
   tests.bind = callTest tests/bind.nix {};
   #tests.blivet = callTest tests/blivet.nix {};   # broken since 2017-07024
   tests.boot = callSubTests tests/boot.nix {};
   tests.boot-stage1 = callTest tests/boot-stage1.nix {};
   tests.borgbackup = callTest tests/borgbackup.nix {};
-  tests.buildbot = callTest tests/buildbot.nix {};
+  tests.buildbot = callSubTests tests/buildbot.nix {};
   tests.cadvisor = callTestOnMatchingSystems ["x86_64-linux"] tests/cadvisor.nix {};
   tests.ceph = callTestOnMatchingSystems ["x86_64-linux"] tests/ceph.nix {};
   tests.certmgr = callSubTests tests/certmgr.nix {};
@@ -261,6 +265,7 @@ in rec {
   tests.chromium = (callSubTestsOnMatchingSystems ["x86_64-linux"] tests/chromium.nix {}).stable or {};
   tests.cjdns = callTest tests/cjdns.nix {};
   tests.cloud-init = callTest tests/cloud-init.nix {};
+  tests.codimd = callTest tests/codimd.nix {};
   tests.containers-ipv4 = callTest tests/containers-ipv4.nix {};
   tests.containers-ipv6 = callTest tests/containers-ipv6.nix {};
   tests.containers-bridge = callTest tests/containers-bridge.nix {};
@@ -284,7 +289,8 @@ in rec {
   tests.ecryptfs = callTest tests/ecryptfs.nix {};
   tests.etcd = callTestOnMatchingSystems ["x86_64-linux"] tests/etcd.nix {};
   tests.ec2-nixops = (callSubTestsOnMatchingSystems ["x86_64-linux"] tests/ec2.nix {}).boot-ec2-nixops or {};
-  tests.ec2-config = (callSubTestsOnMatchingSystems ["x86_64-linux"] tests/ec2.nix {}).boot-ec2-config or {};
+  # ec2-config doesn't work in a sandbox as the simulated ec2 instance needs network access
+  #tests.ec2-config = (callSubTestsOnMatchingSystems ["x86_64-linux"] tests/ec2.nix {}).boot-ec2-config or {};
   tests.elk = callSubTestsOnMatchingSystems ["x86_64-linux"] tests/elk.nix {};
   tests.env = callTest tests/env.nix {};
   tests.ferm = callTest tests/ferm.nix {};
@@ -327,7 +333,6 @@ in rec {
   tests.keymap = callSubTests tests/keymap.nix {};
   tests.initrdNetwork = callTest tests/initrd-network.nix {};
   tests.kafka = callSubTests tests/kafka.nix {};
-  tests.kernel-copperhead = callTest tests/kernel-copperhead.nix {};
   tests.kernel-latest = callTest tests/kernel-latest.nix {};
   tests.kernel-lts = callTest tests/kernel-lts.nix {};
   tests.kubernetes.dns = callSubTestsOnMatchingSystems ["x86_64-linux"] tests/kubernetes/dns.nix {};
@@ -358,6 +363,7 @@ in rec {
   tests.netdata = callTest tests/netdata.nix { };
   tests.networking.networkd = callSubTests tests/networking.nix { networkd = true; };
   tests.networking.scripted = callSubTests tests/networking.nix { networkd = false; };
+  tests.nextcloud = callSubTests tests/nextcloud { };
   # TODO: put in networking.nix after the test becomes more complete
   tests.networkingProxy = callTest tests/networking-proxy.nix {};
   tests.nexus = callTest tests/nexus.nix { };
@@ -380,17 +386,20 @@ in rec {
   tests.pgmanage = callTest tests/pgmanage.nix {};
   tests.postgis = callTest tests/postgis.nix {};
   tests.powerdns = callTest tests/powerdns.nix {};
-  #tests.pgjwt = callTest tests/pgjwt.nix {};
+  tests.pgjwt = callTest tests/pgjwt.nix {};
   tests.predictable-interface-names = callSubTests tests/predictable-interface-names.nix {};
   tests.printing = callTest tests/printing.nix {};
   tests.prometheus = callTest tests/prometheus.nix {};
+  tests.prometheus-exporters = callTest tests/prometheus-exporters.nix {};
   tests.prosody = callTest tests/prosody.nix {};
   tests.proxy = callTest tests/proxy.nix {};
   tests.quagga = callTest tests/quagga.nix {};
   tests.quake3 = callTest tests/quake3.nix {};
   tests.rabbitmq = callTest tests/rabbitmq.nix {};
   tests.radicale = callTest tests/radicale.nix {};
+  tests.redmine = callTest tests/redmine.nix {};
   tests.rspamd = callSubTests tests/rspamd.nix {};
+  tests.rsyslogd = callSubTests tests/rsyslogd.nix {};
   tests.runInMachine = callTest tests/run-in-machine.nix {};
   tests.rxe = callTest tests/rxe.nix {};
   tests.samba = callTest tests/samba.nix {};
@@ -400,7 +409,7 @@ in rec {
   tests.slurm = callTest tests/slurm.nix {};
   tests.smokeping = callTest tests/smokeping.nix {};
   tests.snapper = callTest tests/snapper.nix {};
-  tests.statsd = callTest tests/statsd.nix {};
+  #tests.statsd = callTest tests/statsd.nix {}; # statsd is broken: #45946
   tests.strongswan-swanctl = callTest tests/strongswan-swanctl.nix {};
   tests.sudo = callTest tests/sudo.nix {};
   tests.systemd = callTest tests/systemd.nix {};
@@ -459,7 +468,7 @@ in rec {
       { services.httpd.enable = true;
         services.httpd.adminAddr = "foo@example.org";
         services.postgresql.enable = true;
-        services.postgresql.package = pkgs.postgresql93;
+        services.postgresql.package = pkgs.postgresql_9_3;
         environment.systemPackages = [ pkgs.php ];
       });
   };
diff --git a/nixos/tests/acme.nix b/nixos/tests/acme.nix
index c7fd4910e072..4669a092433e 100644
--- a/nixos/tests/acme.nix
+++ b/nixos/tests/acme.nix
@@ -1,32 +1,5 @@
 let
-  commonConfig = { lib, nodes, ... }: {
-    networking.nameservers = [
-      nodes.letsencrypt.config.networking.primaryIPAddress
-    ];
-
-    nixpkgs.overlays = lib.singleton (self: super: {
-      cacert = super.cacert.overrideDerivation (drv: {
-        installPhase = (drv.installPhase or "") + ''
-          cat "${nodes.letsencrypt.config.test-support.letsencrypt.caCert}" \
-            >> "$out/etc/ssl/certs/ca-bundle.crt"
-        '';
-      });
-
-      # Override certifi so that it accepts fake certificate for Let's Encrypt
-      # Need to override the attribute used by simp_le, which is python3Packages
-      python3Packages = (super.python3.override {
-        packageOverrides = lib.const (pysuper: {
-          certifi = pysuper.certifi.overridePythonAttrs (attrs: {
-            postPatch = (attrs.postPatch or "") + ''
-              cat "${self.cacert}/etc/ssl/certs/ca-bundle.crt" \
-                > certifi/cacert.pem
-            '';
-          });
-        });
-      }).pkgs;
-    });
-  };
-
+  commonConfig = ./common/letsencrypt/common.nix;
 in import ./make-test.nix {
   name = "acme";
 
diff --git a/nixos/tests/atd.nix b/nixos/tests/atd.nix
index 9f367d4c1d2a..25db72799241 100644
--- a/nixos/tests/atd.nix
+++ b/nixos/tests/atd.nix
@@ -16,6 +16,7 @@ import ./make-test.nix ({ pkgs, ... }:
   testScript = ''
     startAll;
 
+    $machine->waitForUnit('atd.service'); # wait for atd to start
     $machine->fail("test -f ~root/at-1");
     $machine->fail("test -f ~alice/at-1");
 
diff --git a/nixos/tests/bittorrent.nix b/nixos/tests/bittorrent.nix
index 609b1ff7a83a..8977be9b859f 100644
--- a/nixos/tests/bittorrent.nix
+++ b/nixos/tests/bittorrent.nix
@@ -13,57 +13,95 @@ let
   # Some random file to serve.
   file = pkgs.hello.src;
 
-  miniupnpdConf = nodes: pkgs.writeText "miniupnpd.conf"
-    ''
-      ext_ifname=eth1
-      listening_ip=${(pkgs.lib.head nodes.router.config.networking.interfaces.eth2.ipv4.addresses).address}/24
-      allow 1024-65535 192.168.2.0/24 1024-65535
-    '';
-
+  internalRouterAddress = "192.168.3.1";
+  internalClient1Address = "192.168.3.2";
+  externalRouterAddress = "80.100.100.1";
+  externalClient2Address = "80.100.100.2";
+  externalTrackerAddress = "80.100.100.3";
 in
 
 {
   name = "bittorrent";
   meta = with pkgs.stdenv.lib.maintainers; {
-    maintainers = [ domenkozar eelco chaoflow rob wkennington ];
+    maintainers = [ domenkozar eelco chaoflow rob wkennington bobvanderlinden ];
   };
 
   nodes =
     { tracker =
         { pkgs, ... }:
-        { environment.systemPackages = [ pkgs.transmission pkgs.opentracker ];
+        { environment.systemPackages = [ pkgs.transmission ];
+
+          virtualisation.vlans = [ 1 ];
+          networking.interfaces.eth1.ipv4.addresses = [
+            { address = externalTrackerAddress; prefixLength = 24; }
+          ];
 
           # We need Apache on the tracker to serve the torrents.
           services.httpd.enable = true;
           services.httpd.adminAddr = "foo@example.org";
           services.httpd.documentRoot = "/tmp";
 
-          networking.firewall.enable = false; # FIXME: figure out what ports we actually need
+          networking.firewall.enable = false;
+
+          services.opentracker.enable = true;
+
+          services.transmission.enable = true;
+          services.transmission.settings.dht-enabled = false;
+          services.transmission.settings.port-forwaring-enabled = false;
         };
 
       router =
-        { pkgs, ... }:
-        { environment.systemPackages = [ pkgs.miniupnpd ];
-          virtualisation.vlans = [ 1 2 ];
+        { pkgs, nodes, ... }:
+        { virtualisation.vlans = [ 1 2 ];
           networking.nat.enable = true;
           networking.nat.internalInterfaces = [ "eth2" ];
           networking.nat.externalInterface = "eth1";
-          networking.firewall.enable = false;
+          networking.firewall.enable = true;
+          networking.firewall.trustedInterfaces = [ "eth2" ];
+          networking.interfaces.eth0.ipv4.addresses = [];
+          networking.interfaces.eth1.ipv4.addresses = [
+            { address = externalRouterAddress; prefixLength = 24; }
+          ];
+          networking.interfaces.eth2.ipv4.addresses = [
+            { address = internalRouterAddress; prefixLength = 24; }
+          ];
+          services.miniupnpd = {
+            enable = true;
+            externalInterface = "eth1";
+            internalIPs = [ "eth2" ];
+            appendConfig = ''
+              ext_ip=${externalRouterAddress}
+            '';
+          };
         };
 
       client1 =
         { pkgs, nodes, ... }:
-        { environment.systemPackages = [ pkgs.transmission ];
+        { environment.systemPackages = [ pkgs.transmission pkgs.miniupnpc ];
           virtualisation.vlans = [ 2 ];
-          networking.defaultGateway =
-            (pkgs.lib.head nodes.router.config.networking.interfaces.eth2.ipv4.addresses).address;
+          networking.interfaces.eth0.ipv4.addresses = [];
+          networking.interfaces.eth1.ipv4.addresses = [
+            { address = internalClient1Address; prefixLength = 24; }
+          ];
+          networking.defaultGateway = internalRouterAddress;
           networking.firewall.enable = false;
+          services.transmission.enable = true;
+          services.transmission.settings.dht-enabled = false;
+          services.transmission.settings.message-level = 3;
         };
 
       client2 =
         { pkgs, ... }:
         { environment.systemPackages = [ pkgs.transmission ];
+          virtualisation.vlans = [ 1 ];
+          networking.interfaces.eth0.ipv4.addresses = [];
+          networking.interfaces.eth1.ipv4.addresses = [
+            { address = externalClient2Address; prefixLength = 24; }
+          ];
           networking.firewall.enable = false;
+          services.transmission.enable = true;
+          services.transmission.settings.dht-enabled = false;
+          services.transmission.settings.port-forwaring-enabled = false;
         };
     };
 
@@ -72,43 +110,38 @@ in
     ''
       startAll;
 
-      # Enable NAT on the router and start miniupnpd.
-      $router->waitForUnit("nat");
-      $router->succeed(
-          "iptables -w -t nat -N MINIUPNPD",
-          "iptables -w -t nat -A PREROUTING -i eth1 -j MINIUPNPD",
-          "echo 1 > /proc/sys/net/ipv4/ip_forward",
-          "miniupnpd -f ${miniupnpdConf nodes}"
-      );
+      # Wait for network and miniupnpd.
+      $router->waitForUnit("network-online.target");
+      $router->waitForUnit("miniupnpd");
 
       # Create the torrent.
       $tracker->succeed("mkdir /tmp/data");
       $tracker->succeed("cp ${file} /tmp/data/test.tar.bz2");
-      $tracker->succeed("transmission-create /tmp/data/test.tar.bz2 -p -t http://${(pkgs.lib.head nodes.tracker.config.networking.interfaces.eth1.ipv4.addresses).address}:6969/announce -o /tmp/test.torrent");
+      $tracker->succeed("transmission-create /tmp/data/test.tar.bz2 --private --tracker http://${externalTrackerAddress}:6969/announce --outfile /tmp/test.torrent");
       $tracker->succeed("chmod 644 /tmp/test.torrent");
 
       # Start the tracker.  !!! use a less crappy tracker
-      $tracker->waitForUnit("network.target");
-      $tracker->succeed("opentracker -p 6969 >&2 &");
+      $tracker->waitForUnit("network-online.target");
+      $tracker->waitForUnit("opentracker.service");
       $tracker->waitForOpenPort(6969);
 
       # Start the initial seeder.
-      my $pid = $tracker->succeed("transmission-cli /tmp/test.torrent -M -w /tmp/data >&2 & echo \$!");
+      $tracker->succeed("transmission-remote --add /tmp/test.torrent --no-portmap --no-dht --download-dir /tmp/data");
 
       # Now we should be able to download from the client behind the NAT.
       $tracker->waitForUnit("httpd");
-      $client1->waitForUnit("network.target");
-      $client1->succeed("transmission-cli http://tracker/test.torrent -w /tmp >&2 &");
+      $client1->waitForUnit("network-online.target");
+      $client1->succeed("transmission-remote --add http://${externalTrackerAddress}/test.torrent --download-dir /tmp >&2 &");
       $client1->waitForFile("/tmp/test.tar.bz2");
       $client1->succeed("cmp /tmp/test.tar.bz2 ${file}");
 
       # Bring down the initial seeder.
-      $tracker->succeed("kill -9 $pid");
+      # $tracker->stopJob("transmission");
 
       # Now download from the second client.  This can only succeed if
       # the first client created a NAT hole in the router.
-      $client2->waitForUnit("network.target");
-      $client2->succeed("transmission-cli http://tracker/test.torrent -M -w /tmp >&2 &");
+      $client2->waitForUnit("network-online.target");
+      $client2->succeed("transmission-remote --add http://${externalTrackerAddress}/test.torrent --no-portmap --no-dht --download-dir /tmp >&2 &");
       $client2->waitForFile("/tmp/test.tar.bz2");
       $client2->succeed("cmp /tmp/test.tar.bz2 ${file}");
     '';
diff --git a/nixos/tests/buildbot.nix b/nixos/tests/buildbot.nix
index cf408dc7fec9..399fd39005e2 100644
--- a/nixos/tests/buildbot.nix
+++ b/nixos/tests/buildbot.nix
@@ -1,111 +1,117 @@
-# Test ensures buildbot master comes up correctly and workers can connect
-
-import ./make-test.nix ({ pkgs, ... } : {
-  name = "buildbot";
-
-  nodes = {
-    bbmaster = { pkgs, ... }: {
-      services.buildbot-master = {
-        enable = true;
-        package = pkgs.buildbot-full;
-
-        # NOTE: use fake repo due to no internet in hydra ci
-        factorySteps = [
-          "steps.Git(repourl='git://gitrepo/fakerepo.git', mode='incremental')"
-          "steps.ShellCommand(command=['bash', 'fakerepo.sh'])"
-        ];
-        changeSource = [
-          "changes.GitPoller('git://gitrepo/fakerepo.git', workdir='gitpoller-workdir', branch='master', pollinterval=300)"
-        ];
+{ system ? builtins.currentSystem }:
+
+with import ../lib/testing.nix { inherit system; };
+
+let
+  # Test ensures buildbot master comes up correctly and workers can connect
+  mkBuildbotTest = python: makeTest {
+    name = "buildbot";
+
+    nodes = {
+      bbmaster = { pkgs, ... }: {
+        services.buildbot-master = {
+          enable = true;
+          package = python.pkgs.buildbot-full;
+
+          # NOTE: use fake repo due to no internet in hydra ci
+          factorySteps = [
+            "steps.Git(repourl='git://gitrepo/fakerepo.git', mode='incremental')"
+            "steps.ShellCommand(command=['bash', 'fakerepo.sh'])"
+          ];
+          changeSource = [
+            "changes.GitPoller('git://gitrepo/fakerepo.git', workdir='gitpoller-workdir', branch='master', pollinterval=300)"
+          ];
+        };
+        networking.firewall.allowedTCPPorts = [ 8010 8011 9989 ];
+        environment.systemPackages = with pkgs; [ git python.pkgs.buildbot-full ];
       };
-      networking.firewall.allowedTCPPorts = [ 8010 8011 9989 ];
-      environment.systemPackages = with pkgs; [ git buildbot-full ];
-    };
 
-    bbworker = { pkgs, ... }: {
-      services.buildbot-worker = {
-        enable = true;
-        masterUrl = "bbmaster:9989";
+      bbworker = { pkgs, ... }: {
+        services.buildbot-worker = {
+          enable = true;
+          masterUrl = "bbmaster:9989";
+        };
+        environment.systemPackages = with pkgs; [ git python.pkgs.buildbot-worker ];
       };
-      environment.systemPackages = with pkgs; [ git buildbot-worker ];
-    };
 
-    gitrepo = { pkgs, ... }: {
-      services.openssh.enable = true;
-      networking.firewall.allowedTCPPorts = [ 22 9418 ];
-      environment.systemPackages = with pkgs; [ git ];
+      gitrepo = { pkgs, ... }: {
+        services.openssh.enable = true;
+        networking.firewall.allowedTCPPorts = [ 22 9418 ];
+        environment.systemPackages = with pkgs; [ git ];
+      };
     };
-  };
 
-  testScript = ''
-    #Start up and populate fake repo
-    $gitrepo->waitForUnit("multi-user.target");
-    print($gitrepo->execute(" \
-      git config --global user.name 'Nobody Fakeuser' && \
-      git config --global user.email 'nobody\@fakerepo.com' && \
-      rm -rvf /srv/repos/fakerepo.git /tmp/fakerepo && \
-      mkdir -pv /srv/repos/fakerepo ~/.ssh && \
-      ssh-keyscan -H gitrepo > ~/.ssh/known_hosts && \
-      cat ~/.ssh/known_hosts && \
-      cd /srv/repos/fakerepo && \
-      git init && \
-      echo -e '#!/bin/sh\necho fakerepo' > fakerepo.sh && \
-      cat fakerepo.sh && \
-      touch .git/git-daemon-export-ok && \
-      git add fakerepo.sh .git/git-daemon-export-ok && \
-      git commit -m fakerepo && \
-      git daemon --verbose --export-all --base-path=/srv/repos --reuseaddr & \
-    "));
-
-    # Test gitrepo
-    $bbmaster->waitForUnit("network-online.target");
-    #$bbmaster->execute("nc -z gitrepo 9418");
-    print($bbmaster->execute(" \
-      rm -rfv /tmp/fakerepo && \
-      git clone git://gitrepo/fakerepo /tmp/fakerepo && \
-      pwd && \
-      ls -la && \
-      ls -la /tmp/fakerepo \
-    "));
-
-    # Test start master and connect worker
-    $bbmaster->waitForUnit("buildbot-master.service");
-    $bbmaster->waitUntilSucceeds("curl -s --head http://bbmaster:8010") =~ /200 OK/;
-    $bbworker->waitForUnit("network-online.target");
-    $bbworker->execute("nc -z bbmaster 8010");
-    $bbworker->execute("nc -z bbmaster 9989");
-    $bbworker->waitForUnit("buildbot-worker.service");
-    print($bbworker->execute("ls -la /home/bbworker/worker"));
-
-
-    # Test stop buildbot master and worker
-    print($bbmaster->execute(" \
-      systemctl -l --no-pager status buildbot-master && \
-      systemctl stop buildbot-master \
-    "));
-    $bbworker->fail("nc -z bbmaster 8010");
-    $bbworker->fail("nc -z bbmaster 9989");
-    print($bbworker->execute(" \
-      systemctl -l --no-pager status buildbot-worker && \
-      systemctl stop buildbot-worker && \
-      ls -la /home/bbworker/worker \
-    "));
-
-
-    # Test buildbot daemon mode
-    # NOTE: daemon mode tests disabled due to broken PYTHONPATH child inheritence
-    #
-    #$bbmaster->execute("buildbot create-master /tmp");
-    #$bbmaster->execute("mv -fv /tmp/master.cfg.sample /tmp/master.cfg");
-    #$bbmaster->execute("sed -i 's/8010/8011/' /tmp/master.cfg");
-    #$bbmaster->execute("buildbot start /tmp");
-    #$bbworker->execute("nc -z bbmaster 8011");
-    #$bbworker->waitUntilSucceeds("curl -s --head http://bbmaster:8011") =~ /200 OK/;
-    #$bbmaster->execute("buildbot stop /tmp");
-    #$bbworker->fail("nc -z bbmaster 8011");
-
-  '';
-
-  meta.maintainers = with pkgs.stdenv.lib.maintainers; [ nand0p ];
-
-})
+    testScript = ''
+      #Start up and populate fake repo
+      $gitrepo->waitForUnit("multi-user.target");
+      print($gitrepo->execute(" \
+        git config --global user.name 'Nobody Fakeuser' && \
+        git config --global user.email 'nobody\@fakerepo.com' && \
+        rm -rvf /srv/repos/fakerepo.git /tmp/fakerepo && \
+        mkdir -pv /srv/repos/fakerepo ~/.ssh && \
+        ssh-keyscan -H gitrepo > ~/.ssh/known_hosts && \
+        cat ~/.ssh/known_hosts && \
+        cd /srv/repos/fakerepo && \
+        git init && \
+        echo -e '#!/bin/sh\necho fakerepo' > fakerepo.sh && \
+        cat fakerepo.sh && \
+        touch .git/git-daemon-export-ok && \
+        git add fakerepo.sh .git/git-daemon-export-ok && \
+        git commit -m fakerepo && \
+        git daemon --verbose --export-all --base-path=/srv/repos --reuseaddr & \
+      "));
+
+      # Test gitrepo
+      $bbmaster->waitForUnit("network-online.target");
+      #$bbmaster->execute("nc -z gitrepo 9418");
+      print($bbmaster->execute(" \
+        rm -rfv /tmp/fakerepo && \
+        git clone git://gitrepo/fakerepo /tmp/fakerepo && \
+        pwd && \
+        ls -la && \
+        ls -la /tmp/fakerepo \
+      "));
+
+      # Test start master and connect worker
+      $bbmaster->waitForUnit("buildbot-master.service");
+      $bbmaster->waitUntilSucceeds("curl -s --head http://bbmaster:8010") =~ /200 OK/;
+      $bbworker->waitForUnit("network-online.target");
+      $bbworker->execute("nc -z bbmaster 8010");
+      $bbworker->execute("nc -z bbmaster 9989");
+      $bbworker->waitForUnit("buildbot-worker.service");
+      print($bbworker->execute("ls -la /home/bbworker/worker"));
+
+
+      # Test stop buildbot master and worker
+      print($bbmaster->execute(" \
+        systemctl -l --no-pager status buildbot-master && \
+        systemctl stop buildbot-master \
+      "));
+      $bbworker->fail("nc -z bbmaster 8010");
+      $bbworker->fail("nc -z bbmaster 9989");
+      print($bbworker->execute(" \
+        systemctl -l --no-pager status buildbot-worker && \
+        systemctl stop buildbot-worker && \
+        ls -la /home/bbworker/worker \
+      "));
+
+
+      # Test buildbot daemon mode
+      $bbmaster->execute("buildbot create-master /tmp");
+      $bbmaster->execute("mv -fv /tmp/master.cfg.sample /tmp/master.cfg");
+      $bbmaster->execute("sed -i 's/8010/8011/' /tmp/master.cfg");
+      $bbmaster->execute("buildbot start /tmp");
+      $bbworker->execute("nc -z bbmaster 8011");
+      $bbworker->waitUntilSucceeds("curl -s --head http://bbmaster:8011") =~ /200 OK/;
+      $bbmaster->execute("buildbot stop /tmp");
+      $bbworker->fail("nc -z bbmaster 8011");
+
+    '';
+
+    meta.maintainers = with pkgs.stdenv.lib.maintainers; [ nand0p ];
+
+  };
+in {
+  python2 = mkBuildbotTest pkgs.python2;
+  python3 = mkBuildbotTest pkgs.python3;
+}
diff --git a/nixos/tests/ceph.nix b/nixos/tests/ceph.nix
index dd45f0157b01..7408029c460e 100644
--- a/nixos/tests/ceph.nix
+++ b/nixos/tests/ceph.nix
@@ -10,9 +10,8 @@ import ./make-test.nix ({pkgs, ...}: rec {
         emptyDiskImages = [ 20480 20480 ];
         vlans = [ 1 ];
       };
-      
+
       networking = {
-        firewall.allowPing = true;
         useDHCP = false;
         interfaces.eth1.ipv4.addresses = pkgs.lib.mkOverride 0 [
           { address = "192.168.1.1"; prefixLength = 24; }
@@ -54,7 +53,7 @@ import ./make-test.nix ({pkgs, ...}: rec {
       };
     };
   };
-  
+
   testScript = { ... }: ''
     startAll;
 
@@ -83,7 +82,7 @@ import ./make-test.nix ({pkgs, ...}: rec {
 
     # Can't check ceph status until a mon is up
     $aio->succeed("ceph -s | grep 'mon: 1 daemons'");
-          
+
     # Start the ceph-mgr daemon, it has no deps and hardly any setup
     $aio->mustSucceed(
       "ceph auth get-or-create mgr.aio mon 'allow profile mgr' osd 'allow *' mds 'allow *' > /var/lib/ceph/mgr/ceph-aio/keyring",
diff --git a/nixos/tests/chromium.nix b/nixos/tests/chromium.nix
index c341e83961a8..fcc55a59a216 100644
--- a/nixos/tests/chromium.nix
+++ b/nixos/tests/chromium.nix
@@ -12,8 +12,10 @@ with pkgs.lib;
 
 mapAttrs (channel: chromiumPkg: makeTest rec {
   name = "chromium-${channel}";
-  meta = with pkgs.stdenv.lib.maintainers; {
-    maintainers = [ aszlig ];
+  meta = {
+    maintainers = with maintainers; [ aszlig ];
+    # https://github.com/NixOS/hydra/issues/591#issuecomment-435125621
+    inherit (chromiumPkg.meta) timeout;
   };
 
   enableOCR = true;
diff --git a/nixos/tests/cjdns.nix b/nixos/tests/cjdns.nix
index ab5f8e0bcf3e..e03bb9882540 100644
--- a/nixos/tests/cjdns.nix
+++ b/nixos/tests/cjdns.nix
@@ -13,9 +13,6 @@ let
 
       # CJDNS output is incompatible with the XML log.
       systemd.services.cjdns.serviceConfig.StandardOutput = "null";
-      #networking.firewall.enable = true;
-      networking.firewall.allowPing = true;
-      #networking.firewall.rejectPackets = true;
     };
 
 in
diff --git a/nixos/tests/codimd.nix b/nixos/tests/codimd.nix
new file mode 100644
index 000000000000..562f6f24f999
--- /dev/null
+++ b/nixos/tests/codimd.nix
@@ -0,0 +1,54 @@
+import ./make-test.nix ({ pkgs, lib, ... }:
+{
+  name = "codimd";
+
+  meta = with lib.maintainers; {
+    maintainers = [ willibutz ];
+  };
+
+  nodes = {
+    codimdSqlite = { ... }: {
+      services = {
+        codimd = {
+          enable = true;
+          configuration.dbURL = "sqlite:///var/lib/codimd/codimd.db";
+        };
+      };
+    };
+
+    codimdPostgres = { ... }: {
+      systemd.services.codimd.after = [ "postgresql.service" ];
+      services = {
+        codimd = {
+          enable = true;
+          configuration.dbURL = "postgres://codimd:snakeoilpassword@localhost:5432/codimddb";
+        };
+        postgresql = {
+          enable = true;
+          initialScript = pkgs.writeText "pg-init-script.sql" ''
+            CREATE ROLE codimd LOGIN PASSWORD 'snakeoilpassword';
+            CREATE DATABASE codimddb OWNER codimd;
+          '';
+        };
+      };
+    };
+  };
+
+  testScript = ''
+    startAll();
+
+    subtest "CodiMD sqlite", sub {
+      $codimdSqlite->waitForUnit("codimd.service");
+      $codimdSqlite->waitForOpenPort(3000);
+      $codimdSqlite->waitUntilSucceeds("curl -sSf http://localhost:3000/new");
+    };
+
+    subtest "CodiMD postgres", sub {
+      $codimdPostgres->waitForUnit("postgresql.service");
+      $codimdPostgres->waitForUnit("codimd.service");
+      $codimdPostgres->waitForOpenPort(5432);
+      $codimdPostgres->waitForOpenPort(3000);
+      $codimdPostgres->waitUntilSucceeds("curl -sSf http://localhost:3000/new");
+    };
+  '';
+})
diff --git a/nixos/tests/common/letsencrypt/common.nix b/nixos/tests/common/letsencrypt/common.nix
new file mode 100644
index 000000000000..798a749f7f9b
--- /dev/null
+++ b/nixos/tests/common/letsencrypt/common.nix
@@ -0,0 +1,27 @@
+{ lib, nodes, ... }: {
+  networking.nameservers = [
+    nodes.letsencrypt.config.networking.primaryIPAddress
+  ];
+
+  nixpkgs.overlays = lib.singleton (self: super: {
+    cacert = super.cacert.overrideDerivation (drv: {
+      installPhase = (drv.installPhase or "") + ''
+        cat "${nodes.letsencrypt.config.test-support.letsencrypt.caCert}" \
+          >> "$out/etc/ssl/certs/ca-bundle.crt"
+      '';
+    });
+
+    # Override certifi so that it accepts fake certificate for Let's Encrypt
+    # Need to override the attribute used by simp_le, which is python3Packages
+    python3Packages = (super.python3.override {
+      packageOverrides = lib.const (pysuper: {
+        certifi = pysuper.certifi.overridePythonAttrs (attrs: {
+          postPatch = (attrs.postPatch or "") + ''
+            cat "${self.cacert}/etc/ssl/certs/ca-bundle.crt" \
+              > certifi/cacert.pem
+          '';
+        });
+      });
+    }).pkgs;
+  });
+}
diff --git a/nixos/tests/containers-bridge.nix b/nixos/tests/containers-bridge.nix
index bd8bd5dee9c8..777cf9a7e7f9 100644
--- a/nixos/tests/containers-bridge.nix
+++ b/nixos/tests/containers-bridge.nix
@@ -42,7 +42,6 @@ import ./make-test.nix ({ pkgs, ...} : {
             { services.httpd.enable = true;
               services.httpd.adminAddr = "foo@example.org";
               networking.firewall.allowedTCPPorts = [ 80 ];
-              networking.firewall.allowPing = true;
             };
         };
 
diff --git a/nixos/tests/containers-extra_veth.nix b/nixos/tests/containers-extra_veth.nix
index 8f874b3585dc..b4c48afe48ba 100644
--- a/nixos/tests/containers-extra_veth.nix
+++ b/nixos/tests/containers-extra_veth.nix
@@ -43,7 +43,6 @@ import ./make-test.nix ({ pkgs, ...} : {
           config =
             {
               networking.firewall.allowedTCPPorts = [ 80 ];
-              networking.firewall.allowPing = true;
             };
         };
 
diff --git a/nixos/tests/containers-imperative.nix b/nixos/tests/containers-imperative.nix
index 913d8bed19d0..782095a09dad 100644
--- a/nixos/tests/containers-imperative.nix
+++ b/nixos/tests/containers-imperative.nix
@@ -13,6 +13,7 @@ import ./make-test.nix ({ pkgs, ...} : {
       # XXX: Sandbox setup fails while trying to hardlink files from the host's
       #      store file system into the prepared chroot directory.
       nix.useSandbox = false;
+      nix.binaryCaches = []; # don't try to access cache.nixos.org
 
       virtualisation.writableStore = true;
       virtualisation.memorySize = 1024;
@@ -27,9 +28,10 @@ import ./make-test.nix ({ pkgs, ...} : {
             };
           };
         };
-      in [
-        pkgs.stdenv pkgs.stdenvNoCC emptyContainer.config.containers.foo.path
-        pkgs.libxslt
+      in with pkgs; [
+        stdenv stdenvNoCC emptyContainer.config.containers.foo.path
+        libxslt desktop-file-utils texinfo docbook5 libxml2
+        docbook_xsl_ns xorg.lndir documentation-highlighter
       ];
     };
 
@@ -84,6 +86,9 @@ import ./make-test.nix ({ pkgs, ...} : {
       # Execute commands via the root shell.
       $machine->succeed("nixos-container run $id1 -- uname") =~ /Linux/ or die;
 
+      # Execute a nix command via the root shell. (regression test for #40355)
+      $machine->succeed("nixos-container run $id1 -- nix-instantiate -E 'derivation { name = \"empty\"; builder = \"false\"; system = \"false\"; }'");
+
       # Stop and start (regression test for #4989)
       $machine->succeed("nixos-container stop $id1");
       $machine->succeed("nixos-container start $id1");
diff --git a/nixos/tests/containers-ipv4.nix b/nixos/tests/containers-ipv4.nix
index 4affe3d9d56d..5f83a33b1079 100644
--- a/nixos/tests/containers-ipv4.nix
+++ b/nixos/tests/containers-ipv4.nix
@@ -20,7 +20,6 @@ import ./make-test.nix ({ pkgs, ...} : {
             { services.httpd.enable = true;
               services.httpd.adminAddr = "foo@example.org";
               networking.firewall.allowedTCPPorts = [ 80 ];
-              networking.firewall.allowPing = true;
               system.stateVersion = "18.03";
             };
         };
diff --git a/nixos/tests/containers-ipv6.nix b/nixos/tests/containers-ipv6.nix
index 7db389a18e72..5866e51b731d 100644
--- a/nixos/tests/containers-ipv6.nix
+++ b/nixos/tests/containers-ipv6.nix
@@ -25,7 +25,6 @@ import ./make-test.nix ({ pkgs, ...} : {
             { services.httpd.enable = true;
               services.httpd.adminAddr = "foo@example.org";
               networking.firewall.allowedTCPPorts = [ 80 ];
-              networking.firewall.allowPing = true;
             };
         };
 
diff --git a/nixos/tests/containers-portforward.nix b/nixos/tests/containers-portforward.nix
index be83f82445ed..d2dda926fc0e 100644
--- a/nixos/tests/containers-portforward.nix
+++ b/nixos/tests/containers-portforward.nix
@@ -28,7 +28,6 @@ import ./make-test.nix ({ pkgs, ...} : {
             { services.httpd.enable = true;
               services.httpd.adminAddr = "foo@example.org";
               networking.firewall.allowedTCPPorts = [ 80 ];
-              networking.firewall.allowPing = true;
             };
         };
 
diff --git a/nixos/tests/containers-restart_networking.nix b/nixos/tests/containers-restart_networking.nix
index aeb0a6e68e21..0fb3b591e9f9 100644
--- a/nixos/tests/containers-restart_networking.nix
+++ b/nixos/tests/containers-restart_networking.nix
@@ -10,7 +10,6 @@ let
       hostBridge = "br0";
       config = {
         networking.firewall.enable = false;
-        networking.firewall.allowPing = true;
         networking.interfaces.eth0.ipv4.addresses = [
           { address = "192.168.1.122"; prefixLength = 24; }
         ];
diff --git a/nixos/tests/docker-tools.nix b/nixos/tests/docker-tools.nix
index db4eacc37287..360b32faae72 100644
--- a/nixos/tests/docker-tools.nix
+++ b/nixos/tests/docker-tools.nix
@@ -20,7 +20,10 @@ import ./make-test.nix ({ pkgs, ... }: {
     ''
       $docker->waitForUnit("sockets.target");
 
+      # Ensure Docker images use a stable date by default
       $docker->succeed("docker load --input='${pkgs.dockerTools.examples.bash}'");
+      $docker->succeed("[ '1970-01-01T00:00:01Z' = \"\$(docker inspect ${pkgs.dockerTools.examples.bash.imageName} | ${pkgs.jq}/bin/jq -r .[].Created)\" ]");
+
       $docker->succeed("docker run --rm ${pkgs.dockerTools.examples.bash.imageName} bash --version");
       $docker->succeed("docker rmi ${pkgs.dockerTools.examples.bash.imageName}");
 
@@ -51,5 +54,13 @@ import ./make-test.nix ({ pkgs, ... }: {
       $docker->succeed("docker run --rm runasrootextracommands cat extraCommands");
       $docker->succeed("docker run --rm runasrootextracommands cat runAsRoot");
       $docker->succeed("docker rmi '${pkgs.dockerTools.examples.runAsRootExtraCommands.imageName}'");
+
+      # Ensure Docker images can use an unstable date
+      $docker->succeed("docker load --input='${pkgs.dockerTools.examples.bash}'");
+      $docker->succeed("[ '1970-01-01T00:00:01Z' != \"\$(docker inspect ${pkgs.dockerTools.examples.unstableDate.imageName} | ${pkgs.jq}/bin/jq -r .[].Created)\" ]");
+
+      # Ensure Layered Docker images work
+      $docker->succeed("docker load --input='${pkgs.dockerTools.examples.layered-image}'");
+      $docker->succeed("docker run --rm ${pkgs.dockerTools.examples.layered-image.imageName}");
     '';
 })
diff --git a/nixos/tests/ferm.nix b/nixos/tests/ferm.nix
index 24b74df85ad1..b8e8663e3ad2 100644
--- a/nixos/tests/ferm.nix
+++ b/nixos/tests/ferm.nix
@@ -11,6 +11,7 @@ import ./make-test.nix ({ pkgs, ...} : {
         with pkgs.lib;
         {
           networking = {
+            dhcpcd.enable = false;
             interfaces.eth1.ipv6.addresses = mkOverride 0 [ { address = "fd00::2"; prefixLength = 64; } ];
             interfaces.eth1.ipv4.addresses = mkOverride 0 [ { address = "192.168.1.2"; prefixLength = 24; } ];
           };
@@ -20,6 +21,7 @@ import ./make-test.nix ({ pkgs, ...} : {
         with pkgs.lib;
         {
           networking = {
+            dhcpcd.enable = false;
             interfaces.eth1.ipv6.addresses = mkOverride 0 [ { address = "fd00::1"; prefixLength = 64; } ];
             interfaces.eth1.ipv4.addresses = mkOverride 0 [ { address = "192.168.1.1"; prefixLength = 24; } ];
           };
@@ -51,7 +53,7 @@ import ./make-test.nix ({ pkgs, ...} : {
     ''
       startAll;
 
-      $client->waitForUnit("network.target");
+      $client->waitForUnit("network-online.target");
       $server->waitForUnit("ferm.service");
       $server->waitForUnit("nginx.service");
       $server->waitUntilSucceeds("ss -ntl | grep -q 80");
diff --git a/nixos/tests/gdk-pixbuf.nix b/nixos/tests/gdk-pixbuf.nix
index b20f61b5ffe2..005c5111da2b 100644
--- a/nixos/tests/gdk-pixbuf.nix
+++ b/nixos/tests/gdk-pixbuf.nix
@@ -10,10 +10,12 @@ import ./make-test.nix ({ pkgs, ... }: {
     environment.systemPackages = with pkgs; [ gnome-desktop-testing ];
     environment.variables.XDG_DATA_DIRS = [ "${pkgs.gdk_pixbuf.installedTests}/share" ];
 
-    virtualisation.memorySize = 4096; # Tests allocate a lot of memory trying to exploit a CVE
+    # Tests allocate a lot of memory trying to exploit a CVE
+    # but qemu-system-i386 has a 2047M memory limit
+    virtualisation.memorySize = if pkgs.stdenv.isi686 then 2047 else 4096;
   };
 
   testScript = ''
-    $machine->succeed("gnome-desktop-testing-runner");
+    $machine->succeed("gnome-desktop-testing-runner -t 1800"); # increase timeout to 1800s
   '';
 })
diff --git a/nixos/tests/hibernate.nix b/nixos/tests/hibernate.nix
index 1f98bb739f21..274aa7becc82 100644
--- a/nixos/tests/hibernate.nix
+++ b/nixos/tests/hibernate.nix
@@ -35,8 +35,8 @@ import ./make-test.nix (pkgs: {
       $machine->waitForOpenPort(4444);
       $machine->succeed("systemctl hibernate &");
       $machine->waitForShutdown;
+      $probe->waitForUnit("multi-user.target");
       $machine->start;
-      $probe->waitForUnit("network.target");
       $probe->waitUntilSucceeds("echo test | nc machine 4444 -N");
     '';
 
diff --git a/nixos/tests/home-assistant.nix b/nixos/tests/home-assistant.nix
index 2d74b59bca46..0b3da0d59c68 100644
--- a/nixos/tests/home-assistant.nix
+++ b/nixos/tests/home-assistant.nix
@@ -74,7 +74,6 @@ in {
     print "$log\n";
 
     # Check that no errors were logged
-    # The timer can get out of sync due to Hydra's load, so this error is ignored
-    $hass->fail("cat ${configDir}/home-assistant.log | grep -vF 'Timer got out of sync' | grep -qF ERROR");
+    $hass->fail("cat ${configDir}/home-assistant.log | grep -qF ERROR");
   '';
 })
diff --git a/nixos/tests/hound.nix b/nixos/tests/hound.nix
index f21c0ad58a85..cb8e25332c07 100644
--- a/nixos/tests/hound.nix
+++ b/nixos/tests/hound.nix
@@ -52,7 +52,7 @@ import ./make-test.nix ({ pkgs, ... } : {
        $machine->waitForUnit("network.target");
        $machine->waitForUnit("hound.service");
        $machine->waitForOpenPort(6080);
-       $machine->succeed('curl http://127.0.0.1:6080/api/v1/search\?stats\=fosho\&repos\=\*\&rng=%3A20\&q\=hi\&files\=\&i=nope | grep "Filename" | grep "hello"');
+       $machine->waitUntilSucceeds('curl http://127.0.0.1:6080/api/v1/search\?stats\=fosho\&repos\=\*\&rng=%3A20\&q\=hi\&files\=\&i=nope | grep "Filename" | grep "hello"');
 
     '';
 })
diff --git a/nixos/tests/installer.nix b/nixos/tests/installer.nix
index 3d31c8dc4457..3f9fa0e6016c 100644
--- a/nixos/tests/installer.nix
+++ b/nixos/tests/installer.nix
@@ -282,9 +282,9 @@ in {
     { createPartitions =
         ''
           $machine->succeed(
-              "parted --script /dev/vda mklabel msdos",
-              "parted --script /dev/vda -- mkpart primary linux-swap 1M 1024M",
-              "parted --script /dev/vda -- mkpart primary ext2 1024M -1s",
+              "flock /dev/vda parted --script /dev/vda -- mklabel msdos"
+              . " mkpart primary linux-swap 1M 1024M"
+              . " mkpart primary ext2 1024M -1s",
               "udevadm settle",
               "mkswap /dev/vda1 -L swap",
               "swapon -L swap",
@@ -299,11 +299,11 @@ in {
     { createPartitions =
         ''
           $machine->succeed(
-              "parted --script /dev/vda mklabel gpt",
-              "parted --script /dev/vda -- mkpart ESP fat32 1M 50MiB", # /boot
-              "parted --script /dev/vda -- set 1 boot on",
-              "parted --script /dev/vda -- mkpart primary linux-swap 50MiB 1024MiB",
-              "parted --script /dev/vda -- mkpart primary ext2 1024MiB -1MiB", # /
+              "flock /dev/vda parted --script /dev/vda -- mklabel gpt"
+              . " mkpart ESP fat32 1M 50MiB" # /boot
+              . " set 1 boot on"
+              . " mkpart primary linux-swap 50MiB 1024MiB"
+              . " mkpart primary ext2 1024MiB -1MiB", # /
               "udevadm settle",
               "mkswap /dev/vda2 -L swap",
               "swapon -L swap",
@@ -321,11 +321,11 @@ in {
     { createPartitions =
         ''
           $machine->succeed(
-              "parted --script /dev/vda mklabel gpt",
-              "parted --script /dev/vda -- mkpart ESP fat32 1M 50MiB", # /boot
-              "parted --script /dev/vda -- set 1 boot on",
-              "parted --script /dev/vda -- mkpart primary linux-swap 50MiB 1024MiB",
-              "parted --script /dev/vda -- mkpart primary ext2 1024MiB -1MiB", # /
+              "flock /dev/vda parted --script /dev/vda -- mklabel gpt"
+              . " mkpart ESP fat32 1M 50MiB" # /boot
+              . " set 1 boot on"
+              . " mkpart primary linux-swap 50MiB 1024MiB"
+              . " mkpart primary ext2 1024MiB -1MiB", # /
               "udevadm settle",
               "mkswap /dev/vda2 -L swap",
               "swapon -L swap",
@@ -345,10 +345,10 @@ in {
     { createPartitions =
         ''
           $machine->succeed(
-              "parted --script /dev/vda mklabel msdos",
-              "parted --script /dev/vda -- mkpart primary ext2 1M 50MB", # /boot
-              "parted --script /dev/vda -- mkpart primary linux-swap 50MB 1024M",
-              "parted --script /dev/vda -- mkpart primary ext2 1024M -1s", # /
+              "flock /dev/vda parted --script /dev/vda -- mklabel msdos"
+              . " mkpart primary ext2 1M 50MB" # /boot
+              . " mkpart primary linux-swap 50MB 1024M"
+              . " mkpart primary ext2 1024M -1s", # /
               "udevadm settle",
               "mkswap /dev/vda2 -L swap",
               "swapon -L swap",
@@ -366,10 +366,10 @@ in {
     { createPartitions =
         ''
           $machine->succeed(
-              "parted --script /dev/vda mklabel msdos",
-              "parted --script /dev/vda -- mkpart primary ext2 1M 50MB", # /boot
-              "parted --script /dev/vda -- mkpart primary linux-swap 50MB 1024M",
-              "parted --script /dev/vda -- mkpart primary ext2 1024M -1s", # /
+              "flock /dev/vda parted --script /dev/vda -- mklabel msdos"
+              . " mkpart primary ext2 1M 50MB" # /boot
+              . " mkpart primary linux-swap 50MB 1024M"
+              . " mkpart primary ext2 1024M -1s", # /
               "udevadm settle",
               "mkswap /dev/vda2 -L swap",
               "swapon -L swap",
@@ -402,9 +402,9 @@ in {
       createPartitions =
         ''
           $machine->succeed(
-              "parted --script /dev/vda mklabel msdos",
-              "parted --script /dev/vda -- mkpart primary linux-swap 1M 1024M",
-              "parted --script /dev/vda -- mkpart primary 1024M -1s",
+              "flock /dev/vda parted --script /dev/vda -- mklabel msdos"
+              . " mkpart primary linux-swap 1M 1024M"
+              . " mkpart primary 1024M -1s",
               "udevadm settle",
 
               "mkswap /dev/vda1 -L swap",
@@ -425,11 +425,11 @@ in {
     { createPartitions =
         ''
           $machine->succeed(
-              "parted --script /dev/vda mklabel msdos",
-              "parted --script /dev/vda -- mkpart primary 1M 2048M", # PV1
-              "parted --script /dev/vda -- set 1 lvm on",
-              "parted --script /dev/vda -- mkpart primary 2048M -1s", # PV2
-              "parted --script /dev/vda -- set 2 lvm on",
+              "flock /dev/vda parted --script /dev/vda -- mklabel msdos"
+              . " mkpart primary 1M 2048M" # PV1
+              . " set 1 lvm on"
+              . " mkpart primary 2048M -1s" # PV2
+              . " set 2 lvm on",
               "udevadm settle",
               "pvcreate /dev/vda1 /dev/vda2",
               "vgcreate MyVolGroup /dev/vda1 /dev/vda2",
@@ -447,10 +447,10 @@ in {
   luksroot = makeInstallerTest "luksroot"
     { createPartitions = ''
         $machine->succeed(
-          "parted --script /dev/vda mklabel msdos",
-          "parted --script /dev/vda -- mkpart primary ext2 1M 50MB", # /boot
-          "parted --script /dev/vda -- mkpart primary linux-swap 50M 1024M",
-          "parted --script /dev/vda -- mkpart primary 1024M -1s", # LUKS
+          "flock /dev/vda parted --script /dev/vda -- mklabel msdos"
+          . " mkpart primary ext2 1M 50MB" # /boot
+          . " mkpart primary linux-swap 50M 1024M"
+          . " mkpart primary 1024M -1s", # LUKS
           "udevadm settle",
           "mkswap /dev/vda2 -L swap",
           "swapon -L swap",
@@ -481,11 +481,11 @@ in {
   filesystemEncryptedWithKeyfile = makeInstallerTest "filesystemEncryptedWithKeyfile"
     { createPartitions = ''
        $machine->succeed(
-          "parted --script /dev/vda mklabel msdos",
-          "parted --script /dev/vda -- mkpart primary ext2 1M 50MB", # /boot
-          "parted --script /dev/vda -- mkpart primary linux-swap 50M 1024M",
-          "parted --script /dev/vda -- mkpart primary 1024M 1280M", # LUKS with keyfile
-          "parted --script /dev/vda -- mkpart primary 1280M -1s",
+          "flock /dev/vda parted --script /dev/vda -- mklabel msdos"
+          . " mkpart primary ext2 1M 50MB" # /boot
+          . " mkpart primary linux-swap 50M 1024M"
+          . " mkpart primary 1024M 1280M" # LUKS with keyfile
+          . " mkpart primary 1280M -1s",
           "udevadm settle",
           "mkswap /dev/vda2 -L swap",
           "swapon -L swap",
@@ -520,7 +520,7 @@ in {
     { createPartitions =
         ''
           $machine->succeed(
-              "parted --script /dev/vda --"
+              "flock /dev/vda parted --script /dev/vda --"
               . " mklabel msdos"
               . " mkpart primary ext2 1M 100MB" # /boot
               . " mkpart extended 100M -1s"
@@ -531,8 +531,10 @@ in {
               "udevadm settle",
               "ls -l /dev/vda* >&2",
               "cat /proc/partitions >&2",
+              "udevadm control --stop-exec-queue",
               "mdadm --create --force /dev/md0 --metadata 1.2 --level=raid1 --raid-devices=2 /dev/vda5 /dev/vda6",
               "mdadm --create --force /dev/md1 --metadata 1.2 --level=raid1 --raid-devices=2 /dev/vda7 /dev/vda8",
+              "udevadm control --start-exec-queue",
               "udevadm settle",
               "mkswap -f /dev/md1 -L swap",
               "swapon -L swap",
@@ -555,14 +557,15 @@ in {
     { createPartitions =
         ''
           $machine->succeed(
-              "parted --script /dev/sda mklabel msdos",
-              "parted --script /dev/sda -- mkpart primary linux-swap 1M 1024M",
-              "parted --script /dev/sda -- mkpart primary ext2 1024M -1s",
+              "flock /dev/sda parted --script /dev/sda -- mklabel msdos"
+              . " mkpart primary linux-swap 1M 1024M"
+              . " mkpart primary ext2 1024M -1s",
               "udevadm settle",
               "mkswap /dev/sda1 -L swap",
               "swapon -L swap",
               "mkfs.ext3 -L nixos /dev/sda2",
               "mount LABEL=nixos /mnt",
+              "mkdir -p /mnt/tmp",
           );
         '';
       grubVersion = 1;
diff --git a/nixos/tests/kernel-copperhead.nix b/nixos/tests/kernel-copperhead.nix
deleted file mode 100644
index 652fbf055373..000000000000
--- a/nixos/tests/kernel-copperhead.nix
+++ /dev/null
@@ -1,19 +0,0 @@
-import ./make-test.nix ({ pkgs, ...} : {
-  name = "kernel-copperhead";
-  meta = with pkgs.stdenv.lib.maintainers; {
-    maintainers = [ nequissimus ];
-  };
-
-  machine = { pkgs, ... }:
-    {
-      boot.kernelPackages = pkgs.linuxPackages_copperhead_lts;
-    };
-
-  testScript =
-    ''
-      $machine->succeed("uname -a");
-      $machine->succeed("uname -s | grep 'Linux'");
-      $machine->succeed("uname -a | grep '${pkgs.linuxPackages_copperhead_lts.kernel.modDirVersion}'");
-      $machine->succeed("uname -a | grep 'hardened'");
-    '';
-})
diff --git a/nixos/tests/misc.nix b/nixos/tests/misc.nix
index b0bc1d083b16..3ad55651b112 100644
--- a/nixos/tests/misc.nix
+++ b/nixos/tests/misc.nix
@@ -14,7 +14,7 @@ import ./make-test.nix ({ pkgs, ...} : rec {
     { swapDevices = mkOverride 0
         [ { device = "/root/swapfile"; size = 128; } ];
       environment.variables.EDITOR = mkOverride 0 "emacs";
-      services.nixosManual.enable = mkOverride 0 true;
+      documentation.nixos.enable = mkOverride 0 true;
       systemd.tmpfiles.rules = [ "d /tmp 1777 root root 10d" ];
       fileSystems = mkVMOverride { "/tmp2" =
         { fsType = "tmpfs";
@@ -78,6 +78,8 @@ import ./make-test.nix ({ pkgs, ...} : rec {
 
       # Test whether we have a reboot record in wtmp.
       subtest "reboot-wtmp", sub {
+          $machine->shutdown;
+          $machine->waitForUnit('multi-user.target');
           $machine->succeed("last | grep reboot >&2");
       };
 
diff --git a/nixos/tests/nat.nix b/nixos/tests/nat.nix
index 9c280fe8b5b6..04b4f0f045f0 100644
--- a/nixos/tests/nat.nix
+++ b/nixos/tests/nat.nix
@@ -11,7 +11,6 @@ import ./make-test.nix ({ pkgs, lib, withFirewall, withConntrackHelpers ? false,
       lib.mkMerge [
         { virtualisation.vlans = [ 2 1 ];
           networking.firewall.enable = withFirewall;
-          networking.firewall.allowPing = true;
           networking.nat.internalIPs = [ "192.168.1.0/24" ];
           networking.nat.externalInterface = "eth1";
         }
@@ -33,7 +32,6 @@ import ./make-test.nix ({ pkgs, lib, withFirewall, withConntrackHelpers ? false,
           { pkgs, nodes, ... }:
           lib.mkMerge [
             { virtualisation.vlans = [ 1 ];
-              networking.firewall.allowPing = true;
               networking.defaultGateway =
                 (pkgs.lib.head nodes.router.config.networking.interfaces.eth2.ipv4.addresses).address;
             }
diff --git a/nixos/tests/networking.nix b/nixos/tests/networking.nix
index 02bd4bd98079..d1d4fd41dda6 100644
--- a/nixos/tests/networking.nix
+++ b/nixos/tests/networking.nix
@@ -17,7 +17,6 @@ let
       networking = {
         useDHCP = false;
         useNetworkd = networkd;
-        firewall.allowPing = true;
         firewall.checkReversePath = true;
         firewall.allowedUDPPorts = [ 547 ];
         interfaces = mkOverride 0 (listToAttrs (flip map vlanIfs (n:
@@ -86,7 +85,6 @@ let
         virtualisation.vlans = [ 1 2 ];
         networking = {
           useNetworkd = networkd;
-          firewall.allowPing = true;
           useDHCP = false;
           defaultGateway = "192.168.1.1";
           interfaces.eth1.ipv4.addresses = mkOverride 0 [
@@ -139,7 +137,6 @@ let
         virtualisation.vlans = [ 1 2 ];
         networking = {
           useNetworkd = networkd;
-          firewall.allowPing = true;
           useDHCP = true;
           interfaces.eth1 = {
             ipv4.addresses = mkOverride 0 [ ];
@@ -194,7 +191,6 @@ let
         virtualisation.vlans = [ 1 2 ];
         networking = {
           useNetworkd = networkd;
-          firewall.allowPing = true;
           useDHCP = false;
           interfaces.eth1 = {
             ipv4.addresses = mkOverride 0 [ ];
@@ -234,7 +230,6 @@ let
         virtualisation.vlans = [ 1 2 ];
         networking = {
           useNetworkd = networkd;
-          firewall.allowPing = true;
           useDHCP = false;
           bonds.bond = {
             interfaces = [ "eth1" "eth2" ];
@@ -271,7 +266,6 @@ let
         virtualisation.vlans = [ vlan ];
         networking = {
           useNetworkd = networkd;
-          firewall.allowPing = true;
           useDHCP = false;
           interfaces.eth1.ipv4.addresses = mkOverride 0
             [ { inherit address; prefixLength = 24; } ];
@@ -285,7 +279,6 @@ let
         virtualisation.vlans = [ 1 2 ];
         networking = {
           useNetworkd = networkd;
-          firewall.allowPing = true;
           useDHCP = false;
           bridges.bridge.interfaces = [ "eth1" "eth2" ];
           interfaces.eth1.ipv4.addresses = mkOverride 0 [ ];
@@ -329,7 +322,6 @@ let
           # reverse path filtering rules for the macvlan interface seem
           # to be incorrect, causing the test to fail. Disable temporarily.
           firewall.checkReversePath = false;
-          firewall.allowPing = true;
           useDHCP = true;
           macvlans.macvlan.interface = "eth1";
           interfaces.eth1.ipv4.addresses = mkOverride 0 [ ];
@@ -415,7 +407,6 @@ let
         #virtualisation.vlans = [ 1 ];
         networking = {
           useNetworkd = networkd;
-          firewall.allowPing = true;
           useDHCP = false;
           vlans.vlan = {
             id = 1;
@@ -467,7 +458,7 @@ let
 
         # Wait for networking to come up
         $machine->start;
-        $machine->waitForUnit("network.target");
+        $machine->waitForUnit("network-online.target");
 
         # Test interfaces set up
         my $list = $machine->succeed("ip tuntap list | sort");
@@ -479,7 +470,9 @@ let
 
         # Test interfaces clean up
         $machine->succeed("systemctl stop network-addresses-tap0");
+        $machine->sleep(10);
         $machine->succeed("systemctl stop network-addresses-tun0");
+        $machine->sleep(10);
         my $residue = $machine->succeed("ip tuntap list");
         $residue eq "" or die(
           "Some virtual interface has not been properly cleaned:\n",
diff --git a/nixos/tests/nextcloud/basic.nix b/nixos/tests/nextcloud/basic.nix
new file mode 100644
index 000000000000..c3b710f0f904
--- /dev/null
+++ b/nixos/tests/nextcloud/basic.nix
@@ -0,0 +1,56 @@
+import ../make-test.nix ({ pkgs, ...}: let
+  adminpass = "notproduction";
+  adminuser = "root";
+in {
+  name = "nextcloud-basic";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ globin eqyiel ];
+  };
+
+  nodes = {
+    # The only thing the client needs to do is download a file.
+    client = { ... }: {};
+
+    nextcloud = { config, pkgs, ... }: {
+      networking.firewall.allowedTCPPorts = [ 80 ];
+
+      services.nextcloud = {
+        enable = true;
+        nginx.enable = true;
+        hostName = "nextcloud";
+        config = {
+          # Don't inherit adminuser since "root" is supposed to be the default
+          inherit adminpass;
+        };
+      };
+    };
+  };
+
+  testScript = let
+    withRcloneEnv = pkgs.writeScript "with-rclone-env" ''
+      #!${pkgs.stdenv.shell}
+      export RCLONE_CONFIG_NEXTCLOUD_TYPE=webdav
+      export RCLONE_CONFIG_NEXTCLOUD_URL="http://nextcloud/remote.php/webdav/"
+      export RCLONE_CONFIG_NEXTCLOUD_VENDOR="nextcloud"
+      export RCLONE_CONFIG_NEXTCLOUD_USER="${adminuser}"
+      export RCLONE_CONFIG_NEXTCLOUD_PASS="$(${pkgs.rclone}/bin/rclone obscure ${adminpass})"
+      "''${@}"
+    '';
+    copySharedFile = pkgs.writeScript "copy-shared-file" ''
+      #!${pkgs.stdenv.shell}
+      echo 'hi' | ${withRcloneEnv} ${pkgs.rclone}/bin/rclone rcat nextcloud:test-shared-file
+    '';
+
+    diffSharedFile = pkgs.writeScript "diff-shared-file" ''
+      #!${pkgs.stdenv.shell}
+      diff <(echo 'hi') <(${pkgs.rclone}/bin/rclone cat nextcloud:test-shared-file)
+    '';
+  in ''
+    startAll();
+    $nextcloud->waitForUnit("multi-user.target");
+    $nextcloud->succeed("curl -sSf http://nextcloud/login");
+    $nextcloud->succeed("${withRcloneEnv} ${copySharedFile}");
+    $client->waitForUnit("multi-user.target");
+    $client->succeed("${withRcloneEnv} ${diffSharedFile}");
+  '';
+})
diff --git a/nixos/tests/nextcloud/default.nix b/nixos/tests/nextcloud/default.nix
new file mode 100644
index 000000000000..66da6794b961
--- /dev/null
+++ b/nixos/tests/nextcloud/default.nix
@@ -0,0 +1,6 @@
+{ system ? builtins.currentSystem }:
+{
+  basic = import ./basic.nix { inherit system; };
+  with-postgresql-and-redis = import ./with-postgresql-and-redis.nix { inherit system; };
+  with-mysql-and-memcached = import ./with-mysql-and-memcached.nix { inherit system; };
+}
diff --git a/nixos/tests/nextcloud/with-mysql-and-memcached.nix b/nixos/tests/nextcloud/with-mysql-and-memcached.nix
new file mode 100644
index 000000000000..c0d347238b47
--- /dev/null
+++ b/nixos/tests/nextcloud/with-mysql-and-memcached.nix
@@ -0,0 +1,97 @@
+import ../make-test.nix ({ pkgs, ...}: let
+  adminpass = "hunter2";
+  adminuser = "root";
+in {
+  name = "nextcloud-with-mysql-and-memcached";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ eqyiel ];
+  };
+
+  nodes = {
+    # The only thing the client needs to do is download a file.
+    client = { ... }: {};
+
+    nextcloud = { config, pkgs, ... }: {
+      networking.firewall.allowedTCPPorts = [ 80 ];
+
+      services.nextcloud = {
+        enable = true;
+        hostName = "nextcloud";
+        nginx.enable = true;
+        https = true;
+        caching = {
+          apcu = true;
+          redis = false;
+          memcached = true;
+        };
+        config = {
+          dbtype = "mysql";
+          dbname = "nextcloud";
+          dbuser = "nextcloud";
+          dbhost = "127.0.0.1";
+          dbport = 3306;
+          dbpass = "hunter2";
+          # Don't inherit adminuser since "root" is supposed to be the default
+          inherit adminpass;
+        };
+      };
+
+      services.mysql = {
+        enable = true;
+        bind = "127.0.0.1";
+        package = pkgs.mariadb;
+        initialScript = pkgs.writeText "mysql-init" ''
+          CREATE USER 'nextcloud'@'localhost' IDENTIFIED BY 'hunter2';
+          CREATE DATABASE IF NOT EXISTS nextcloud;
+          GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, INDEX, ALTER,
+            CREATE TEMPORARY TABLES ON nextcloud.* TO 'nextcloud'@'localhost'
+            IDENTIFIED BY 'hunter2';
+          FLUSH privileges;
+        '';
+      };
+
+      systemd.services."nextcloud-setup"= {
+        requires = ["mysql.service"];
+        after = ["mysql.service"];
+      };
+
+      services.memcached.enable = true;
+    };
+  };
+
+  testScript = let
+    configureMemcached = pkgs.writeScript "configure-memcached" ''
+      #!${pkgs.stdenv.shell}
+      nextcloud-occ config:system:set memcached_servers 0 0 --value 127.0.0.1 --type string
+      nextcloud-occ config:system:set memcached_servers 0 1 --value 11211 --type integer
+      nextcloud-occ config:system:set memcache.local --value '\OC\Memcache\APCu' --type string
+      nextcloud-occ config:system:set memcache.distributed --value '\OC\Memcache\Memcached' --type string
+    '';
+    withRcloneEnv = pkgs.writeScript "with-rclone-env" ''
+      #!${pkgs.stdenv.shell}
+      export RCLONE_CONFIG_NEXTCLOUD_TYPE=webdav
+      export RCLONE_CONFIG_NEXTCLOUD_URL="http://nextcloud/remote.php/webdav/"
+      export RCLONE_CONFIG_NEXTCLOUD_VENDOR="nextcloud"
+      export RCLONE_CONFIG_NEXTCLOUD_USER="${adminuser}"
+      export RCLONE_CONFIG_NEXTCLOUD_PASS="$(${pkgs.rclone}/bin/rclone obscure ${adminpass})"
+    '';
+    copySharedFile = pkgs.writeScript "copy-shared-file" ''
+      #!${pkgs.stdenv.shell}
+      echo 'hi' | ${pkgs.rclone}/bin/rclone rcat nextcloud:test-shared-file
+    '';
+
+    diffSharedFile = pkgs.writeScript "diff-shared-file" ''
+      #!${pkgs.stdenv.shell}
+      diff <(echo 'hi') <(${pkgs.rclone}/bin/rclone cat nextcloud:test-shared-file)
+    '';
+  in ''
+    startAll();
+    $nextcloud->waitForUnit("multi-user.target");
+    $nextcloud->succeed("${configureMemcached}");
+    $nextcloud->succeed("curl -sSf http://nextcloud/login");
+    $nextcloud->succeed("${withRcloneEnv} ${copySharedFile}");
+    $client->waitForUnit("multi-user.target");
+    $client->succeed("${withRcloneEnv} ${diffSharedFile}");
+
+  '';
+})
diff --git a/nixos/tests/nextcloud/with-postgresql-and-redis.nix b/nixos/tests/nextcloud/with-postgresql-and-redis.nix
new file mode 100644
index 000000000000..0351d4db69ac
--- /dev/null
+++ b/nixos/tests/nextcloud/with-postgresql-and-redis.nix
@@ -0,0 +1,130 @@
+import ../make-test.nix ({ pkgs, ...}: let
+  adminpass = "hunter2";
+  adminuser = "custom-admin-username";
+in {
+  name = "nextcloud-with-postgresql-and-redis";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ eqyiel ];
+  };
+
+  nodes = {
+    # The only thing the client needs to do is download a file.
+    client = { ... }: {};
+
+    nextcloud = { config, pkgs, ... }: {
+      networking.firewall.allowedTCPPorts = [ 80 ];
+
+      services.nextcloud = {
+        enable = true;
+        hostName = "nextcloud";
+        nginx.enable = true;
+        caching = {
+          apcu = false;
+          redis = true;
+          memcached = false;
+        };
+        config = {
+          dbtype = "pgsql";
+          dbname = "nextcloud";
+          dbuser = "nextcloud";
+          dbhost = "localhost";
+          dbpassFile = toString (pkgs.writeText "db-pass-file" ''
+            hunter2
+          '');
+          inherit adminuser;
+          adminpassFile = toString (pkgs.writeText "admin-pass-file" ''
+            ${adminpass}
+          '');
+        };
+      };
+
+      services.redis = {
+        unixSocket = "/var/run/redis/redis.sock";
+        enable = true;
+        extraConfig = ''
+          unixsocketperm 770
+        '';
+      };
+
+      systemd.services.redis = {
+        preStart = ''
+          mkdir -p /var/run/redis
+          chown ${config.services.redis.user}:${config.services.nginx.group} /var/run/redis
+        '';
+        serviceConfig.PermissionsStartOnly = true;
+      };
+
+      systemd.services."nextcloud-setup"= {
+        requires = ["postgresql.service"];
+        after = [
+          "postgresql.service"
+          "chown-redis-socket.service"
+        ];
+      };
+
+      # At the time of writing, redis creates its socket with the "nobody"
+      # group.  I figure this is slightly less bad than making the socket world
+      # readable.
+      systemd.services."chown-redis-socket" = {
+        enable = true;
+        script = ''
+          until ${pkgs.redis}/bin/redis-cli ping; do
+            echo "waiting for redis..."
+            sleep 1
+          done
+          chown ${config.services.redis.user}:${config.services.nginx.group} /var/run/redis/redis.sock
+        '';
+        after = [ "redis.service" ];
+        requires = [ "redis.service" ];
+        wantedBy = [ "redis.service" ];
+        serviceConfig = {
+          Type = "oneshot";
+        };
+      };
+
+      services.postgresql = {
+        enable = true;
+        initialScript = pkgs.writeText "psql-init" ''
+          create role nextcloud with login password 'hunter2';
+          create database nextcloud with owner nextcloud;
+        '';
+      };
+    };
+  };
+
+  testScript = let
+    configureRedis = pkgs.writeScript "configure-redis" ''
+      #!${pkgs.stdenv.shell}
+      nextcloud-occ config:system:set redis 'host' --value '/var/run/redis/redis.sock' --type string
+      nextcloud-occ config:system:set redis 'port' --value 0 --type integer
+      nextcloud-occ config:system:set memcache.local --value '\OC\Memcache\Redis' --type string
+      nextcloud-occ config:system:set memcache.locking --value '\OC\Memcache\Redis' --type string
+    '';
+    withRcloneEnv = pkgs.writeScript "with-rclone-env" ''
+      #!${pkgs.stdenv.shell}
+      export RCLONE_CONFIG_NEXTCLOUD_TYPE=webdav
+      export RCLONE_CONFIG_NEXTCLOUD_URL="http://nextcloud/remote.php/webdav/"
+      export RCLONE_CONFIG_NEXTCLOUD_VENDOR="nextcloud"
+      export RCLONE_CONFIG_NEXTCLOUD_USER="${adminuser}"
+      export RCLONE_CONFIG_NEXTCLOUD_PASS="$(${pkgs.rclone}/bin/rclone obscure ${adminpass})"
+      "''${@}"
+    '';
+    copySharedFile = pkgs.writeScript "copy-shared-file" ''
+      #!${pkgs.stdenv.shell}
+      echo 'hi' | ${pkgs.rclone}/bin/rclone rcat nextcloud:test-shared-file
+    '';
+
+    diffSharedFile = pkgs.writeScript "diff-shared-file" ''
+      #!${pkgs.stdenv.shell}
+      diff <(echo 'hi') <(${pkgs.rclone}/bin/rclone cat nextcloud:test-shared-file)
+    '';
+  in ''
+    startAll();
+    $nextcloud->waitForUnit("multi-user.target");
+    $nextcloud->succeed("${configureRedis}");
+    $nextcloud->succeed("curl -sSf http://nextcloud/login");
+    $nextcloud->succeed("${withRcloneEnv} ${copySharedFile}");
+    $client->waitForUnit("multi-user.target");
+    $client->succeed("${withRcloneEnv} ${diffSharedFile}");
+  '';
+})
diff --git a/nixos/tests/nix-ssh-serve.nix b/nixos/tests/nix-ssh-serve.nix
index aa366d8612d7..494d55121eb1 100644
--- a/nixos/tests/nix-ssh-serve.nix
+++ b/nixos/tests/nix-ssh-serve.nix
@@ -14,8 +14,8 @@ in
              keys = [ snakeOilPublicKey ];
              protocol = "ssh-ng";
            };
-         server.nix.package = pkgs.nixUnstable;
-         client.nix.package = pkgs.nixUnstable;
+         server.nix.package = pkgs.nix;
+         client.nix.package = pkgs.nix;
        };
      testScript = ''
        startAll;
diff --git a/nixos/tests/novacomd.nix b/nixos/tests/novacomd.nix
index 2b56aee0a2e7..4eb60c0feb5c 100644
--- a/nixos/tests/novacomd.nix
+++ b/nixos/tests/novacomd.nix
@@ -9,12 +9,16 @@ import ./make-test.nix ({ pkgs, ...} : {
   };
 
   testScript = ''
-    startAll;
+    $machine->waitForUnit("multi-user.target");
 
+    # multi-user.target wants novacomd.service, but let's make sure
     $machine->waitForUnit("novacomd.service");
 
     # Check status and try connecting with novacom
     $machine->succeed("systemctl status novacomd.service >&2");
+    # to prevent non-deterministic failure,
+    # make sure the daemon is really listening
+    $machine->waitForOpenPort(6968);
     $machine->succeed("novacom -l");
 
     # Stop the daemon, double-check novacom fails if daemon isn't working
@@ -23,6 +27,8 @@ import ./make-test.nix ({ pkgs, ...} : {
 
     # And back again for good measure
     $machine->startJob("novacomd");
+    # make sure the daemon is really listening
+    $machine->waitForOpenPort(6968);
     $machine->succeed("novacom -l");
   '';
 })
diff --git a/nixos/tests/opensmtpd.nix b/nixos/tests/opensmtpd.nix
index 5079779f35b4..4d3479168f70 100644
--- a/nixos/tests/opensmtpd.nix
+++ b/nixos/tests/opensmtpd.nix
@@ -17,11 +17,12 @@ import ./make-test.nix {
         extraServerArgs = [ "-v" ];
         serverConfiguration = ''
           listen on 0.0.0.0
+          action do_relay relay
           # DO NOT DO THIS IN PRODUCTION!
           # Setting up authentication requires a certificate which is painful in
           # a test environment, but THIS WOULD BE DANGEROUS OUTSIDE OF A
           # WELL-CONTROLLED ENVIRONMENT!
-          accept from any for any relay
+          match from any for any action do_relay
         '';
       };
     };
@@ -41,8 +42,9 @@ import ./make-test.nix {
         extraServerArgs = [ "-v" ];
         serverConfiguration = ''
           listen on 0.0.0.0
-          accept from any for local deliver to mda \
+          action dovecot_deliver mda \
             "${pkgs.dovecot}/libexec/dovecot/deliver -d %{user.username}"
+          match from any for local action dovecot_deliver
         '';
       };
       services.dovecot2 = {
@@ -102,11 +104,17 @@ import ./make-test.nix {
   testScript = ''
     startAll;
 
-    $client->waitForUnit("network.target");
+    $client->waitForUnit("network-online.target");
     $smtp1->waitForUnit('opensmtpd');
     $smtp2->waitForUnit('opensmtpd');
     $smtp2->waitForUnit('dovecot2');
 
+    # To prevent sporadic failures during daemon startup, make sure
+    # services are listening on their ports before sending requests
+    $smtp1->waitForOpenPort(25);
+    $smtp2->waitForOpenPort(25);
+    $smtp2->waitForOpenPort(143);
+
     $client->succeed('send-a-test-mail');
     $smtp1->waitUntilFails('smtpctl show queue | egrep .');
     $smtp2->waitUntilFails('smtpctl show queue | egrep .');
diff --git a/nixos/tests/plasma5.nix b/nixos/tests/plasma5.nix
index eb705536827e..788c8719c8d2 100644
--- a/nixos/tests/plasma5.nix
+++ b/nixos/tests/plasma5.nix
@@ -26,31 +26,20 @@ import ./make-test.nix ({ pkgs, ...} :
     services.xserver.displayManager.sddm.theme = "breeze-ocr-theme";
     services.xserver.desktopManager.plasma5.enable = true;
     services.xserver.desktopManager.default = "plasma5";
+    services.xserver.displayManager.sddm.autoLogin = {
+      enable = true;
+      user = "alice";
+    };
     virtualisation.memorySize = 1024;
     environment.systemPackages = [ sddm_theme ];
-
-    # fontconfig-penultimate-0.3.3 -> 0.3.4 broke OCR apparently, but no idea why.
-    nixpkgs.config.packageOverrides = superPkgs: {
-      fontconfig-penultimate = superPkgs.fontconfig-penultimate.override {
-        version = "0.3.3";
-        sha256 = "1z76jbkb0nhf4w7fy647yyayqr4q02fgk6w58k0yi700p0m3h4c9";
-      };
-    };
   };
 
-  enableOCR = true;
-
   testScript = { nodes, ... }: let
     user = nodes.machine.config.users.users.alice;
     xdo = "${pkgs.xdotool}/bin/xdotool";
   in ''
     startAll;
-    # Wait for display manager to start
-    $machine->waitForText(qr/${user.description}/);
-    $machine->screenshot("sddm");
-
-    # Log in
-    $machine->sendChars("${user.password}\n");
+    # wait for log in
     $machine->waitForFile("/home/alice/.Xauthority");
     $machine->succeed("xauth merge ~alice/.Xauthority");
 
diff --git a/nixos/tests/postgis.nix b/nixos/tests/postgis.nix
index f8b63c5b6a27..49be0672a8e5 100644
--- a/nixos/tests/postgis.nix
+++ b/nixos/tests/postgis.nix
@@ -9,7 +9,7 @@ import ./make-test.nix ({ pkgs, ...} : {
       { pkgs, ... }:
 
       {
-        services.postgresql = let mypg = pkgs.postgresql100; in {
+        services.postgresql = let mypg = pkgs.postgresql_11; in {
             enable = true;
             package = mypg;
             extraPlugins = [ (pkgs.postgis.override { postgresql = mypg; }) ];
diff --git a/nixos/tests/prometheus-exporters.nix b/nixos/tests/prometheus-exporters.nix
new file mode 100644
index 000000000000..5d1e004c5dd1
--- /dev/null
+++ b/nixos/tests/prometheus-exporters.nix
@@ -0,0 +1,316 @@
+import ./make-test.nix ({ lib, pkgs, ... }:
+let
+  escape' = str: lib.replaceChars [''"'' "$" "\n"] [''\\\"'' "\\$" ""] str;
+
+/*
+ * The attrset `exporterTests` contains one attribute
+ * for each exporter test. Each of these attributes
+ * is expected to be an attrset containing:
+ *
+ *  `exporterConfig`:
+ *    this attribute set contains config for the exporter itself
+ *
+ *  `exporterTest`
+ *    this attribute set contains test instructions
+ *
+ *  `metricProvider` (optional)
+ *    this attribute contains additional machine config
+ *
+ *  Example:
+ *    exporterTests.<exporterName> = {
+ *      exporterConfig = {
+ *        enable = true;
+ *      };
+ *      metricProvider = {
+ *        services.<metricProvider>.enable = true;
+ *      };
+ *      exporterTest = ''
+ *        waitForUnit("prometheus-<exporterName>-exporter.service");
+ *        waitForOpenPort("1234");
+ *        succeed("curl -sSf 'localhost:1234/metrics'");
+ *      '';
+ *    };
+ *
+ *  # this would generate the following test config:
+ *
+ *    nodes.<exporterName> = {
+ *      services.prometheus.<exporterName> = {
+ *        enable = true;
+ *      };
+ *      services.<metricProvider>.enable = true;
+ *    };
+ *
+ *    testScript = ''
+ *      $<exporterName>->start();
+ *      $<exporterName>->waitForUnit("prometheus-<exporterName>-exporter.service");
+ *      $<exporterName>->waitForOpenPort("1234");
+ *      $<exporterName>->succeed("curl -sSf 'localhost:1234/metrics'");
+ *      $<exporterName>->shutdown();
+ *    '';
+ */
+
+  exporterTests = {
+
+    blackbox = {
+      exporterConfig = {
+        enable = true;
+        configFile = pkgs.writeText "config.yml" (builtins.toJSON {
+          modules.icmp_v6 = {
+            prober = "icmp";
+            icmp.preferred_ip_protocol = "ip6";
+          };
+        });
+      };
+      exporterTest = ''
+        waitForUnit("prometheus-blackbox-exporter.service");
+        waitForOpenPort(9115);
+        succeed("curl -sSf 'http://localhost:9115/probe?target=localhost&module=icmp_v6' | grep -q 'probe_success 1'");
+      '';
+    };
+
+    collectd = {
+      exporterConfig = {
+        enable = true;
+        extraFlags = [ "--web.collectd-push-path /collectd" ];
+      };
+      exporterTest =let postData = escape' ''
+        [{
+          "values":[23],
+          "dstypes":["gauge"],
+          "type":"gauge",
+          "interval":1000,
+          "host":"testhost",
+          "plugin":"testplugin",
+          "time":$(date +%s)
+        }]
+        ''; in ''
+        waitForUnit("prometheus-collectd-exporter.service");
+        waitForOpenPort(9103);
+        succeed("curl -sSfH 'Content-Type: application/json' -X POST --data \"${postData}\" localhost:9103/collectd");
+        succeed("curl -sSf localhost:9103/metrics | grep -q 'collectd_testplugin_gauge{instance=\"testhost\"} 23'");
+      '';
+    };
+
+    dnsmasq = {
+      exporterConfig = {
+        enable = true;
+        leasesPath = "/var/lib/dnsmasq/dnsmasq.leases";
+      };
+      metricProvider = {
+        services.dnsmasq.enable = true;
+      };
+      exporterTest = ''
+        waitForUnit("prometheus-dnsmasq-exporter.service");
+        waitForOpenPort(9153);
+        succeed("curl -sSf http://localhost:9153/metrics | grep -q 'dnsmasq_leases 0'");
+      '';
+    };
+
+    dovecot = {
+      exporterConfig = {
+        enable = true;
+        scopes = [ "global" ];
+        socketPath = "/var/run/dovecot2/old-stats";
+        user = "root"; # <- don't use user root in production
+      };
+      metricProvider = {
+        services.dovecot2.enable = true;
+      };
+      exporterTest = ''
+        waitForUnit("prometheus-dovecot-exporter.service");
+        waitForOpenPort(9166);
+        succeed("curl -sSf http://localhost:9166/metrics | grep -q 'dovecot_up{scope=\"global\"} 1'");
+      '';
+    };
+
+    fritzbox = { # TODO add proper test case
+      exporterConfig = {
+        enable = true;
+      };
+      exporterTest = ''
+        waitForUnit("prometheus-fritzbox-exporter.service");
+        waitForOpenPort(9133);
+        succeed("curl -sSf http://localhost:9133/metrics | grep -q 'fritzbox_exporter_collect_errors 0'");
+      '';
+    };
+
+    json = {
+      exporterConfig = {
+        enable = true;
+        url = "http://localhost";
+        configFile = pkgs.writeText "json-exporter-conf.json" (builtins.toJSON [{
+          name = "json_test_metric";
+          path = "$.test";
+        }]);
+      };
+      metricProvider = {
+        systemd.services.prometheus-json-exporter.after = [ "nginx.service" ];
+        services.nginx = {
+          enable = true;
+          virtualHosts.localhost.locations."/".extraConfig = ''
+            return 200 "{\"test\":1}";
+          '';
+        };
+      };
+      exporterTest = ''
+        waitForUnit("nginx.service");
+        waitForOpenPort(80);
+        waitForUnit("prometheus-json-exporter.service");
+        waitForOpenPort(7979);
+        succeed("curl -sSf localhost:7979/metrics | grep -q 'json_test_metric 1'");
+      '';
+    };
+
+    nginx = {
+      exporterConfig = {
+        enable = true;
+      };
+      metricProvider = {
+        services.nginx = {
+          enable = true;
+          statusPage = true;
+          virtualHosts."/".extraConfig = "return 204;";
+        };
+      };
+      exporterTest = ''
+        waitForUnit("nginx.service")
+        waitForUnit("prometheus-nginx-exporter.service")
+        waitForOpenPort(9113)
+        succeed("curl -sSf http://localhost:9113/metrics | grep -q 'nginx_up 1'")
+      '';
+    };
+
+    node = {
+      exporterConfig = {
+        enable = true;
+      };
+      exporterTest = ''
+        waitForUnit("prometheus-node-exporter.service");
+        waitForOpenPort(9100);
+        succeed("curl -sSf http://localhost:9100/metrics | grep -q 'node_exporter_build_info{.\\+} 1'");
+      '';
+    };
+
+    postfix = {
+      exporterConfig = {
+        enable = true;
+      };
+      metricProvider = {
+        services.postfix.enable = true;
+      };
+      exporterTest = ''
+        waitForUnit("prometheus-postfix-exporter.service");
+        waitForOpenPort(9154);
+        succeed("curl -sSf http://localhost:9154/metrics | grep -q 'postfix_smtpd_connects_total 0'");
+      '';
+    };
+
+    snmp = {
+      exporterConfig = {
+        enable = true;
+        configuration.default = {
+          version = 2;
+          auth.community = "public";
+        };
+      };
+      exporterTest = ''
+        waitForUnit("prometheus-snmp-exporter.service");
+        waitForOpenPort(9116);
+        succeed("curl -sSf localhost:9116/metrics | grep -q 'snmp_request_errors_total 0'");
+      '';
+    };
+
+    surfboard = {
+      exporterConfig = {
+        enable = true;
+        modemAddress = "localhost";
+      };
+      metricProvider = {
+        systemd.services.prometheus-surfboard-exporter.after = [ "nginx.service" ];
+        services.nginx = {
+          enable = true;
+          virtualHosts.localhost.locations."/cgi-bin/status".extraConfig = ''
+            return 204;
+          '';
+        };
+      };
+      exporterTest = ''
+        waitForUnit("nginx.service");
+        waitForOpenPort(80);
+        waitForUnit("prometheus-surfboard-exporter.service");
+        waitForOpenPort(9239);
+        succeed("curl -sSf localhost:9239/metrics | grep -q 'surfboard_up 1'");
+      '';
+    };
+
+    tor = {
+      exporterConfig = {
+        enable = true;
+      };
+      metricProvider = {
+        # Note: this does not connect the test environment to the Tor network.
+        # Client, relay, bridge or exit connectivity are disabled by default.
+        services.tor.enable = true;
+        services.tor.controlPort = 9051;
+      };
+      exporterTest = ''
+        waitForUnit("tor.service");
+        waitForOpenPort(9051);
+        waitForUnit("prometheus-tor-exporter.service");
+        waitForOpenPort(9130);
+        succeed("curl -sSf localhost:9130/metrics | grep -q 'tor_version{.\\+} 1'");
+      '';
+    };
+
+    varnish = {
+      exporterConfig = {
+        enable = true;
+        instance = "/var/spool/varnish/varnish";
+        group = "varnish";
+      };
+      metricProvider = {
+        systemd.services.prometheus-varnish-exporter.after = [
+          "varnish.service"
+        ];
+        services.varnish = {
+          enable = true;
+          config = ''
+            vcl 4.0;
+            backend default {
+              .host = "127.0.0.1";
+              .port = "80";
+            }
+          '';
+        };
+      };
+      exporterTest = ''
+        waitForUnit("prometheus-varnish-exporter.service");
+        waitForOpenPort(9131);
+        succeed("curl -sSf http://localhost:9131/metrics | grep -q 'varnish_up 1'");
+      '';
+    };
+  };
+
+  nodes = lib.mapAttrs (exporter: testConfig: lib.mkMerge [{
+    services.prometheus.exporters.${exporter} = testConfig.exporterConfig;
+  } testConfig.metricProvider or {}]) exporterTests;
+
+  testScript = lib.concatStrings (lib.mapAttrsToList (exporter: testConfig: (''
+    subtest "${exporter}", sub {
+      ${"$"+exporter}->start();
+      ${lib.concatStringsSep "  " (map (line: ''
+        ${"$"+exporter}->${line};
+      '') (lib.splitString "\n" (lib.removeSuffix "\n" testConfig.exporterTest)))}
+      ${"$"+exporter}->shutdown();
+    };
+  '')) exporterTests);
+in
+{
+  name = "prometheus-exporters";
+
+  inherit nodes testScript;
+
+  meta = with lib.maintainers; {
+    maintainers = [ willibutz ];
+  };
+})
diff --git a/nixos/tests/prosody.nix b/nixos/tests/prosody.nix
index 5d33aaf8d65d..61ae5bb38ed9 100644
--- a/nixos/tests/prosody.nix
+++ b/nixos/tests/prosody.nix
@@ -6,6 +6,9 @@ import ./make-test.nix {
       enable = true;
       # TODO: use a self-signed certificate
       c2sRequireEncryption = false;
+      extraConfig = ''
+        storage = "sql"
+      '';
     };
     environment.systemPackages = let
       sendMessage = pkgs.writeScriptBin "send-message" ''
diff --git a/nixos/tests/quagga.nix b/nixos/tests/quagga.nix
index 0ff14a21584a..6aee7ea57f03 100644
--- a/nixos/tests/quagga.nix
+++ b/nixos/tests/quagga.nix
@@ -66,7 +66,6 @@ import ./make-test.nix ({ pkgs, ... }:
             virtualisation.vlans = [ 3 ];
             networking.defaultGateway = ifAddr nodes.router2 "eth1";
             networking.firewall.allowedTCPPorts = [ 80 ];
-            networking.firewall.allowPing = true;
             services.httpd.enable = true;
             services.httpd.adminAddr = "foo@example.com";
           };
diff --git a/nixos/tests/redmine.nix b/nixos/tests/redmine.nix
new file mode 100644
index 000000000000..330f72854cac
--- /dev/null
+++ b/nixos/tests/redmine.nix
@@ -0,0 +1,40 @@
+import ./make-test.nix ({ pkgs, lib, ... }:
+{
+  name = "redmine";
+  meta.maintainers = [ lib.maintainers.aanderse ];
+
+  machine =
+    { config, pkgs, ... }:
+    { services.mysql.enable = true;
+      services.mysql.package = pkgs.mariadb;
+      services.mysql.ensureDatabases = [ "redmine" ];
+      services.mysql.ensureUsers = [
+        { name = "redmine";
+          ensurePermissions = { "redmine.*" = "ALL PRIVILEGES"; };
+        }
+      ];
+
+      services.redmine.enable = true;
+      services.redmine.database.socket = "/run/mysqld/mysqld.sock";
+      services.redmine.plugins = {
+        redmine_env_auth = pkgs.fetchurl {
+          url = https://github.com/Intera/redmine_env_auth/archive/0.6.zip;
+          sha256 = "0yyr1yjd8gvvh832wdc8m3xfnhhxzk2pk3gm2psg5w9jdvd6skak";
+        };
+      };
+      services.redmine.themes = {
+        dkuk-redmine_alex_skin = pkgs.fetchurl {
+          url = https://bitbucket.org/dkuk/redmine_alex_skin/get/1842ef675ef3.zip;
+          sha256 = "0hrin9lzyi50k4w2bd2b30vrf1i4fi1c0gyas5801wn8i7kpm9yl";
+        };
+      };
+    };
+
+  testScript = ''
+    startAll;
+
+    $machine->waitForUnit('redmine.service');
+    $machine->waitForOpenPort('3000');
+    $machine->succeed("curl --fail http://localhost:3000/");
+  '';
+})
diff --git a/nixos/tests/rspamd.nix b/nixos/tests/rspamd.nix
index 6b2e2dd3a531..a12622b6aa0b 100644
--- a/nixos/tests/rspamd.nix
+++ b/nixos/tests/rspamd.nix
@@ -13,13 +13,10 @@ let
     $machine->succeed("[[ \"\$(stat -c %G ${socket})\" == \"${group}\" ]]");
     $machine->succeed("[[ \"\$(stat -c %a ${socket})\" == \"${mode}\" ]]");
   '';
-  simple = name: socketActivation: enableIPv6: makeTest {
+  simple = name: enableIPv6: makeTest {
     name = "rspamd-${name}";
     machine = {
-      services.rspamd = {
-        enable = true;
-        socketActivation = socketActivation;
-      };
+      services.rspamd.enable = true;
       networking.enableIPv6 = enableIPv6;
     };
     testScript = ''
@@ -32,13 +29,6 @@ let
       sleep 10;
       $machine->log($machine->succeed("cat /etc/rspamd.conf"));
       $machine->log($machine->succeed("systemctl cat rspamd.service"));
-      ${if socketActivation then ''
-        $machine->log($machine->succeed("systemctl cat rspamd-controller-1.socket"));
-        $machine->log($machine->succeed("systemctl cat rspamd-normal-1.socket"));
-      '' else ''
-        $machine->fail("systemctl cat rspamd-controller-1.socket");
-        $machine->fail("systemctl cat rspamd-normal-1.socket");
-      ''}
       $machine->log($machine->succeed("curl http://localhost:11334/auth"));
       $machine->log($machine->succeed("curl http://127.0.0.1:11334/auth"));
       ${optionalString enableIPv6 ''
@@ -48,10 +38,8 @@ let
   };
 in
 {
-  simple = simple "simple" false true;
-  ipv4only = simple "ipv4only" false false;
-  simple-socketActivated = simple "simple-socketActivated" true true;
-  ipv4only-socketActivated = simple "ipv4only-socketActivated" true false;
+  simple = simple "simple" true;
+  ipv4only = simple "ipv4only" false;
   deprecated = makeTest {
     name = "rspamd-deprecated";
     machine = {
@@ -68,7 +56,6 @@ in
       ${checkSocket "/run/rspamd.sock" "root" "root" "600" }
       ${checkSocket "/run/rspamd-worker.sock" "root" "root" "666" }
       $machine->log($machine->succeed("cat /etc/rspamd.conf"));
-      $machine->fail("systemctl cat rspamd-normal-1.socket");
       $machine->log($machine->succeed("rspamc -h /run/rspamd-worker.sock stat"));
       $machine->log($machine->succeed("curl --unix-socket /run/rspamd-worker.sock http://localhost/ping"));
     '';
@@ -79,7 +66,6 @@ in
     machine = {
       services.rspamd = {
         enable = true;
-        socketActivation = false;
         workers.normal.bindSockets = [{
           socket = "/run/rspamd.sock";
           mode = "0600";
@@ -101,38 +87,6 @@ in
       ${checkSocket "/run/rspamd.sock" "root" "root" "600" }
       ${checkSocket "/run/rspamd-worker.sock" "root" "root" "666" }
       $machine->log($machine->succeed("cat /etc/rspamd.conf"));
-      $machine->fail("systemctl cat rspamd-normal-1.socket");
-      $machine->log($machine->succeed("rspamc -h /run/rspamd-worker.sock stat"));
-      $machine->log($machine->succeed("curl --unix-socket /run/rspamd-worker.sock http://localhost/ping"));
-    '';
-  };
-  socketActivated = makeTest {
-    name = "rspamd-socketActivated";
-    machine = {
-      services.rspamd = {
-        enable = true;
-        workers.normal.bindSockets = [{
-          socket = "/run/rspamd.sock";
-          mode = "0600";
-          owner = "root";
-          group = "root";
-        }];
-        workers.controller.bindSockets = [{
-          socket = "/run/rspamd-worker.sock";
-          mode = "0666";
-          owner = "root";
-          group = "root";
-        }];
-      };
-    };
-
-    testScript = ''
-      startAll
-      $machine->waitForFile("/run/rspamd.sock");
-      ${checkSocket "/run/rspamd.sock" "root" "root" "600" }
-      ${checkSocket "/run/rspamd-worker.sock" "root" "root" "666" }
-      $machine->log($machine->succeed("cat /etc/rspamd.conf"));
-      $machine->log($machine->succeed("systemctl cat rspamd-normal-1.socket"));
       $machine->log($machine->succeed("rspamc -h /run/rspamd-worker.sock stat"));
       $machine->log($machine->succeed("curl --unix-socket /run/rspamd-worker.sock http://localhost/ping"));
     '';
diff --git a/nixos/tests/rsyslogd.nix b/nixos/tests/rsyslogd.nix
new file mode 100644
index 000000000000..969d59e0f2c2
--- /dev/null
+++ b/nixos/tests/rsyslogd.nix
@@ -0,0 +1,38 @@
+{ system ? builtins.currentSystem }:
+
+with import ../lib/testing.nix { inherit system; };
+with pkgs.lib;
+{
+  test1 = makeTest {
+    name = "rsyslogd-test1";
+    meta.maintainers = [ maintainers.aanderse ];
+
+    machine =
+      { config, pkgs, ... }:
+      { services.rsyslogd.enable = true;
+        services.journald.forwardToSyslog = false;
+      };
+
+    # ensure rsyslogd isn't receiving messages from journald if explicitly disabled
+    testScript = ''
+      $machine->waitForUnit("default.target");
+      $machine->fail("test -f /var/log/messages");
+    '';
+  };
+
+  test2 = makeTest {
+    name = "rsyslogd-test2";
+    meta.maintainers = [ maintainers.aanderse ];
+
+    machine =
+      { config, pkgs, ... }:
+      { services.rsyslogd.enable = true;
+      };
+
+    # ensure rsyslogd is receiving messages from journald
+    testScript = ''
+      $machine->waitForUnit("default.target");
+      $machine->succeed("test -f /var/log/messages");
+    '';
+  };
+}
diff --git a/nixos/tests/slurm.nix b/nixos/tests/slurm.nix
index 60f44c3c8459..7f9c266cbff6 100644
--- a/nixos/tests/slurm.nix
+++ b/nixos/tests/slurm.nix
@@ -1,22 +1,27 @@
-import ./make-test.nix ({ ... }:
-let mungekey = "mungeverryweakkeybuteasytointegratoinatest";
+import ./make-test.nix ({ lib, ... }:
+let
+    mungekey = "mungeverryweakkeybuteasytointegratoinatest";
+
     slurmconfig = {
       controlMachine = "control";
-      nodeName = ''
-        control
-        NodeName=node[1-3] CPUs=1 State=UNKNOWN
+      nodeName = [ "node[1-3] CPUs=1 State=UNKNOWN" ];
+      partitionName = [ "debug Nodes=node[1-3] Default=YES MaxTime=INFINITE State=UP" ];
+      extraConfig = ''
+        AccountingStorageHost=dbd
+        AccountingStorageType=accounting_storage/slurmdbd
       '';
-      partitionName = "debug Nodes=node[1-3] Default=YES MaxTime=INFINITE State=UP";
     };
 in {
   name = "slurm";
 
+  meta.maintainers = [ lib.maintainers.markuskowa ];
+
   nodes =
     let
     computeNode =
       { ...}:
       {
-        # TODO slrumd port and slurmctld port should be configurations and
+        # TODO slurmd port and slurmctld port should be configurations and
         # automatically allowed by the  firewall.
         networking.firewall.enable = false;
         services.slurm = {
@@ -43,6 +48,24 @@ in {
         } // slurmconfig;
       };
 
+    dbd =
+      { pkgs, ... } :
+      {
+        networking.firewall.enable = false;
+        services.slurm.dbdserver = {
+          enable = true;
+        };
+        services.mysql = {
+          enable = true;
+          package = pkgs.mysql;
+          ensureDatabases = [ "slurm_acct_db" ];
+          ensureUsers = [{
+            ensurePermissions = { "slurm_acct_db.*" = "ALL PRIVILEGES"; };
+            name = "slurm";
+          }];
+        };
+      };
+
     node1 = computeNode;
     node2 = computeNode;
     node3 = computeNode;
@@ -54,7 +77,7 @@ in {
   startAll;
 
   # Set up authentification across the cluster
-  foreach my $node (($submit,$control,$node1,$node2,$node3))
+  foreach my $node (($submit,$control,$dbd,$node1,$node2,$node3))
   {
     $node->waitForUnit("default.target");
 
@@ -63,10 +86,22 @@ in {
     $node->succeed("chmod 0400 /etc/munge/munge.key");
     $node->succeed("chown munge:munge /etc/munge/munge.key");
     $node->succeed("systemctl restart munged");
-  }
+
+    $node->waitForUnit("munged");
+  };
 
   # Restart the services since they have probably failed due to the munge init
   # failure
+  subtest "can_start_slurmdbd", sub {
+    $dbd->succeed("systemctl restart slurmdbd");
+    $dbd->waitForUnit("slurmdbd.service");
+  };
+
+  # there needs to be an entry for the current
+  # cluster in the database before slurmctld is restarted
+  subtest "add_account", sub {
+    $control->succeed("sacctmgr -i add cluster default");
+  };
 
   subtest "can_start_slurmctld", sub {
     $control->succeed("systemctl restart slurmctld");
@@ -81,12 +116,17 @@ in {
     }
   };
 
-  # Test that the cluster work and can distribute jobs;
+  # Test that the cluster works and can distribute jobs;
 
   subtest "run_distributed_command", sub {
     # Run `hostname` on 3 nodes of the partition (so on all the 3 nodes).
     # The output must contain the 3 different names
     $submit->succeed("srun -N 3 hostname | sort | uniq | wc -l | xargs test 3 -eq");
   };
+
+  subtest "check_slurm_dbd", sub {
+    # find the srun job from above in the database
+    $submit->succeed("sacct | grep hostname");
+  };
   '';
 })
diff --git a/nixos/tests/upnp.nix b/nixos/tests/upnp.nix
new file mode 100644
index 000000000000..3f2dd13fb560
--- /dev/null
+++ b/nixos/tests/upnp.nix
@@ -0,0 +1,94 @@
+# This tests whether UPnP port mappings can be created using Miniupnpd
+# and Miniupnpc.
+# It runs a Miniupnpd service on one machine, and verifies
+# a client can indeed create a port mapping using Miniupnpc. If
+# this succeeds an external client will try to connect to the port
+# mapping.
+
+import ./make-test.nix ({ pkgs, ... }:
+
+let
+  internalRouterAddress = "192.168.3.1";
+  internalClient1Address = "192.168.3.2";
+  externalRouterAddress = "80.100.100.1";
+  externalClient2Address = "80.100.100.2";
+in
+{
+  name = "upnp";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ bobvanderlinden ];
+  };
+
+  nodes =
+    {
+      router =
+        { pkgs, nodes, ... }:
+        { virtualisation.vlans = [ 1 2 ];
+          networking.nat.enable = true;
+          networking.nat.internalInterfaces = [ "eth2" ];
+          networking.nat.externalInterface = "eth1";
+          networking.firewall.enable = true;
+          networking.firewall.trustedInterfaces = [ "eth2" ];
+          networking.interfaces.eth1.ipv4.addresses = [
+            { address = externalRouterAddress; prefixLength = 24; }
+          ];
+          networking.interfaces.eth2.ipv4.addresses = [
+            { address = internalRouterAddress; prefixLength = 24; }
+          ];
+          services.miniupnpd = {
+            enable = true;
+            externalInterface = "eth1";
+            internalIPs = [ "eth2" ];
+            appendConfig = ''
+              ext_ip=${externalRouterAddress}
+            '';
+          };
+        };
+
+      client1 =
+        { pkgs, nodes, ... }:
+        { environment.systemPackages = [ pkgs.miniupnpc pkgs.netcat ];
+          virtualisation.vlans = [ 2 ];
+          networking.defaultGateway = internalRouterAddress;
+          networking.interfaces.eth1.ipv4.addresses = [
+            { address = internalClient1Address; prefixLength = 24; }
+          ];
+          networking.firewall.enable = false;
+
+          services.httpd.enable = true;
+          services.httpd.listen = [{ ip = "*"; port = 9000; }];
+          services.httpd.adminAddr = "foo@example.org";
+          services.httpd.documentRoot = "/tmp";
+        };
+
+      client2 =
+        { pkgs, ... }:
+        { environment.systemPackages = [ pkgs.miniupnpc ];
+          virtualisation.vlans = [ 1 ];
+          networking.interfaces.eth1.ipv4.addresses = [
+            { address = externalClient2Address; prefixLength = 24; }
+          ];
+          networking.firewall.enable = false;
+        };
+    };
+
+  testScript =
+    { nodes, ... }:
+    ''
+      startAll;
+
+      # Wait for network and miniupnpd.
+      $router->waitForUnit("network-online.target");
+      # $router->waitForUnit("nat");
+      $router->waitForUnit("firewall.service");
+      $router->waitForUnit("miniupnpd");
+
+      $client1->waitForUnit("network-online.target");
+
+      $client1->succeed("upnpc -a ${internalClient1Address} 9000 9000 TCP");
+
+      $client1->waitForUnit("httpd");
+      $client2->waitUntilSucceeds("curl http://${externalRouterAddress}:9000/");
+    '';
+
+})
diff --git a/nixos/tests/yabar.nix b/nixos/tests/yabar.nix
index 06fe5bc2b278..bbc0cf4c7dd7 100644
--- a/nixos/tests/yabar.nix
+++ b/nixos/tests/yabar.nix
@@ -8,18 +8,26 @@ with lib;
     maintainers = [ ma27 ];
   };
 
-  nodes.yabar = {
+  machine = {
     imports = [ ./common/x11.nix ./common/user-account.nix ];
 
     services.xserver.displayManager.auto.user = "bob";
 
     programs.yabar.enable = true;
+    programs.yabar.bars = {
+      top.indicators.date.exec = "YABAR_DATE";
+    };
   };
 
   testScript = ''
-    $yabar->start;
-    $yabar->waitForX;
+    $machine->start;
+    $machine->waitForX;
 
-    $yabar->waitForUnit("yabar.service", "bob");
+    # confirm proper startup
+    $machine->waitForUnit("yabar.service", "bob");
+    $machine->sleep(10);
+    $machine->waitForUnit("yabar.service", "bob");
+
+    $machine->screenshot("top_bar");
   '';
 })