about summary refs log tree commit diff
path: root/nixos
diff options
context:
space:
mode:
Diffstat (limited to 'nixos')
-rw-r--r--nixos/doc/manual/configuration/configuration.xml1
-rw-r--r--nixos/doc/manual/configuration/gpu-accel.xml258
-rw-r--r--nixos/doc/manual/configuration/profiles/demo.xml3
-rw-r--r--nixos/doc/manual/configuration/x-windows.xml46
-rw-r--r--nixos/doc/manual/development/freeform-modules.xml68
-rw-r--r--nixos/doc/manual/development/running-nixos-tests-interactively.xml9
-rw-r--r--nixos/doc/manual/development/settings-options.xml216
-rw-r--r--nixos/doc/manual/development/writing-modules.xml2
-rw-r--r--nixos/doc/manual/development/writing-nixos-tests.xml20
-rw-r--r--nixos/doc/manual/installation/installing-pxe.xml2
-rw-r--r--nixos/doc/manual/installation/installing-virtualbox-guest.xml2
-rw-r--r--nixos/doc/manual/installation/installing.xml6
-rw-r--r--nixos/doc/manual/man-nixos-build-vms.xml6
-rw-r--r--nixos/doc/manual/man-nixos-enter.xml12
-rw-r--r--nixos/doc/manual/man-nixos-rebuild.xml2
-rw-r--r--nixos/doc/manual/release-notes/rl-2003.xml2
-rw-r--r--nixos/doc/manual/release-notes/rl-2009.xml326
-rw-r--r--nixos/lib/make-disk-image.nix82
-rw-r--r--nixos/lib/make-ext4-fs.nix42
-rw-r--r--nixos/lib/make-iso9660-image.nix4
-rw-r--r--nixos/lib/make-iso9660-image.sh14
-rw-r--r--nixos/lib/make-options-doc/default.nix6
-rw-r--r--nixos/lib/qemu-flags.nix11
-rw-r--r--nixos/lib/test-driver/Logger.pm8
-rw-r--r--nixos/lib/test-driver/test-driver.py47
-rw-r--r--nixos/lib/testing-python.nix2
-rw-r--r--nixos/lib/utils.nix8
-rw-r--r--nixos/maintainers/scripts/azure-new/.gitignore2
-rw-r--r--nixos/maintainers/scripts/azure-new/README.md2
-rwxr-xr-xnixos/maintainers/scripts/azure-new/upload-image.sh4
-rw-r--r--nixos/maintainers/scripts/ec2/amazon-image.nix4
-rwxr-xr-xnixos/maintainers/scripts/ec2/create-amis.sh2
-rw-r--r--nixos/modules/config/appstream.nix8
-rw-r--r--nixos/modules/config/fonts/fontconfig-penultimate.nix292
-rw-r--r--nixos/modules/config/fonts/fontconfig.nix94
-rw-r--r--nixos/modules/config/no-x-libs.nix2
-rw-r--r--nixos/modules/config/system-path.nix5
-rw-r--r--nixos/modules/config/update-users-groups.pl55
-rw-r--r--nixos/modules/config/users-groups.nix93
-rw-r--r--nixos/modules/hardware/bladeRF.nix2
-rw-r--r--nixos/modules/hardware/ckb-next.nix1
-rw-r--r--nixos/modules/hardware/device-tree.nix13
-rw-r--r--nixos/modules/hardware/logitech.nix94
-rw-r--r--nixos/modules/hardware/onlykey.nix2
-rw-r--r--nixos/modules/hardware/tuxedo-keyboard.nix4
-rw-r--r--nixos/modules/hardware/video/hidpi.nix16
-rw-r--r--nixos/modules/hardware/video/uvcvideo/default.nix2
-rw-r--r--nixos/modules/hardware/xpadneo.nix29
-rw-r--r--nixos/modules/i18n/input-method/ibus.nix2
-rw-r--r--nixos/modules/i18n/input-method/uim.nix2
-rw-r--r--nixos/modules/installer/cd-dvd/installation-cd-graphical-gnome.nix20
-rw-r--r--nixos/modules/installer/cd-dvd/installation-cd-graphical-plasma5.nix4
-rw-r--r--nixos/modules/installer/cd-dvd/iso-image.nix9
-rw-r--r--nixos/modules/installer/cd-dvd/sd-image-aarch64.nix8
-rw-r--r--nixos/modules/installer/cd-dvd/sd-image-armv7l-multiplatform.nix8
-rw-r--r--nixos/modules/installer/cd-dvd/sd-image-raspberrypi.nix8
-rw-r--r--nixos/modules/installer/cd-dvd/sd-image.nix2
-rw-r--r--nixos/modules/installer/cd-dvd/system-tarball-pc-readme.txt2
-rw-r--r--nixos/modules/installer/tools/nix-fallback-paths.nix8
-rw-r--r--nixos/modules/installer/tools/nixos-build-vms/nixos-build-vms.sh2
-rw-r--r--nixos/modules/installer/tools/nixos-enter.sh2
-rw-r--r--nixos/modules/installer/tools/nixos-generate-config.pl4
-rw-r--r--nixos/modules/installer/tools/nixos-install.sh13
-rw-r--r--nixos/modules/installer/tools/nixos-rebuild.sh4
-rw-r--r--nixos/modules/installer/tools/nixos-version.sh2
-rw-r--r--nixos/modules/installer/tools/tools.nix5
-rw-r--r--nixos/modules/misc/documentation.nix38
-rw-r--r--nixos/modules/misc/ids.nix10
-rw-r--r--nixos/modules/module-list.nix27
-rw-r--r--nixos/modules/profiles/base.nix1
-rw-r--r--nixos/modules/profiles/demo.nix10
-rw-r--r--nixos/modules/programs/autojump.nix4
-rw-r--r--nixos/modules/programs/ccache.nix2
-rw-r--r--nixos/modules/programs/dconf.nix37
-rw-r--r--nixos/modules/programs/fish.nix3
-rw-r--r--nixos/modules/programs/freetds.nix4
-rw-r--r--nixos/modules/programs/gnupg.nix3
-rw-r--r--nixos/modules/programs/steam.nix25
-rw-r--r--nixos/modules/rename.nix8
-rw-r--r--nixos/modules/security/acme.nix60
-rw-r--r--nixos/modules/security/pam.nix47
-rw-r--r--nixos/modules/security/tpm2.nix1
-rw-r--r--nixos/modules/security/wrappers/default.nix3
-rw-r--r--nixos/modules/services/audio/icecast.nix4
-rw-r--r--nixos/modules/services/audio/roon-server.nix6
-rw-r--r--nixos/modules/services/audio/snapserver.nix166
-rw-r--r--nixos/modules/services/audio/spotifyd.nix2
-rw-r--r--nixos/modules/services/backup/bacula.nix36
-rw-r--r--nixos/modules/services/backup/restic.nix66
-rw-r--r--nixos/modules/services/backup/zfs-replication.nix2
-rw-r--r--nixos/modules/services/computing/slurm/slurm.nix4
-rw-r--r--nixos/modules/services/computing/torque/mom.nix2
-rw-r--r--nixos/modules/services/computing/torque/server.nix2
-rw-r--r--nixos/modules/services/continuous-integration/buildbot/worker.nix11
-rw-r--r--nixos/modules/services/continuous-integration/gitlab-runner.nix93
-rw-r--r--nixos/modules/services/databases/mysql.nix260
-rw-r--r--nixos/modules/services/databases/openldap.nix30
-rw-r--r--nixos/modules/services/databases/postgresql.nix89
-rw-r--r--nixos/modules/services/databases/riak-cs.nix2
-rw-r--r--nixos/modules/services/desktops/flatpak.nix1
-rw-r--r--nixos/modules/services/desktops/malcontent.nix5
-rw-r--r--nixos/modules/services/development/jupyter/default.nix28
-rw-r--r--nixos/modules/services/development/jupyterhub/default.nix190
-rw-r--r--nixos/modules/services/editors/emacs.nix47
-rw-r--r--nixos/modules/services/editors/emacs.xml12
-rw-r--r--nixos/modules/services/games/minetest-server.nix16
-rw-r--r--nixos/modules/services/games/terraria.nix2
-rw-r--r--nixos/modules/services/hardware/thinkfan.nix2
-rw-r--r--nixos/modules/services/hardware/tlp.nix59
-rw-r--r--nixos/modules/services/hardware/trezord.nix4
-rw-r--r--nixos/modules/services/hardware/undervolt.nix25
-rw-r--r--nixos/modules/services/logging/logrotate.nix146
-rw-r--r--nixos/modules/services/logging/logstash.nix6
-rw-r--r--nixos/modules/services/mail/dovecot.nix33
-rw-r--r--nixos/modules/services/mail/pfix-srsd.nix2
-rw-r--r--nixos/modules/services/mail/postfix.nix90
-rw-r--r--nixos/modules/services/mail/roundcube.nix18
-rw-r--r--nixos/modules/services/misc/gitea.nix144
-rw-r--r--nixos/modules/services/misc/gitlab.nix68
-rw-r--r--nixos/modules/services/misc/gitolite.nix33
-rw-r--r--nixos/modules/services/misc/gollum.nix9
-rw-r--r--nixos/modules/services/misc/jellyfin.nix15
-rw-r--r--nixos/modules/services/misc/mathics.nix54
-rw-r--r--nixos/modules/services/misc/matrix-synapse.nix2
-rw-r--r--nixos/modules/services/misc/matrix-synapse.xml38
-rw-r--r--nixos/modules/services/misc/mautrix-telegram.nix2
-rw-r--r--nixos/modules/services/misc/mesos-master.nix125
-rw-r--r--nixos/modules/services/misc/mesos-slave.nix220
-rw-r--r--nixos/modules/services/misc/nix-daemon.nix164
-rw-r--r--nixos/modules/services/misc/pinnwand.nix78
-rw-r--r--nixos/modules/services/misc/siproxd.nix8
-rw-r--r--nixos/modules/services/misc/tzupdate.nix4
-rw-r--r--nixos/modules/services/misc/zigbee2mqtt.nix98
-rw-r--r--nixos/modules/services/monitoring/cadvisor.nix2
-rw-r--r--nixos/modules/services/monitoring/dd-agent/dd-agent.nix4
-rw-r--r--nixos/modules/services/monitoring/do-agent.nix17
-rw-r--r--nixos/modules/services/monitoring/monit.nix18
-rw-r--r--nixos/modules/services/monitoring/netdata.nix45
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters.nix1
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/modemmanager.nix33
-rw-r--r--nixos/modules/services/monitoring/smartd.nix18
-rw-r--r--nixos/modules/services/monitoring/teamviewer.nix2
-rw-r--r--nixos/modules/services/monitoring/zabbix-agent.nix41
-rw-r--r--nixos/modules/services/monitoring/zabbix-proxy.nix75
-rw-r--r--nixos/modules/services/monitoring/zabbix-server.nix72
-rw-r--r--nixos/modules/services/network-filesystems/ipfs.nix22
-rw-r--r--nixos/modules/services/networking/3proxy.nix2
-rw-r--r--nixos/modules/services/networking/bitcoind.nix206
-rw-r--r--nixos/modules/services/networking/blockbook-frontend.nix275
-rw-r--r--nixos/modules/services/networking/corerad.nix3
-rw-r--r--nixos/modules/services/networking/dhcpd.nix12
-rw-r--r--nixos/modules/services/networking/gateone.nix2
-rw-r--r--nixos/modules/services/networking/jicofo.nix152
-rw-r--r--nixos/modules/services/networking/jitsi-videobridge.nix276
-rw-r--r--nixos/modules/services/networking/kresd.nix13
-rw-r--r--nixos/modules/services/networking/mstpd.nix2
-rw-r--r--nixos/modules/services/networking/ncdns.nix278
-rw-r--r--nixos/modules/services/networking/nextdns.nix44
-rw-r--r--nixos/modules/services/networking/nghttpx/default.nix6
-rw-r--r--nixos/modules/services/networking/nsd.nix7
-rw-r--r--nixos/modules/services/networking/onedrive.nix72
-rw-r--r--nixos/modules/services/networking/onedrive.xml34
-rw-r--r--nixos/modules/services/networking/prosody.nix2
-rw-r--r--nixos/modules/services/networking/radicale.nix11
-rw-r--r--nixos/modules/services/networking/resilio.nix6
-rw-r--r--nixos/modules/services/networking/skydns.nix2
-rw-r--r--nixos/modules/services/networking/sslh.nix18
-rw-r--r--nixos/modules/services/networking/supplicant.nix26
-rw-r--r--nixos/modules/services/networking/tinc.nix16
-rw-r--r--nixos/modules/services/networking/trickster.nix5
-rw-r--r--nixos/modules/services/networking/unifi.nix2
-rw-r--r--nixos/modules/services/networking/websockify.nix6
-rw-r--r--nixos/modules/services/networking/wg-quick.nix20
-rw-r--r--nixos/modules/services/networking/wireguard.nix15
-rw-r--r--nixos/modules/services/networking/wpa_supplicant.nix2
-rw-r--r--nixos/modules/services/networking/xandikos.nix4
-rw-r--r--nixos/modules/services/networking/yggdrasil.nix5
-rw-r--r--nixos/modules/services/networking/yggdrasil.xml157
-rw-r--r--nixos/modules/services/scheduling/chronos.nix54
-rw-r--r--nixos/modules/services/scheduling/marathon.nix98
-rw-r--r--nixos/modules/services/security/haveged.nix18
-rw-r--r--nixos/modules/services/security/nginx-sso.nix11
-rw-r--r--nixos/modules/services/security/oauth2_proxy.nix12
-rw-r--r--nixos/modules/services/security/privacyidea.nix1
-rw-r--r--nixos/modules/services/security/tor.nix2
-rw-r--r--nixos/modules/services/security/usbguard.nix124
-rw-r--r--nixos/modules/services/security/yubikey-agent.nix60
-rw-r--r--nixos/modules/services/system/earlyoom.nix1
-rw-r--r--nixos/modules/services/torrent/transmission.nix451
-rw-r--r--nixos/modules/services/video/mirakurun.nix165
-rw-r--r--nixos/modules/services/wayland/cage.nix2
-rw-r--r--nixos/modules/services/web-apps/codimd.nix4
-rw-r--r--nixos/modules/services/web-apps/convos.nix72
-rw-r--r--nixos/modules/services/web-apps/dokuwiki.nix28
-rw-r--r--nixos/modules/services/web-apps/jitsi-meet.nix333
-rw-r--r--nixos/modules/services/web-apps/moodle.nix14
-rw-r--r--nixos/modules/services/web-apps/nextcloud.nix248
-rw-r--r--nixos/modules/services/web-apps/nextcloud.xml61
-rw-r--r--nixos/modules/services/web-apps/pgpkeyserver-lite.nix2
-rw-r--r--nixos/modules/services/web-apps/rss-bridge.nix127
-rw-r--r--nixos/modules/services/web-apps/trilium.nix2
-rw-r--r--nixos/modules/services/web-apps/tt-rss.nix2
-rw-r--r--nixos/modules/services/web-servers/apache-httpd/default.nix32
-rw-r--r--nixos/modules/services/web-servers/jboss/builder.sh12
-rw-r--r--nixos/modules/services/web-servers/meguca.nix174
-rw-r--r--nixos/modules/services/web-servers/molly-brown.nix117
-rw-r--r--nixos/modules/services/web-servers/nginx/default.nix12
-rw-r--r--nixos/modules/services/web-servers/shellinabox.nix2
-rw-r--r--nixos/modules/services/web-servers/traefik.nix4
-rw-r--r--nixos/modules/services/web-servers/unit/default.nix5
-rw-r--r--nixos/modules/services/x11/colord.nix2
-rw-r--r--nixos/modules/services/x11/desktop-managers/plasma5.nix2
-rw-r--r--nixos/modules/services/x11/display-managers/default.nix33
-rw-r--r--nixos/modules/services/x11/display-managers/gdm.nix66
-rw-r--r--nixos/modules/services/x11/display-managers/lightdm-greeters/pantheon.nix2
-rw-r--r--nixos/modules/services/x11/display-managers/lightdm.nix66
-rw-r--r--nixos/modules/services/x11/display-managers/sddm.nix63
-rw-r--r--nixos/modules/services/x11/imwheel.nix3
-rw-r--r--nixos/modules/services/x11/urserver.nix38
-rw-r--r--nixos/modules/services/x11/window-managers/qtile.nix2
-rw-r--r--nixos/modules/services/x11/window-managers/xmonad.nix11
-rw-r--r--nixos/modules/system/activation/top-level.nix19
-rw-r--r--nixos/modules/system/boot/emergency-mode.nix2
-rw-r--r--nixos/modules/system/boot/initrd-network.nix2
-rw-r--r--nixos/modules/system/boot/initrd-openvpn.nix81
-rw-r--r--nixos/modules/system/boot/kernel_config.nix2
-rw-r--r--nixos/modules/system/boot/loader/generations-dir/generations-dir-builder.sh2
-rw-r--r--nixos/modules/system/boot/loader/generic-extlinux-compatible/default.nix29
-rw-r--r--nixos/modules/system/boot/loader/generic-extlinux-compatible/extlinux-conf-builder.sh17
-rw-r--r--nixos/modules/system/boot/loader/grub/grub.nix106
-rw-r--r--nixos/modules/system/boot/loader/grub/install-grub.pl163
-rw-r--r--nixos/modules/system/boot/loader/init-script/init-script-builder.sh2
-rw-r--r--nixos/modules/system/boot/loader/systemd-boot/systemd-boot.nix4
-rw-r--r--nixos/modules/system/boot/luksroot.nix34
-rw-r--r--nixos/modules/system/boot/modprobe.nix2
-rw-r--r--nixos/modules/system/boot/networkd.nix1492
-rw-r--r--nixos/modules/system/boot/plymouth.nix3
-rw-r--r--nixos/modules/system/boot/stage-1-init.sh22
-rw-r--r--nixos/modules/system/boot/stage-1.nix19
-rw-r--r--nixos/modules/system/boot/stage-2-init.sh2
-rw-r--r--nixos/modules/system/boot/stage-2.nix10
-rw-r--r--nixos/modules/system/boot/systemd-nspawn.nix6
-rw-r--r--nixos/modules/system/boot/systemd-unit-options.nix10
-rw-r--r--nixos/modules/system/boot/systemd.nix144
-rw-r--r--nixos/modules/system/boot/tmp.nix2
-rw-r--r--nixos/modules/system/etc/make-etc.sh7
-rw-r--r--nixos/modules/tasks/auto-upgrade.nix98
-rw-r--r--nixos/modules/tasks/bcache.nix2
-rw-r--r--nixos/modules/tasks/encrypted-devices.nix19
-rw-r--r--nixos/modules/tasks/filesystems/zfs.nix51
-rw-r--r--nixos/modules/tasks/lvm.nix67
-rw-r--r--nixos/modules/tasks/network-interfaces-scripted.nix4
-rw-r--r--nixos/modules/tasks/network-interfaces.nix3
-rw-r--r--nixos/modules/testing/test-instrumentation.nix18
-rw-r--r--nixos/modules/virtualisation/azure-image.nix2
-rw-r--r--nixos/modules/virtualisation/containers.nix63
-rw-r--r--nixos/modules/virtualisation/cri-o.nix4
-rw-r--r--nixos/modules/virtualisation/docker.nix1
-rw-r--r--nixos/modules/virtualisation/libvirtd.nix4
-rw-r--r--nixos/modules/virtualisation/podman.nix28
-rw-r--r--nixos/modules/virtualisation/qemu-vm.nix154
-rw-r--r--nixos/modules/virtualisation/railcar.nix12
-rw-r--r--nixos/modules/virtualisation/virtualbox-guest.nix2
-rw-r--r--nixos/modules/virtualisation/virtualbox-image.nix66
-rw-r--r--nixos/release.nix5
-rw-r--r--nixos/tests/acme.nix62
-rw-r--r--nixos/tests/all-tests.nix24
-rw-r--r--nixos/tests/bcachefs.nix2
-rw-r--r--nixos/tests/bitcoind.nix46
-rw-r--r--nixos/tests/bittorrent.nix24
-rw-r--r--nixos/tests/bitwarden.nix188
-rw-r--r--nixos/tests/blockbook-frontend.nix28
-rw-r--r--nixos/tests/borgbackup.nix2
-rw-r--r--nixos/tests/ceph-multi-node.nix6
-rw-r--r--nixos/tests/ceph-single-node.nix6
-rw-r--r--nixos/tests/common/auto.nix4
-rw-r--r--nixos/tests/consul.nix136
-rw-r--r--nixos/tests/containers-portforward.nix2
-rw-r--r--nixos/tests/convos.nix30
-rw-r--r--nixos/tests/corerad.nix2
-rw-r--r--nixos/tests/docker-preloader.nix4
-rw-r--r--nixos/tests/docker-tools.nix65
-rw-r--r--nixos/tests/docker.nix4
-rw-r--r--nixos/tests/dokuwiki.nix24
-rw-r--r--nixos/tests/firejail.nix82
-rw-r--r--nixos/tests/gnome3-xorg.nix6
-rw-r--r--nixos/tests/gnome3.nix6
-rw-r--r--nixos/tests/graphite.nix11
-rw-r--r--nixos/tests/grub.nix60
-rw-r--r--nixos/tests/home-assistant.nix21
-rw-r--r--nixos/tests/hydra/common.nix1
-rw-r--r--nixos/tests/ihatemoney.nix13
-rw-r--r--nixos/tests/initrd-network-openvpn/default.nix145
-rw-r--r--nixos/tests/initrd-network-openvpn/initrd.ovpn29
-rw-r--r--nixos/tests/initrd-network-openvpn/shared.key21
-rw-r--r--nixos/tests/installed-tests/default.nix6
-rw-r--r--nixos/tests/installed-tests/flatpak.nix9
-rw-r--r--nixos/tests/installed-tests/ibus.nix14
-rw-r--r--nixos/tests/installed-tests/ostree.nix11
-rw-r--r--nixos/tests/installer.nix21
-rw-r--r--nixos/tests/jitsi-meet.nix55
-rw-r--r--nixos/tests/kubernetes/base.nix1
-rw-r--r--nixos/tests/leaps.nix2
-rw-r--r--nixos/tests/lorri/default.nix2
-rw-r--r--nixos/tests/mathics.nix20
-rw-r--r--nixos/tests/mesos.nix92
-rw-r--r--nixos/tests/mesos_test.py72
-rw-r--r--nixos/tests/misc.nix32
-rw-r--r--nixos/tests/molly-brown.nix71
-rw-r--r--nixos/tests/mongodb.nix6
-rw-r--r--nixos/tests/mysql/mysql.nix22
-rw-r--r--nixos/tests/ncdns.nix77
-rw-r--r--nixos/tests/networking.nix21
-rw-r--r--nixos/tests/nextcloud/basic.nix1
-rw-r--r--nixos/tests/nextcloud/with-mysql-and-memcached.nix1
-rw-r--r--nixos/tests/nextcloud/with-postgresql-and-redis.nix1
-rw-r--r--nixos/tests/nfs/kerberos.nix4
-rw-r--r--nixos/tests/pinnwand.nix86
-rw-r--r--nixos/tests/plasma5.nix2
-rw-r--r--nixos/tests/podman.nix3
-rw-r--r--nixos/tests/postfix-raise-smtpd-tls-security-level.nix44
-rw-r--r--nixos/tests/postfix.nix76
-rw-r--r--nixos/tests/postgresql-wal-receiver.nix4
-rw-r--r--nixos/tests/prometheus-exporters.nix25
-rw-r--r--nixos/tests/prometheus.nix7
-rw-r--r--nixos/tests/pt2-clone.nix35
-rw-r--r--nixos/tests/radicale.nix28
-rw-r--r--nixos/tests/restic.nix55
-rw-r--r--nixos/tests/sddm.nix4
-rw-r--r--nixos/tests/shattered-pixel-dungeon.nix29
-rw-r--r--nixos/tests/slurm.nix93
-rw-r--r--nixos/tests/snapcast.nix58
-rw-r--r--nixos/tests/sslh.nix83
-rw-r--r--nixos/tests/sudo.nix2
-rw-r--r--nixos/tests/syncthing-init.nix3
-rw-r--r--nixos/tests/syncthing.nix65
-rw-r--r--nixos/tests/systemd-boot.nix32
-rw-r--r--nixos/tests/systemd-networkd-vrf.nix24
-rw-r--r--nixos/tests/systemd.nix27
-rw-r--r--nixos/tests/taskserver.nix310
-rw-r--r--nixos/tests/tiddlywiki.nix2
-rw-r--r--nixos/tests/transmission.nix2
-rw-r--r--nixos/tests/trezord.nix5
-rw-r--r--nixos/tests/trickster.nix6
-rw-r--r--nixos/tests/virtualbox.nix1
-rw-r--r--nixos/tests/xandikos.nix6
-rw-r--r--nixos/tests/xfce.nix4
-rw-r--r--nixos/tests/yggdrasil.nix42
-rw-r--r--nixos/tests/zfs.nix23
-rw-r--r--nixos/tests/zigbee2mqtt.nix19
350 files changed, 10890 insertions, 4106 deletions
diff --git a/nixos/doc/manual/configuration/configuration.xml b/nixos/doc/manual/configuration/configuration.xml
index 507d28814ead3..6eb8f50bacac6 100644
--- a/nixos/doc/manual/configuration/configuration.xml
+++ b/nixos/doc/manual/configuration/configuration.xml
@@ -18,6 +18,7 @@
  <xi:include href="user-mgmt.xml" />
  <xi:include href="file-systems.xml" />
  <xi:include href="x-windows.xml" />
+ <xi:include href="gpu-accel.xml" />
  <xi:include href="xfce.xml" />
  <xi:include href="networking.xml" />
  <xi:include href="linux-kernel.xml" />
diff --git a/nixos/doc/manual/configuration/gpu-accel.xml b/nixos/doc/manual/configuration/gpu-accel.xml
new file mode 100644
index 0000000000000..251e5c26ba44c
--- /dev/null
+++ b/nixos/doc/manual/configuration/gpu-accel.xml
@@ -0,0 +1,258 @@
+<chapter xmlns="http://docbook.org/ns/docbook"
+         xmlns:xlink="http://www.w3.org/1999/xlink"
+         xmlns:xi="http://www.w3.org/2001/XInclude"
+         version="5.0"
+         xml:id="sec-gpu-accel">
+  <title>GPU acceleration</title>
+
+  <para>
+    NixOS provides various APIs that benefit from GPU hardware
+    acceleration, such as VA-API and VDPAU for video playback; OpenGL and
+    Vulkan for 3D graphics; and OpenCL for general-purpose computing.
+    This chapter describes how to set up GPU hardware acceleration (as far
+    as this is not done automatically) and how to verify that hardware
+    acceleration is indeed used.
+  </para>
+
+  <para>
+    Most of the aforementioned APIs are agnostic with regards to which
+    display server is used. Consequently, these instructions should apply
+    both to the X Window System and Wayland compositors.
+  </para>
+
+  <section xml:id="sec-gpu-accel-opencl">
+    <title>OpenCL</title>
+
+    <para>
+      <link xlink:href="https://en.wikipedia.org/wiki/OpenCL">OpenCL</link> is a
+      general compute API. It is used by various applications such as
+      Blender and Darktable to accelerate certain operations.
+    </para>
+
+    <para>
+      OpenCL applications load drivers through the <emphasis>Installable Client
+      Driver</emphasis> (ICD) mechanism. In this mechanism, an ICD file
+      specifies the path to the OpenCL driver for a particular GPU family.
+      In NixOS, there are two ways to make ICD files visible to the ICD
+      loader. The first is through the <varname>OCL_ICD_VENDORS</varname>
+      environment variable. This variable can contain a directory which
+      is scanned by the ICL loader for ICD files. For example:
+
+      <screen><prompt>$</prompt> export \
+  OCL_ICD_VENDORS=`nix-build '&lt;nixpkgs&gt;' --no-out-link -A rocm-opencl-icd`/etc/OpenCL/vendors/</screen>
+    </para>
+
+    <para>
+      The second mechanism is to add the OpenCL driver package to
+      <xref linkend="opt-hardware.opengl.extraPackages"/>. This links the
+      ICD file under <filename>/run/opengl-driver</filename>, where it will
+      be visible to the ICD loader.
+    </para>
+
+    <para>
+      The proper installation of OpenCL drivers can be verified through
+      the <command>clinfo</command> command of the <package>clinfo</package>
+      package. This command will report the number of hardware devices
+      that is found and give detailed information for each device:
+    </para>
+
+    <screen><prompt>$</prompt> clinfo | head -n3
+Number of platforms  1
+Platform Name        AMD Accelerated Parallel Processing
+Platform Vendor      Advanced Micro Devices, Inc.</screen>
+
+    <section xml:id="sec-gpu-accel-opencl-amd">
+      <title>AMD</title>
+
+      <para>
+	Modern AMD <link
+	xlink:href="https://en.wikipedia.org/wiki/Graphics_Core_Next">Graphics
+	Core Next</link> (GCN) GPUs are supported through the
+	<package>rocm-opencl-icd</package> package. Adding this package to
+	<xref linkend="opt-hardware.opengl.extraPackages"/> enables OpenCL
+	support:
+
+	<programlisting><xref linkend="opt-hardware.opengl.extraPackages"/> = [
+  rocm-opencl-icd
+];</programlisting>
+      </para>
+    </section>
+
+    <section xml:id="sec-gpu-accel-opencl-intel">
+      <title>Intel</title>
+
+      <para>
+       <link
+         xlink:href="https://en.wikipedia.org/wiki/List_of_Intel_graphics_processing_units#Gen8">Intel
+       Gen8 and later GPUs</link> are supported by the Intel NEO OpenCL
+       runtime that is provided by the
+       <package>intel-compute-runtime</package> package. For Gen7 GPUs,
+       the deprecated Beignet runtime can be used, which is provided
+       by the <package>beignet</package> package. The proprietary Intel
+       OpenCL runtime, in the <package>intel-ocl</package> package, is
+       an alternative for Gen7 GPUs.
+      </para>
+
+      <para>
+       The <package>intel-compute-runtime</package>, <package>beignet</package>,
+       or <package>intel-ocl</package> package can be added to
+       <xref linkend="opt-hardware.opengl.extraPackages"/> to enable OpenCL
+       support. For example, for Gen8 and later GPUs, the following
+       configuration can be used:
+
+	      <programlisting><xref linkend="opt-hardware.opengl.extraPackages"/> = [
+  intel-compute-runtime
+];</programlisting>
+
+      </para>
+    </section>
+  </section>
+
+  <section xml:id="sec-gpu-accel-vulkan">
+    <title>Vulkan</title>
+
+    <para>
+      <link xlink:href="https://en.wikipedia.org/wiki/Vulkan_(API)">Vulkan</link> is a
+      graphics and compute API for GPUs. It is used directly by games or indirectly though
+      compatibility layers like <link xlink:href="https://github.com/doitsujin/dxvk/wiki">DXVK</link>.
+    </para>
+
+    <para>
+     By default, if <xref linkend="opt-hardware.opengl.driSupport"/> is enabled,
+     <package>mesa</package> is installed and provides Vulkan for supported hardware.
+    </para>
+
+    <para>
+      Similar to OpenCL, Vulkan drivers are loaded through the <emphasis>Installable Client
+      Driver</emphasis> (ICD) mechanism. ICD files for Vulkan are JSON files that specify
+      the path to the driver library and the supported Vulkan version. All successfully
+      loaded drivers are exposed to the application as different GPUs.
+      In NixOS, there are two ways to make ICD files visible to Vulkan applications: an
+      environment variable and a module option.
+    </para>
+
+    <para>
+      The first option is through the <varname>VK_ICD_FILENAMES</varname>
+      environment variable. This variable can contain multiple JSON files, separated by
+      <literal>:</literal>. For example:
+
+      <screen><prompt>$</prompt> export \
+  VK_ICD_FILENAMES=`nix-build '&lt;nixpkgs&gt;' --no-out-link -A amdvlk`/share/vulkan/icd.d/amd_icd64.json</screen>
+    </para>
+
+    <para>
+      The second mechanism is to add the Vulkan driver package to
+      <xref linkend="opt-hardware.opengl.extraPackages"/>. This links the
+      ICD file under <filename>/run/opengl-driver</filename>, where it will
+      be visible to the ICD loader.
+    </para>
+
+    <para>
+      The proper installation of Vulkan drivers can be verified through
+      the <command>vulkaninfo</command> command of the <package>vulkan-tools</package>
+      package. This command will report the hardware devices and drivers found,
+      in this example output amdvlk and radv:
+    </para>
+
+    <screen><prompt>$</prompt> vulkaninfo | grep GPU
+                GPU id  : 0 (Unknown AMD GPU)
+                GPU id  : 1 (AMD RADV NAVI10 (LLVM 9.0.1))
+     ...
+GPU0:
+        deviceType     = PHYSICAL_DEVICE_TYPE_DISCRETE_GPU
+        deviceName     = Unknown AMD GPU
+GPU1:
+        deviceType     = PHYSICAL_DEVICE_TYPE_DISCRETE_GPU</screen>
+
+    <para>
+      A simple graphical application that uses Vulkan is <command>vkcube</command>
+      from the <package>vulkan-tools</package> package.
+    </para>
+
+    <section xml:id="sec-gpu-accel-vulkan-amd">
+      <title>AMD</title>
+
+      <para>
+	Modern AMD <link
+	xlink:href="https://en.wikipedia.org/wiki/Graphics_Core_Next">Graphics
+	Core Next</link> (GCN) GPUs are supported through either radv, which is
+	part of <package>mesa</package>, or the <package>amdvlk</package> package.
+	Adding the <package>amdvlk</package> package to
+	<xref linkend="opt-hardware.opengl.extraPackages"/> makes both drivers
+	available for applications and lets them choose. A specific driver can
+	be forced as follows:
+
+	<programlisting><xref linkend="opt-hardware.opengl.extraPackages"/> = [
+  <package>amdvlk</package>
+];
+
+# For amdvlk
+<xref linkend="opt-environment.variables"/>.VK_ICD_FILENAMES =
+   "/run/opengl-driver/share/vulkan/icd.d/amd_icd64.json";
+# For radv
+<xref linkend="opt-environment.variables"/>.VK_ICD_FILENAMES =
+  "/run/opengl-driver/share/vulkan/icd.d/radeon_icd.x86_64.json";
+</programlisting>
+      </para>
+    </section>
+  </section>
+
+  <section xml:id="sec-gpu-accel-common-issues">
+   <title>Common issues</title>
+
+   <section xml:id="sec-gpu-accel-common-issues-permissions">
+    <title>User permissions</title>
+
+    <para>
+     Except where noted explicitly, it should not be necessary to
+     adjust user permissions to use these acceleration APIs. In the default
+     configuration, GPU devices have world-read/write permissions
+     (<filename>/dev/dri/renderD*</filename>) or are tagged as
+     <code>uaccess</code> (<filename>/dev/dri/card*</filename>).  The
+     access control lists of devices with the <varname>uaccess</varname>
+     tag will be updated automatically when a user logs in through
+     <command>systemd-logind</command>. For example, if the user
+     <emphasis>jane</emphasis> is logged in, the access control list
+     should look as follows:
+
+     <screen><prompt>$</prompt> getfacl /dev/dri/card0
+# file: dev/dri/card0
+# owner: root
+# group: video
+user::rw-
+user:jane:rw-
+group::rw-
+mask::rw-
+other::---</screen>
+
+     If you disabled (this functionality of) <command>systemd-logind</command>,
+     you may need to add the user to the <code>video</code> group and
+     log in again.
+    </para>
+   </section>
+
+   <section xml:id="sec-gpu-accel-common-issues-mixing-nixpkgs">
+    <title>Mixing different versions of nixpkgs</title>
+
+    <para>
+     The <emphasis>Installable Client Driver</emphasis> (ICD)
+     mechanism used by OpenCL and Vulkan loads runtimes into its address
+     space using <code>dlopen</code>. Mixing an ICD loader mechanism and
+     runtimes from different version of nixpkgs may not work. For example,
+     if the ICD loader uses an older version of <package>glibc</package>
+     than the runtime, the runtime may not be loadable due to
+     missing symbols. Unfortunately, the loader will generally be quiet
+     about such issues.
+    </para>
+
+    <para>
+     If you suspect that you are running into library version mismatches
+     between an ICL loader and a runtime, you could run an application with
+     the <code>LD_DEBUG</code> variable set to get more diagnostic
+     information. For example, OpenCL can be tested with
+     <code>LD_DEBUG=files clinfo</code>, which should report missing
+     symbols.
+    </para>
+   </section>
+  </section>
+</chapter>
diff --git a/nixos/doc/manual/configuration/profiles/demo.xml b/nixos/doc/manual/configuration/profiles/demo.xml
index 395a5ec357c90..bc801bb3dc5b8 100644
--- a/nixos/doc/manual/configuration/profiles/demo.xml
+++ b/nixos/doc/manual/configuration/profiles/demo.xml
@@ -9,7 +9,6 @@
   This profile just enables a <systemitem class="username">demo</systemitem>
   user, with password <literal>demo</literal>, uid <literal>1000</literal>,
   <systemitem class="groupname">wheel</systemitem> group and
-  <link linkend="opt-services.xserver.displayManager.sddm.autoLogin"> autologin
-  in the SDDM display manager</link>.
+  <link linkend="opt-services.xserver.displayManager.autoLogin">autologin in the SDDM display manager</link>.
  </para>
 </section>
diff --git a/nixos/doc/manual/configuration/x-windows.xml b/nixos/doc/manual/configuration/x-windows.xml
index 110712baf5f16..18f0be5e7f394 100644
--- a/nixos/doc/manual/configuration/x-windows.xml
+++ b/nixos/doc/manual/configuration/x-windows.xml
@@ -90,10 +90,50 @@
   using lightdm for a user <literal>alice</literal>:
 <programlisting>
 <xref linkend="opt-services.xserver.displayManager.lightdm.enable"/> = true;
-<xref linkend="opt-services.xserver.displayManager.lightdm.autoLogin.enable"/> = true;
-<xref linkend="opt-services.xserver.displayManager.lightdm.autoLogin.user"/> = "alice";
+<xref linkend="opt-services.xserver.displayManager.autoLogin.enable"/> = true;
+<xref linkend="opt-services.xserver.displayManager.autoLogin.user"/> = "alice";
 </programlisting>
-  The options are named identically for all other display managers.
+  </para>
+ </simplesect>
+ <simplesect xml:id="sec-x11--graphics-cards-intel">
+  <title>Intel Graphics drivers</title>
+  <para>
+   There are two choices for Intel Graphics drivers in X.org:
+   <literal>modesetting</literal> (included in the <package>xorg-server</package> itself)
+   and <literal>intel</literal> (provided by the package <package>xf86-video-intel</package>).
+  </para>
+  <para>
+   The default and recommended is <literal>modesetting</literal>.
+   It is a generic driver which uses the kernel
+   <link xlink:href="https://en.wikipedia.org/wiki/Mode_setting">mode setting</link>
+   (KMS) mechanism. It supports Glamor (2D graphics acceleration via OpenGL)
+   and is actively maintained but may perform worse in some cases (like in old chipsets).
+  </para>
+  <para>
+   The second driver, <literal>intel</literal>, is specific to Intel GPUs,
+   but not recommended by most distributions: it lacks several modern features
+   (for example, it doesn't support Glamor) and the package hasn't been officially
+   updated since 2015.
+  </para>
+  <para>
+   The results vary depending on the hardware, so you may have to try both drivers.
+   Use the option <xref linkend="opt-services.xserver.videoDrivers"/> to set one.
+   The recommended configuration for modern systems is:
+<programlisting>
+  <xref linkend="opt-services.xserver.videoDrivers"/> = [ "modesetting" ];
+  <xref linkend="opt-services.xserver.useGlamor"/> = true;
+</programlisting>
+   If you experience screen tearing no matter what, this configuration was
+   reported to resolve the issue:
+<programlisting>
+  <xref linkend="opt-services.xserver.videoDrivers"/> = [ "intel" ];
+  <xref linkend="opt-services.xserver.deviceSection"/> = ''
+    Option "DRI" "2"
+    Option "TearFree" "true"
+  '';
+</programlisting>
+   Note that this will likely downgrade the performance compared to
+  <literal>modesetting</literal> or <literal>intel</literal> with DRI 3 (default).
   </para>
  </simplesect>
  <simplesect xml:id="sec-x11-graphics-cards-nvidia">
diff --git a/nixos/doc/manual/development/freeform-modules.xml b/nixos/doc/manual/development/freeform-modules.xml
new file mode 100644
index 0000000000000..257e6b11bf014
--- /dev/null
+++ b/nixos/doc/manual/development/freeform-modules.xml
@@ -0,0 +1,68 @@
+<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-freeform-modules">
+ <title>Freeform modules</title>
+ <para>
+  Freeform modules allow you to define values for option paths that have not been declared explicitly. This can be used to add attribute-specific types to what would otherwise have to be <literal>attrsOf</literal> options in order to accept all attribute names.
+ </para>
+ <para>
+  This feature can be enabled by using the attribute <literal>freeformType</literal> to define a freeform type. By doing this, all assignments without an associated option will be merged using the freeform type and combined into the resulting <literal>config</literal> set. Since this feature nullifies name checking for entire option trees, it is only recommended for use in submodules.
+ </para>
+ <example xml:id="ex-freeform-module">
+  <title>Freeform submodule</title>
+  <para>
+   The following shows a submodule assigning a freeform type that allows arbitrary attributes with <literal>str</literal> values below <literal>settings</literal>, but also declares an option for the <literal>settings.port</literal> attribute to have it type-checked and assign a default value. See <xref linkend="ex-settings-typed-attrs"/> for a more complete example.
+  </para>
+ <programlisting>
+{ lib, config, ... }: {
+
+  options.settings = lib.mkOption {
+    type = lib.types.submodule {
+
+      freeformType = with lib.types; attrsOf str;
+
+      # We want this attribute to be checked for the correct type
+      options.port = lib.mkOption {
+        type = lib.types.port;
+        # Declaring the option also allows defining a default value
+        default = 8080;
+      };
+
+    };
+  };
+}
+ </programlisting>
+ <para>
+  And the following shows what such a module then allows
+ </para>
+ <programlisting>
+{
+  # Not a declared option, but the freeform type allows this
+  settings.logLevel = "debug";
+
+  # Not allowed because the the freeform type only allows strings
+  # settings.enable = true;
+
+  # Allowed because there is a port option declared
+  settings.port = 80;
+
+  # Not allowed because the port option doesn't allow strings
+  # settings.port = "443";
+}
+ </programlisting>
+ </example>
+ <note>
+  <para>
+   Freeform attributes cannot depend on other attributes of the same set without infinite recursion:
+<programlisting>
+{
+  # This throws infinite recursion encountered
+  settings.logLevel = lib.mkIf (config.settings.port == 80) "debug";
+}
+</programlisting>
+   To prevent this, declare options for all attributes that need to depend on others. For above example this means to declare <literal>logLevel</literal> to be an option.
+  </para>
+ </note>
+</section>
diff --git a/nixos/doc/manual/development/running-nixos-tests-interactively.xml b/nixos/doc/manual/development/running-nixos-tests-interactively.xml
index 31216874c7060..a11a9382764d2 100644
--- a/nixos/doc/manual/development/running-nixos-tests-interactively.xml
+++ b/nixos/doc/manual/development/running-nixos-tests-interactively.xml
@@ -38,7 +38,12 @@ starting VDE switch for network 1
  </para>
 
  <para>
-  The machine state is kept across VM restarts in
-  <filename>/tmp/vm-state-</filename><varname>machinename</varname>.
+   You can re-use the VM states coming from a previous run
+   by setting the <command>--keep-vm-state</command> flag.
+<screen>
+<prompt>$ </prompt>./result/bin/nixos-run-vms --keep-vm-state
+</screen>
+  The machine state is stored in the
+  <filename>$TMPDIR/vm-state-</filename><varname>machinename</varname> directory.
  </para>
 </section>
diff --git a/nixos/doc/manual/development/settings-options.xml b/nixos/doc/manual/development/settings-options.xml
new file mode 100644
index 0000000000000..c99c3af92f894
--- /dev/null
+++ b/nixos/doc/manual/development/settings-options.xml
@@ -0,0 +1,216 @@
+<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-settings-options">
+ <title>Options for Program Settings</title>
+
+ <para>
+   Many programs have configuration files where program-specific settings can be declared. File formats can be separated into two categories:
+   <itemizedlist>
+     <listitem>
+       <para>
+         Nix-representable ones: These can trivially be mapped to a subset of Nix syntax. E.g. JSON is an example, since its values like <literal>{"foo":{"bar":10}}</literal> can be mapped directly to Nix: <literal>{ foo = { bar = 10; }; }</literal>. Other examples are INI, YAML and TOML. The following section explains the convention for these settings.
+       </para>
+     </listitem>
+     <listitem>
+       <para>
+         Non-nix-representable ones: These can't be trivially mapped to a subset of Nix syntax. Most generic programming languages are in this group, e.g. bash, since the statement <literal>if true; then echo hi; fi</literal> doesn't have a trivial representation in Nix.
+       </para>
+       <para>
+         Currently there are no fixed conventions for these, but it is common to have a <literal>configFile</literal> option for setting the configuration file path directly. The default value of <literal>configFile</literal> can be an auto-generated file, with convenient options for controlling the contents. For example an option of type <literal>attrsOf str</literal> can be used for representing environment variables which generates a section like <literal>export FOO="foo"</literal>. Often it can also be useful to also include an <literal>extraConfig</literal> option of type <literal>lines</literal> to allow arbitrary text after the autogenerated part of the file.
+       </para>
+     </listitem>
+   </itemizedlist>
+ </para>
+ <section xml:id="sec-settings-nix-representable">
+   <title>Nix-representable Formats (JSON, YAML, TOML, INI, ...)</title>
+   <para>
+     By convention, formats like this are handled with a generic <literal>settings</literal> option, representing the full program configuration as a Nix value. The type of this option should represent the format. The most common formats have a predefined type and string generator already declared under <literal>pkgs.formats</literal>:
+     <variablelist>
+       <varlistentry>
+         <term>
+           <varname>pkgs.formats.json</varname> { }
+         </term>
+         <listitem>
+           <para>
+             A function taking an empty attribute set (for future extensibility) and returning a set with JSON-specific attributes <varname>type</varname> and <varname>generate</varname> as specified <link linkend='pkgs-formats-result'>below</link>.
+           </para>
+         </listitem>
+       </varlistentry>
+       <varlistentry>
+         <term>
+           <varname>pkgs.formats.yaml</varname> { }
+         </term>
+         <listitem>
+           <para>
+             A function taking an empty attribute set (for future extensibility) and returning a set with YAML-specific attributes <varname>type</varname> and <varname>generate</varname> as specified <link linkend='pkgs-formats-result'>below</link>.
+           </para>
+         </listitem>
+       </varlistentry>
+       <varlistentry>
+         <term>
+           <varname>pkgs.formats.ini</varname> { <replaceable>listsAsDuplicateKeys</replaceable> ? false, ... }
+         </term>
+         <listitem>
+           <para>
+             A function taking an attribute set with values
+             <variablelist>
+               <varlistentry>
+                 <term>
+                   <varname>listsAsDuplicateKeys</varname>
+                 </term>
+                 <listitem>
+                   <para>
+                     A boolean for controlling whether list values can be used to represent duplicate INI keys
+                   </para>
+                 </listitem>
+               </varlistentry>
+             </variablelist>
+            It returns a set with INI-specific attributes <varname>type</varname> and <varname>generate</varname> as specified <link linkend='pkgs-formats-result'>below</link>.
+           </para>
+         </listitem>
+       </varlistentry>
+       <varlistentry>
+         <term>
+           <varname>pkgs.formats.toml</varname> { }
+         </term>
+         <listitem>
+           <para>
+             A function taking an empty attribute set (for future extensibility) and returning a set with TOML-specific attributes <varname>type</varname> and <varname>generate</varname> as specified <link linkend='pkgs-formats-result'>below</link>.
+           </para>
+         </listitem>
+       </varlistentry>
+     </variablelist>
+
+   </para>
+   <para xml:id="pkgs-formats-result">
+     These functions all return an attribute set with these values:
+    <variablelist>
+      <varlistentry>
+        <term>
+          <varname>type</varname>
+        </term>
+        <listitem>
+          <para>
+            A module system type representing a value of the format
+          </para>
+        </listitem>
+      </varlistentry>
+      <varlistentry>
+        <term>
+          <varname>generate</varname> <replaceable>filename</replaceable> <replaceable>jsonValue</replaceable>
+        </term>
+        <listitem>
+          <para>
+           A function that can render a value of the format to a file. Returns a file path.
+           <note>
+            <para>
+             This function puts the value contents in the Nix store. So this should be avoided for secrets.
+            </para>
+           </note>
+          </para>
+        </listitem>
+      </varlistentry>
+    </variablelist>
+   </para>
+   <example xml:id="ex-settings-nix-representable">
+     <title>Module with conventional <literal>settings</literal> option</title>
+     <para>
+       The following shows a module for an example program that uses a JSON configuration file. It demonstrates how above values can be used, along with some other related best practices. See the comments for explanations.
+     </para>
+<programlisting>
+{ options, config, lib, pkgs, ... }:
+let
+  cfg = config.services.foo;
+  # Define the settings format used for this program
+  settingsFormat = pkgs.formats.json {};
+in {
+
+  options.services.foo = {
+    enable = lib.mkEnableOption "foo service";
+
+    settings = lib.mkOption {
+      # Setting this type allows for correct merging behavior
+      type = settingsFormat.type;
+      default = {};
+      description = ''
+        Configuration for foo, see
+        &lt;link xlink:href="https://example.com/docs/foo"/&gt;
+        for supported settings.
+      '';
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    # We can assign some default settings here to make the service work by just
+    # enabling it. We use `mkDefault` for values that can be changed without
+    # problems
+    services.foo.settings = {
+      # Fails at runtime without any value set
+      log_level = lib.mkDefault "WARN";
+
+      # We assume systemd's `StateDirectory` is used, so we require this value,
+      # therefore no mkDefault
+      data_path = "/var/lib/foo";
+
+      # Since we use this to create a user we need to know the default value at
+      # eval time
+      user = lib.mkDefault "foo";
+    };
+
+    environment.etc."foo.json".source =
+      # The formats generator function takes a filename and the Nix value
+      # representing the format value and produces a filepath with that value
+      # rendered in the format
+      settingsFormat.generate "foo-config.json" cfg.settings;
+
+    # We know that the `user` attribute exists because we set a default value
+    # for it above, allowing us to use it without worries here
+    users.users.${cfg.settings.user} = {};
+
+    # ...
+  };
+}
+</programlisting>
+   </example>
+   <section xml:id="sec-settings-attrs-options">
+    <title>Option declarations for attributes</title>
+    <para>
+     Some <literal>settings</literal> attributes may deserve some extra care. They may need a different type, default or merging behavior, or they are essential options that should show their documentation in the manual. This can be done using <xref linkend='sec-freeform-modules'/>.
+     <example xml:id="ex-settings-typed-attrs">
+      <title>Declaring a type-checked <literal>settings</literal> attribute</title>
+      <para>
+       We extend above example using freeform modules to declare an option for the port, which will enforce it to be a valid integer and make it show up in the manual.
+      </para>
+<programlisting>
+settings = lib.mkOption {
+  type = lib.types.submodule {
+
+    freeformType = settingsFormat.type;
+
+    # Declare an option for the port such that the type is checked and this option
+    # is shown in the manual.
+    options.port = lib.mkOption {
+      type = lib.types.port;
+      default = 8080;
+      description = ''
+        Which port this service should listen on.
+      '';
+    };
+
+  };
+  default = {};
+  description = ''
+    Configuration for Foo, see
+    &lt;link xlink:href="https://example.com/docs/foo"/&gt;
+    for supported values.
+  '';
+};
+</programlisting>
+     </example>
+    </para>
+   </section>
+ </section>
+
+</section>
diff --git a/nixos/doc/manual/development/writing-modules.xml b/nixos/doc/manual/development/writing-modules.xml
index bbf793bb0be98..d244356dbed19 100644
--- a/nixos/doc/manual/development/writing-modules.xml
+++ b/nixos/doc/manual/development/writing-modules.xml
@@ -183,4 +183,6 @@ in {
  <xi:include href="meta-attributes.xml" />
  <xi:include href="importing-modules.xml" />
  <xi:include href="replace-modules.xml" />
+ <xi:include href="freeform-modules.xml" />
+ <xi:include href="settings-options.xml" />
 </chapter>
diff --git a/nixos/doc/manual/development/writing-nixos-tests.xml b/nixos/doc/manual/development/writing-nixos-tests.xml
index 150bea8c2d86c..74ab23605b353 100644
--- a/nixos/doc/manual/development/writing-nixos-tests.xml
+++ b/nixos/doc/manual/development/writing-nixos-tests.xml
@@ -216,12 +216,12 @@ start_all()
    </varlistentry>
    <varlistentry>
     <term>
-     <methodname>send_keys</methodname>
+     <methodname>send_key</methodname>
     </term>
     <listitem>
      <para>
       Simulate pressing keys on the virtual keyboard, e.g.,
-      <literal>send_keys("ctrl-alt-delete")</literal>.
+      <literal>send_key("ctrl-alt-delete")</literal>.
      </para>
     </listitem>
    </varlistentry>
@@ -232,7 +232,7 @@ start_all()
     <listitem>
      <para>
       Simulate typing a sequence of characters on the virtual keyboard, e.g.,
-      <literal>send_keys("foobar\n")</literal> will type the string
+      <literal>send_chars("foobar\n")</literal> will type the string
       <literal>foobar</literal> followed by the Enter key.
      </para>
     </listitem>
@@ -362,6 +362,18 @@ start_all()
    </varlistentry>
    <varlistentry>
     <term>
+     <methodname>wait_for_console_text</methodname>
+    </term>
+    <listitem>
+     <para>
+      Wait until the supplied regular expressions match a line of the serial
+      console output. This method is useful when OCR is not possibile or
+      accurate enough.
+     </para>
+    </listitem>
+   </varlistentry>
+   <varlistentry>
+    <term>
      <methodname>wait_for_window</methodname>
     </term>
     <listitem>
@@ -378,7 +390,7 @@ start_all()
     <listitem>
      <para>
       Copies a file from host to machine, e.g.,
-      <literal>copy_file_from_host("myfile", "/etc/my/important/file")</literal>.
+      <literal>copy_from_host("myfile", "/etc/my/important/file")</literal>.
      </para>
      <para>
       The first argument is the file on the host. The file needs to be
diff --git a/nixos/doc/manual/installation/installing-pxe.xml b/nixos/doc/manual/installation/installing-pxe.xml
index 94199e5e028db..ea88fbdad7e29 100644
--- a/nixos/doc/manual/installation/installing-pxe.xml
+++ b/nixos/doc/manual/installation/installing-pxe.xml
@@ -16,7 +16,7 @@
  </para>
 
 <programlisting>
-nix-build -A netboot nixos/release.nix
+nix-build -A netboot.x86_64-linux nixos/release.nix
 </programlisting>
 
  <para>
diff --git a/nixos/doc/manual/installation/installing-virtualbox-guest.xml b/nixos/doc/manual/installation/installing-virtualbox-guest.xml
index 0ba909fa953fb..1cffeed480790 100644
--- a/nixos/doc/manual/installation/installing-virtualbox-guest.xml
+++ b/nixos/doc/manual/installation/installing-virtualbox-guest.xml
@@ -49,7 +49,7 @@
   </listitem>
   <listitem>
    <para>
-    Click on Settings / Display / Screen and select VBoxVGA as Graphics Controller
+    Click on Settings / Display / Screen and select VMSVGA as Graphics Controller
    </para>
   </listitem>
   <listitem>
diff --git a/nixos/doc/manual/installation/installing.xml b/nixos/doc/manual/installation/installing.xml
index 673df8f2e4c4e..5f216df66f84d 100644
--- a/nixos/doc/manual/installation/installing.xml
+++ b/nixos/doc/manual/installation/installing.xml
@@ -42,7 +42,7 @@
   </para>
 
   <para>
-   If the text is too small to be legible, try <command>setfont ter-132n</command>
+   If the text is too small to be legible, try <command>setfont ter-v32n</command>
    to increase the font size.
   </para>
 
@@ -146,7 +146,7 @@
        partition. It uses the initially reserved 512MiB at the start of the
        disk.
 <screen language="commands"><prompt># </prompt>parted /dev/sda -- mkpart ESP fat32 1MiB 512MiB
-<prompt># </prompt>parted /dev/sda -- set 3 boot on</screen>
+<prompt># </prompt>parted /dev/sda -- set 3 esp on</screen>
       </para>
      </listitem>
     </orderedlist>
@@ -513,7 +513,7 @@ Retype new UNIX password: ***</screen>
 <prompt># </prompt>parted /dev/sda -- mkpart primary 512MiB -8GiB
 <prompt># </prompt>parted /dev/sda -- mkpart primary linux-swap -8GiB 100%
 <prompt># </prompt>parted /dev/sda -- mkpart ESP fat32 1MiB 512MiB
-<prompt># </prompt>parted /dev/sda -- set 3 boot on</screen>
+<prompt># </prompt>parted /dev/sda -- set 3 esp on</screen>
   </example>
 
   <example xml:id="ex-install-sequence">
diff --git a/nixos/doc/manual/man-nixos-build-vms.xml b/nixos/doc/manual/man-nixos-build-vms.xml
index d114261f53bef..fa7c8c0c6d799 100644
--- a/nixos/doc/manual/man-nixos-build-vms.xml
+++ b/nixos/doc/manual/man-nixos-build-vms.xml
@@ -13,15 +13,15 @@
  </refnamediv>
  <refsynopsisdiv>
   <cmdsynopsis>
-   <command>nixos-build-vms</command> 
+   <command>nixos-build-vms</command>
    <arg>
     <option>--show-trace</option>
    </arg>
-    
+
    <arg>
     <option>--no-out-link</option>
    </arg>
-    
+
    <arg>
     <option>--help</option>
   </arg>
diff --git a/nixos/doc/manual/man-nixos-enter.xml b/nixos/doc/manual/man-nixos-enter.xml
index fe560d3efdd8e..f533d66099d39 100644
--- a/nixos/doc/manual/man-nixos-enter.xml
+++ b/nixos/doc/manual/man-nixos-enter.xml
@@ -13,21 +13,21 @@
  </refnamediv>
  <refsynopsisdiv>
   <cmdsynopsis>
-   <command>nixos-enter</command> 
+   <command>nixos-enter</command>
    <arg>
     <arg choice='plain'>
      <option>--root</option>
     </arg>
      <replaceable>root</replaceable>
    </arg>
-    
+
    <arg>
     <arg choice='plain'>
      <option>--system</option>
     </arg>
      <replaceable>system</replaceable>
    </arg>
-    
+
    <arg>
     <arg choice='plain'>
      <option>-c</option>
@@ -40,13 +40,13 @@
      <option>--silent</option>
     </arg>
    </arg>
-    
+
    <arg>
     <arg choice='plain'>
      <option>--help</option>
     </arg>
    </arg>
-    
+
    <arg>
     <arg choice='plain'>
      <option>--</option>
@@ -136,7 +136,7 @@
    <filename>/mnt</filename>:
   </para>
 <screen>
-# nixos-enter /mnt
+# nixos-enter --root /mnt
 </screen>
   <para>
    Run a shell command:
diff --git a/nixos/doc/manual/man-nixos-rebuild.xml b/nixos/doc/manual/man-nixos-rebuild.xml
index f4f663b84f056..f70f08a0f8a77 100644
--- a/nixos/doc/manual/man-nixos-rebuild.xml
+++ b/nixos/doc/manual/man-nixos-rebuild.xml
@@ -315,7 +315,7 @@
        switch</command>), because the hardware and boot loader configuration in
        the VM are different. The boot loader is installed on an automatically
        generated virtual disk containing a <filename>/boot</filename>
-       partition, which is mounted read-only in the VM.
+       partition.
       </para>
      </listitem>
     </varlistentry>
diff --git a/nixos/doc/manual/release-notes/rl-2003.xml b/nixos/doc/manual/release-notes/rl-2003.xml
index 393a9286ca4f5..0e9ba027a382a 100644
--- a/nixos/doc/manual/release-notes/rl-2003.xml
+++ b/nixos/doc/manual/release-notes/rl-2003.xml
@@ -792,7 +792,7 @@ users.users.me =
      The <option>services.xserver.displayManager.auto</option> module has been removed.
      It was only intended for use in internal NixOS tests, and gave the false impression
      of it being a special display manager when it's actually LightDM.
-     Please use the <xref linkend="opt-services.xserver.displayManager.lightdm.autoLogin"/> options instead,
+     Please use the <option>services.xserver.displayManager.lightdm.autoLogin</option> options instead,
      or any other display manager in NixOS as they all support auto-login. If you used this module specifically
      because it permitted root auto-login you can override the lightdm-autologin pam module like:
 <programlisting>
diff --git a/nixos/doc/manual/release-notes/rl-2009.xml b/nixos/doc/manual/release-notes/rl-2009.xml
index a0a0b2cb40e4e..83bc2f82bbfcc 100644
--- a/nixos/doc/manual/release-notes/rl-2009.xml
+++ b/nixos/doc/manual/release-notes/rl-2009.xml
@@ -44,6 +44,11 @@
    </listitem>
    <listitem>
     <para>
+     PHP 7.2 is no longer supported due to upstream not supporting this version for the entire lifecycle of the 20.09 release.
+    </para>
+   </listitem>
+   <listitem>
+    <para>
      Python 3 now defaults to Python 3.8 instead of 3.7.
     </para>
    </listitem>
@@ -109,6 +114,86 @@ systemd.services.mysql.serviceConfig.ProtectHome = lib.mkForce "read-only";
 systemd.services.mysql.serviceConfig.ReadWritePaths = [ "/var/data" ];
 </programlisting>
     </para>
+    <para>
+      The MySQL service no longer runs its <literal>systemd</literal> service startup script as <literal>root</literal> anymore. A dedicated non <literal>root</literal>
+      super user account is required for operation. This means users with an existing MySQL or MariaDB database server are required to run the following SQL statements
+      as a super admin user before upgrading:
+<programlisting>
+CREATE USER IF NOT EXISTS 'mysql'@'localhost' identified with unix_socket;
+GRANT ALL PRIVILEGES ON *.* TO 'mysql'@'localhost' WITH GRANT OPTION;
+</programlisting>
+      If you use MySQL instead of MariaDB please replace <literal>unix_socket</literal> with <literal>auth_socket</literal>. If you have changed the value of <xref linkend="opt-services.mysql.user"/>
+      from the default of <literal>mysql</literal> to a different user please change <literal>'mysql'@'localhost'</literal> to the corresponding user instead.
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+      Two new option <link linkend="opt-documentation.man.generateCaches">documentation.man.generateCaches</link>
+      has been added to automatically generate the <literal>man-db</literal> caches, which are needed by utilities
+      like <command>whatis</command> and <command>apropos</command>. The caches are generated during the build of
+      the NixOS configuration: since this can be expensive when a large number of packages are installed, the
+      feature is disabled by default.
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     <varname>services.postfix.sslCACert</varname> was replaced by <varname>services.postfix.tlsTrustedAuthorities</varname> which now defaults to system certificate authorities.
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+      Subordinate GID and UID mappings are now set up automatically for all normal users.
+      This will make container tools like Podman work as non-root users out of the box.
+    </para>
+   </listitem>
+   <listitem>
+     <para>
+       The various documented workarounds to use steam have been converted to a module. <varname>programs.steam.enable</varname> enables steam, controller support and the workarounds.
+     </para>
+   </listitem>
+   <listitem>
+     <para>
+       Support for built-in LCDs in various pieces of Logitech hardware (keyboards and USB speakers). <varname>hardware.logitech.lcd.enable</varname> enables support for all hardware supported by the g15daemon project.
+     </para>
+   </listitem>
+   <listitem>
+    <para>
+      Zabbix now defaults to 5.0, updated from 4.4. Please carefully read through
+      <link xlink:href="https://www.zabbix.com/documentation/current/manual/installation/upgrade/sources">the upgrade guide</link>
+      and apply any changes required. Be sure to take special note of the section on
+      <link xlink:href="https://www.zabbix.com/documentation/current/manual/installation/upgrade_notes_500#enabling_extended_range_of_numeric_float_values">enabling extended range of numeric (float) values</link>
+      as you will need to apply this database migration manually.
+    </para>
+    <para>
+      If you are using Zabbix Server with a MySQL or MariaDB database you should note that using a character set of <literal>utf8</literal> and a collate of <literal>utf8_bin</literal> has become mandatory with
+      this release. See the upstream <link xlink:href="https://support.zabbix.com/browse/ZBX-17357">issue</link> for further discussion. Before upgrading you should check the character set and collation used by
+      your database and ensure they are correct:
+<programlisting>
+  SELECT
+    default_character_set_name,
+    default_collation_name
+  FROM
+    information_schema.schemata
+  WHERE
+    schema_name = 'zabbix';
+</programlisting>
+      If these values are not correct you should take a backup of your database and convert the character set and collation as required. Here is an
+      <link xlink:href="https://www.zabbix.com/forum/zabbix-help/396573-reinstall-after-upgrade?p=396891#post396891">example</link> of how to do so, taken from
+      the Zabbix forums:
+<programlisting>
+  ALTER DATABASE `zabbix` DEFAULT CHARACTER SET utf8 COLLATE utf8_bin;
+
+  -- the following will produce a list of SQL commands you should subsequently execute
+  SELECT CONCAT("ALTER TABLE ", TABLE_NAME," CONVERT TO CHARACTER SET utf8 COLLATE utf8_bin;") AS ExecuteTheString
+  FROM information_schema.`COLUMNS`
+  WHERE table_schema = "zabbix" AND COLLATION_NAME = "utf8_general_ci";
+</programlisting>
+    </para>
+   </listitem>
+   <listitem>
+     <para>
+      The NixOS module system now supports freeform modules as a mix between <literal>types.attrsOf</literal> and <literal>types.submodule</literal>. These allow you to explicitly declare a subset of options while still permitting definitions without an associated option. See <xref linkend='sec-freeform-modules'/> for how to use them.
+     </para>
    </listitem>
   </itemizedlist>
  </section>
@@ -153,12 +238,10 @@ systemd.services.mysql.serviceConfig.ReadWritePaths = [ "/var/data" ];
      in the source tree for downloaded modules instead of using go's <link
      xlink:href="https://golang.org/cmd/go/#hdr-Module_proxy_protocol">module
      proxy protocol</link>. This storage format is simpler and therefore less
-     likekly to break with future versions of go. As a result
+     likely to break with future versions of go. As a result
      <literal>buildGoModule</literal> switched from
      <literal>modSha256</literal> to the <literal>vendorSha256</literal>
-     attribute to pin fetched version data. <literal>buildGoModule</literal>
-     still accepts <literal>modSha256</literal> with a warning, but support will
-     be removed in the next release.
+     attribute to pin fetched version data.
     </para>
    </listitem>
    <listitem>
@@ -167,7 +250,7 @@ systemd.services.mysql.serviceConfig.ReadWritePaths = [ "/var/data" ];
      <link xlink:href="https://grafana.com/docs/grafana/latest/guides/whats-new-in-v6-4/">deprecated in Grafana</link>
      and the <package>phantomjs</package> project is
      <link xlink:href="https://github.com/ariya/phantomjs/issues/15344#issue-302015362">currently unmaintained</link>.
-     It can still be enabled by providing <literal>phantomJsSupport = true</literal> to the package instanciation:
+     It can still be enabled by providing <literal>phantomJsSupport = true</literal> to the package instantiation:
 <programlisting>{
   services.grafana.package = pkgs.grafana.overrideAttrs (oldAttrs: rec {
     phantomJsSupport = false;
@@ -179,7 +262,7 @@ systemd.services.mysql.serviceConfig.ReadWritePaths = [ "/var/data" ];
     <para>
       The <link linkend="opt-services.supybot.enable">supybot</link> module now uses <literal>/var/lib/supybot</literal>
       as its default <link linkend="opt-services.supybot.stateDir">stateDir</link> path if <literal>stateVersion</literal>
-      is 20.09 or higher. It also enables number of
+      is 20.09 or higher. It also enables a number of
       <link xlink:href="https://www.freedesktop.org/software/systemd/man/systemd.exec.html#Sandboxing">systemd sandboxing options</link>
       which may possibly interfere with some plugins. If this is the case you can disable the options through attributes in
       <option>systemd.services.supybot.serviceConfig</option>.
@@ -493,6 +576,171 @@ systemd.services.nginx.serviceConfig.ReadWritePaths = [ "/var/www" ];
    <listitem>
     <para>
      In the <literal>resilio</literal> module, <xref linkend="opt-services.resilio.httpListenAddr"/> has been changed to listen to <literal>[::1]</literal> instead of <literal>0.0.0.0</literal>.
+     </para>
+   </listitem>
+   <listitem>
+    <para>
+     Users of <link xlink:href="http://openafs.org">OpenAFS 1.6</link> must
+     upgrade their services to OpenAFS 1.8! In this release, the OpenAFS package
+     version 1.6.24 is marked broken but can be used during transition to
+     OpenAFS 1.8.x. Use the options
+     <option>services.openafsClient.packages.module</option>,
+     <option>services.openafsClient.packages.programs</option> and
+     <option>services.openafsServer.package</option> to select a different
+     OpenAFS package. OpenAFS 1.6 will be removed in the next release. The
+     package <literal>openafs</literal> and the service options will then
+     silently point to the OpenAFS 1.8 release.
+    </para>
+    <para>
+     See also the OpenAFS <link
+     xlink:href="http://docs.openafs.org/AdminGuide/index.html">Administrator
+     Guide</link> for instructions. Beware of the following when updating
+     servers:
+     <itemizedlist>
+      <listitem>
+       <para>
+       The storage format of the server key has changed and the key must be converted before running the new release.
+       </para>
+      </listitem>
+      <listitem>
+       <para>
+       When updating multiple database servers, turn off the database servers
+       from the highest IP down to the lowest with resting periods in
+       between. Start up in reverse order. Do not concurrently run database
+       servers working with different OpenAFS releases!
+       </para>
+      </listitem>
+      <listitem>
+       <para>
+       Update servers first, then clients.
+       </para>
+      </listitem>
+     </itemizedlist>
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     Radicale's default package has changed from 2.x to 3.x. An upgrade
+     checklist can be found
+     <link xlink:href="https://github.com/Kozea/Radicale/blob/3.0.x/NEWS.md#upgrade-checklist">here</link>.
+     You can use the newer version in the NixOS service by setting the
+     <literal>package</literal> to <literal>radicale3</literal>, which is done
+     automatically if <literal>stateVersion</literal> is 20.09 or higher.
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+      <literal>udpt</literal> experienced a complete rewrite from C++ to rust. The configuration format changed from ini to toml.
+      The new configuration documentation can be found at
+      <link xlink:href="https://naim94a.github.io/udpt/config.html">the official website</link> and example
+      configuration is packaged in <literal>${udpt}/share/udpt/udpt.toml</literal>.
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     We now have a unified <xref linkend="opt-services.xserver.displayManager.autoLogin"/> option interface
+     to be used for every display-manager in NixOS.
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     The <literal>bitcoind</literal> module has changed to multi-instance, using submodules.
+     Therefore, it is now mandatory to name each instance.
+     To use this new multi-instance config with an existing bitcoind data directory and user,
+     you have to adjust the original config, e.g.:
+<programlisting>
+  services.bitcoind = {
+    enable = true;
+    extraConfig = "...";
+    ...
+  };
+</programlisting>
+     To something similar:
+<programlisting>
+  services.bitcoind.mainnet = {
+    enable = true;
+    dataDir = "/var/lib/bitcoind";
+    user = "bitcoin";
+    extraConfig = "...";
+    ...
+  };
+</programlisting>
+     The key settings are:
+     <itemizedlist>
+      <listitem>
+       <para>
+        <literal>dataDir</literal> - to continue using the same data directory.
+       </para>
+      </listitem>
+      <listitem>
+       <para>
+        <literal>user</literal> - to continue using the same user so that bitcoind maintains access to its files.
+       </para>
+      </listitem>
+     </itemizedlist>
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+      Graylog introduced a change in the LDAP server certificate validation behaviour for version 3.3.3 which might break existing setups.
+      When updating Graylog from a version before 3.3.3 make sure to check the Graylog <link xlink:href="https://www.graylog.org/post/announcing-graylog-v3-3-3">release info</link> for information on how to avoid the issue.
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     The <literal>dokuwiki</literal> module has changed to multi-instance, using submodules.
+     Therefore, it is now mandatory to name each instance. Moreover, forcing SSL by default has been dropped, so
+     <literal>nginx.forceSSL</literal> and <literal>nginx.enableACME</literal> are no longer set to <literal>true</literal>.
+     To continue using your service with the original SSL settings, you have to adjust the original config, e.g.:
+<programlisting>
+services.dokuwiki = {
+  enable = true;
+  ...
+};
+</programlisting>
+     To something similar:
+<programlisting>
+services.dokuwiki."mywiki" = {
+  enable = true;
+  nginx = {
+    forceSSL = true;
+    enableACME = true;
+  };
+  ...
+};
+</programlisting>
+     The base package has also been upgraded to the 2020-07-29 "Hogfather" release. Plugins might be incompatible or require upgrading.
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+      The <xref linkend="opt-services.postgresql.dataDir"/> option is now set to <literal>"/var/lib/postgresql/${cfg.package.psqlSchema}"</literal> regardless of your
+      <xref linkend="opt-system.stateVersion"/>. Users with an existing postgresql install that have a <xref linkend="opt-system.stateVersion"/> of <literal>17.03</literal> or below
+      should double check what the value of their <xref linkend="opt-services.postgresql.dataDir"/> option is (<literal>/var/db/postgresql</literal>) and then explicitly
+      set this value to maintain compatibility:
+<programlisting>
+services.postgresql.dataDir = "/var/db/postgresql";
+</programlisting>
+    </para>
+    <para>
+     The postgresql module now expects there to be a database super user account called <literal>postgres</literal> regardless of your <xref linkend="opt-system.stateVersion"/>. Users
+     with an existing postgresql install that have a <xref linkend="opt-system.stateVersion"/> of <literal>17.03</literal> or below should run the following SQL statements as a
+     database super admin user before upgrading:
+<programlisting>
+CREATE ROLE postgres LOGIN SUPERUSER;
+</programlisting>
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     The USBGuard module now removes options and instead hardcodes values for <literal>IPCAccessControlFiles</literal>, <literal>ruleFiles</literal>, and <literal>auditFilePath</literal>. Audit logs can be found in the journal.
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     The NixOS module system now evaluates option definitions more strictly, allowing it to detect a larger set of problems.
+     As a result, what previously evaluated may not do so anymore.
+     See <link xlink:href="https://github.com/NixOS/nixpkgs/pull/82743#issuecomment-674520472">the PR that changed this</link> for more info.
     </para>
    </listitem>
   </itemizedlist>
@@ -532,6 +780,11 @@ systemd.services.nginx.serviceConfig.ReadWritePaths = [ "/var/www" ];
    </listitem>
    <listitem>
     <para>
+     <literal>buildGoModule</literal> <literal>doCheck</literal> now defaults to <literal>true</literal>.
+    </para>
+   </listitem>
+   <listitem>
+    <para>
      Packages built using <literal>buildRustPackage</literal> now use <literal>release</literal>
      mode for the <literal>checkPhase</literal> by default.
     </para>
@@ -589,6 +842,37 @@ systemd.services.nginx.serviceConfig.ReadWritePaths = [ "/var/www" ];
    </listitem>
    <listitem>
     <para>
+     The <literal>services.transmission</literal> module
+     was enhanced with the new options:
+     <xref linkend="opt-services.transmission.credentialsFile"/>,
+     <xref linkend="opt-services.transmission.openFirewall"/>,
+     and <xref linkend="opt-services.transmission.performanceNetParameters"/>.
+    </para>
+    <para>
+     <literal>transmission-daemon</literal> is now started with additional systemd sandbox/hardening options for better security.
+     Please <link xlink:href="https://github.com/NixOS/nixpkgs/issues">report</link>
+     any use case where this is not working well.
+     In particular, the <literal>RootDirectory</literal> option newly set
+     forbids uploading or downloading a torrent outside of the default directory
+     configured at <link linkend="opt-services.transmission.settings">settings.download-dir</link>.
+     If you really need Transmission to access other directories,
+     you must include those directories into the <literal>BindPaths</literal> of the service:
+<programlisting>
+systemd.services.transmission.serviceConfig.BindPaths = [ "/path/to/alternative/download-dir" ];
+</programlisting>
+    </para>
+    <para>
+     Also, connection to the RPC (Remote Procedure Call) of <literal>transmission-daemon</literal>
+     is now only available on the local network interface by default.
+     Use:
+<programlisting>
+services.transmission.settings.rpc-bind-address = "0.0.0.0";
+</programlisting>
+     to get the previous behavior of listening on all network interfaces.
+    </para>
+   </listitem>
+   <listitem>
+    <para>
      With this release <literal>systemd-networkd</literal> (when enabled through <xref linkend="opt-networking.useNetworkd"/>)
      has it's netlink socket created through a <literal>systemd.socket</literal> unit. This gives us control over
      socket buffer sizes and other parameters. For larger setups where networkd has to create a lot of (virtual)
@@ -656,6 +940,36 @@ systemd.services.nginx.serviceConfig.ReadWritePaths = [ "/var/www" ];
      <package>nextcloud18</package> before upgrading to <package>nextcloud19</package>
      since Nextcloud doesn't support upgrades across multiple major versions.
     </para>
+     <para>
+       The <literal>nixos-run-vms</literal> script now deletes the
+       previous run machines states on test startup. You can use the
+       <literal>--keep-vm-state</literal> flag to match the previous
+       behaviour and keep the same VM state between different test runs.
+     </para>
+   </listitem>
+   <listitem>
+    <para>
+     The <link linkend="opt-nix.buildMachines">nix.buildMachines</link> option is now type-checked.
+     There are no functional changes, however this may require updating some configurations to use correct types for all attributes.
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     The <literal>fontconfig</literal> module stopped generating fontconfig 2.10.x config and cache.
+     Fontconfig 2.10.x was removed from Nixpkgs - it hasn't been used in any nixpkgs package anymore.
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+      The packages <package>perl</package>, <package>rsync</package> and <package>strace</package> were removed from <option>systemPackages</option>. If you need them, install them again with <code><xref linkend="opt-environment.systemPackages"/> = with pkgs; [ perl rsync strace ];</code> in your <filename>configuration.nix</filename>.
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+      The <literal>undervolt</literal> option no longer needs to apply its
+      settings every 30s. If they still become undone, open an issue and restore
+      the previous behaviour using <literal>undervolt.useTimer</literal>.
+    </para>
    </listitem>
   </itemizedlist>
  </section>
diff --git a/nixos/lib/make-disk-image.nix b/nixos/lib/make-disk-image.nix
index 9364aab327705..8aa606a56af80 100644
--- a/nixos/lib/make-disk-image.nix
+++ b/nixos/lib/make-disk-image.nix
@@ -5,21 +5,32 @@
   config
 
 , # The size of the disk, in megabytes.
-  diskSize
+  # if "auto" size is calculated based on the contents copied to it and
+  #   additionalSpace is taken into account.
+  diskSize ? "auto"
 
-  # The files and directories to be placed in the target file system.
+, # additional disk space to be added to the image if diskSize "auto"
+  # is used
+  additionalSpace ? "512M"
+
+, # size of the boot partition, is only used if partitionTableType is
+  # either "efi" or "hybrid"
+  bootSize ? "256M"
+
+, # The files and directories to be placed in the target file system.
   # This is a list of attribute sets {source, target} where `source'
   # is the file system object (regular file or directory) to be
   # grafted in the file system at path `target'.
-, contents ? []
+  contents ? []
 
 , # Type of partition table to use; either "legacy", "efi", or "none".
   # For "efi" images, the GPT partition table is used and a mandatory ESP
   #   partition of reasonable size is created in addition to the root partition.
-  #   If `installBootLoader` is true, GRUB will be installed in EFI mode.
   # For "legacy", the msdos partition table is used and a single large root
-  #   partition is created. If `installBootLoader` is true, GRUB will be
-  #   installed in legacy mode.
+  #   partition is created.
+  # For "hybrid", the GPT partition table is used and a mandatory ESP
+  #   partition of reasonable size is created in addition to the root partition.
+  #   Also a legacy MBR will be present.
   # For "none", no partition table is created. Enabling `installBootLoader`
   #   most likely fails as GRUB will probably refuse to install.
   partitionTableType ? "legacy"
@@ -43,7 +54,7 @@
   format ? "raw"
 }:
 
-assert partitionTableType == "legacy" || partitionTableType == "efi" || partitionTableType == "none";
+assert partitionTableType == "legacy" || partitionTableType == "efi" || partitionTableType == "hybrid" || partitionTableType == "none";
 # We use -E offset=X below, which is only supported by e2fsprogs
 assert partitionTableType != "none" -> fsType == "ext4";
 
@@ -60,11 +71,12 @@ let format' = format; in let
     vdi   = "vdi";
     vpc   = "vhd";
     raw   = "img";
-  }.${format};
+  }.${format} or format;
 
   rootPartition = { # switch-case
     legacy = "1";
     efi = "2";
+    hybrid = "3";
   }.${partitionTableType};
 
   partitionDiskScript = { # switch-case
@@ -76,9 +88,18 @@ let format' = format; in let
     efi = ''
       parted --script $diskImage -- \
         mklabel gpt \
-        mkpart ESP fat32 8MiB 256MiB \
+        mkpart ESP fat32 8MiB ${bootSize} \
         set 1 boot on \
-        mkpart primary ext4 256MiB -1
+        mkpart primary ext4 ${bootSize} -1
+    '';
+    hybrid = ''
+      parted --script $diskImage -- \
+        mklabel gpt \
+        mkpart ESP fat32 8MiB ${bootSize} \
+        set 1 boot on \
+        mkpart no-fs 0 1024KiB \
+        set 2 bios_grub on \
+        mkpart primary ext4 ${bootSize} -1
     '';
     none = "";
   }.${partitionTableType};
@@ -129,19 +150,6 @@ let format' = format; in let
     }
 
     mkdir $out
-    diskImage=nixos.raw
-    truncate -s ${toString diskSize}M $diskImage
-
-    ${partitionDiskScript}
-
-    ${if partitionTableType != "none" then ''
-      # Get start & length of the root partition in sectors to $START and $SECTORS.
-      eval $(partx $diskImage -o START,SECTORS --nr ${rootPartition} --pairs)
-
-      mkfs.${fsType} -F -L ${label} $diskImage -E offset=$(sectorsToBytes $START) $(sectorsToKilobytes $SECTORS)K
-    '' else ''
-      mkfs.${fsType} -F -L ${label} $diskImage
-    ''}
 
     root="$PWD/root"
     mkdir -p $root
@@ -181,10 +189,36 @@ let format' = format; in let
     export NIX_STATE_DIR=$TMPDIR/state
     nix-store --load-db < ${closureInfo}/registration
 
+    chmod 755 "$TMPDIR"
     echo "running nixos-install..."
     nixos-install --root $root --no-bootloader --no-root-passwd \
       --system ${config.system.build.toplevel} --channel ${channelSources} --substituters ""
 
+    diskImage=nixos.raw
+
+    ${if diskSize == "auto" then ''
+      ${if partitionTableType == "efi" || partitionTableType == "hybrid" then ''
+        additionalSpace=$(( ($(numfmt --from=iec '${additionalSpace}') + $(numfmt --from=iec '${bootSize}')) / 1000 ))
+      '' else ''
+        additionalSpace=$(( $(numfmt --from=iec '${additionalSpace}') / 1000 ))
+      ''}
+      diskSize=$(( $(set -- $(du -d0 $root); echo "$1") + $additionalSpace ))
+      truncate -s "$diskSize"K $diskImage
+    '' else ''
+      truncate -s ${toString diskSize}M $diskImage
+    ''}
+
+    ${partitionDiskScript}
+
+    ${if partitionTableType != "none" then ''
+      # Get start & length of the root partition in sectors to $START and $SECTORS.
+      eval $(partx $diskImage -o START,SECTORS --nr ${rootPartition} --pairs)
+
+      mkfs.${fsType} -F -L ${label} $diskImage -E offset=$(sectorsToBytes $START) $(sectorsToKilobytes $SECTORS)K
+    '' else ''
+      mkfs.${fsType} -F -L ${label} $diskImage
+    ''}
+
     echo "copying staging root to image..."
     cptofs -p ${optionalString (partitionTableType != "none") "-P ${rootPartition}"} -t ${fsType} -i $diskImage $root/* /
   '';
@@ -218,7 +252,7 @@ in pkgs.vmTools.runInLinuxVM (
 
       # Create the ESP and mount it. Unlike e2fsprogs, mkfs.vfat doesn't support an
       # '-E offset=X' option, so we can't do this outside the VM.
-      ${optionalString (partitionTableType == "efi") ''
+      ${optionalString (partitionTableType == "efi" || partitionTableType == "hybrid") ''
         mkdir -p /mnt/boot
         mkfs.vfat -n ESP /dev/vda1
         mount /dev/vda1 /mnt/boot
diff --git a/nixos/lib/make-ext4-fs.nix b/nixos/lib/make-ext4-fs.nix
index 516fe3fc673cf..33dbc8f5ec461 100644
--- a/nixos/lib/make-ext4-fs.nix
+++ b/nixos/lib/make-ext4-fs.nix
@@ -17,7 +17,7 @@
 , e2fsprogs
 , libfaketime
 , perl
-, lkl
+, fakeroot
 }:
 
 let
@@ -26,7 +26,7 @@ in
 pkgs.stdenv.mkDerivation {
   name = "ext4-fs.img${lib.optionalString compressImage ".zst"}";
 
-  nativeBuildInputs = [ e2fsprogs.bin libfaketime perl lkl ]
+  nativeBuildInputs = [ e2fsprogs.bin libfaketime perl fakeroot ]
   ++ lib.optional compressImage zstd;
 
   buildCommand =
@@ -37,32 +37,34 @@ pkgs.stdenv.mkDerivation {
       ${populateImageCommands}
       )
 
-      # Add the closures of the top-level store objects.
-      storePaths=$(cat ${sdClosureInfo}/store-paths)
+      echo "Preparing store paths for image..."
+
+      # Create nix/store before copying path
+      mkdir -p ./rootImage/nix/store
+
+      xargs -I % cp -a --reflink=auto % -t ./rootImage/nix/store/ < ${sdClosureInfo}/store-paths
+      (
+        GLOBIGNORE=".:.."
+        shopt -u dotglob
+
+        for f in ./files/*; do
+            cp -a --reflink=auto -t ./rootImage/ "$f"
+        done
+      )
+
+      # Also include a manifest of the closures in a format suitable for nix-store --load-db
+      cp ${sdClosureInfo}/registration ./rootImage/nix-path-registration
 
       # Make a crude approximation of the size of the target image.
       # If the script starts failing, increase the fudge factors here.
-      numInodes=$(find $storePaths ./files | wc -l)
-      numDataBlocks=$(du -s -c -B 4096 --apparent-size $storePaths ./files | tail -1 | awk '{ print int($1 * 1.10) }')
+      numInodes=$(find ./rootImage | wc -l)
+      numDataBlocks=$(du -s -c -B 4096 --apparent-size ./rootImage | tail -1 | awk '{ print int($1 * 1.10) }')
       bytes=$((2 * 4096 * $numInodes + 4096 * $numDataBlocks))
       echo "Creating an EXT4 image of $bytes bytes (numInodes=$numInodes, numDataBlocks=$numDataBlocks)"
 
       truncate -s $bytes $img
-      faketime -f "1970-01-01 00:00:01" mkfs.ext4 -L ${volumeLabel} -U ${uuid} $img
-
-      # Also include a manifest of the closures in a format suitable for nix-store --load-db.
-      cp ${sdClosureInfo}/registration nix-path-registration
-      cptofs -t ext4 -i $img nix-path-registration /
-
-      # Create nix/store before copying paths
-      faketime -f "1970-01-01 00:00:01" mkdir -p nix/store
-      cptofs -t ext4 -i $img nix /
-
-      echo "copying store paths to image..."
-      cptofs -t ext4 -i $img $storePaths /nix/store/
 
-      echo "copying files to image..."
-      cptofs -t ext4 -i $img ./files/* /
+      faketime -f "1970-01-01 00:00:01" fakeroot mkfs.ext4 -L ${volumeLabel} -U ${uuid} -d ./rootImage $img
 
       export EXT2FS_NO_MTAB_OK=yes
       # I have ended up with corrupted images sometimes, I suspect that happens when the build machine's disk gets full during the build.
diff --git a/nixos/lib/make-iso9660-image.nix b/nixos/lib/make-iso9660-image.nix
index 12a6cf6499663..6a0e0e7c635a4 100644
--- a/nixos/lib/make-iso9660-image.nix
+++ b/nixos/lib/make-iso9660-image.nix
@@ -1,4 +1,4 @@
-{ stdenv, closureInfo, xorriso, syslinux
+{ stdenv, closureInfo, xorriso, syslinux, libossp_uuid
 
 , # The file name of the resulting ISO image.
   isoName ? "cd.iso"
@@ -48,7 +48,7 @@ assert usbBootable -> isohybridMbrImage != "";
 stdenv.mkDerivation {
   name = isoName;
   builder = ./make-iso9660-image.sh;
-  buildInputs = [ xorriso syslinux zstd ];
+  buildInputs = [ xorriso syslinux zstd libossp_uuid ];
 
   inherit isoName bootable bootImage compressImage volumeID efiBootImage efiBootable isohybridMbrImage usbBootable;
 
diff --git a/nixos/lib/make-iso9660-image.sh b/nixos/lib/make-iso9660-image.sh
index d64fe9a365e75..4740b05f95571 100644
--- a/nixos/lib/make-iso9660-image.sh
+++ b/nixos/lib/make-iso9660-image.sh
@@ -99,7 +99,12 @@ done
 
 mkdir -p $out/iso
 
+# daed2280-b91e-42c0-aed6-82c825ca41f3 is an arbitrary namespace, to prevent
+# independent applications from generating the same UUID for the same value.
+# (the chance of that being problematic seem pretty slim here, but that's how
+# version-5 UUID's work)
 xorriso="xorriso
+ -boot_image any gpt_disk_guid=$(uuid -v 5 daed2280-b91e-42c0-aed6-82c825ca41f3 $out | tr -d -)
  -as mkisofs
  -iso-level 3
  -volid ${volumeID}
@@ -118,15 +123,6 @@ xorriso="xorriso
 
 $xorriso -output $out/iso/$isoName
 
-if test -n "$usbBootable"; then
-    echo "Making image hybrid..."
-    if test -n "$efiBootable"; then
-        isohybrid --uefi $out/iso/$isoName
-    else
-        isohybrid $out/iso/$isoName
-    fi
-fi
-
 if test -n "$compressImage"; then
     echo "Compressing image..."
     zstd -T$NIX_BUILD_CORES --rm $out/iso/$isoName
diff --git a/nixos/lib/make-options-doc/default.nix b/nixos/lib/make-options-doc/default.nix
index 772b7d3add951..a1161621f0d49 100644
--- a/nixos/lib/make-options-doc/default.nix
+++ b/nixos/lib/make-options-doc/default.nix
@@ -36,7 +36,7 @@ let
     // lib.optionalAttrs (opt ? example) { example = substFunction opt.example; }
     // lib.optionalAttrs (opt ? default) { default = substFunction opt.default; }
     // lib.optionalAttrs (opt ? type) { type = substFunction opt.type; }
-    // lib.optionalAttrs (opt ? relatedPackages && opt.relatedPackages != []) { relatedPackages = genRelatedPackages opt.relatedPackages; }
+    // lib.optionalAttrs (opt ? relatedPackages && opt.relatedPackages != []) { relatedPackages = genRelatedPackages opt.relatedPackages opt.name; }
    );
 
   # Generate DocBook documentation for a list of packages. This is
@@ -48,7 +48,7 @@ let
   # - a list:    that will be interpreted as an attribute path from `pkgs`,
   # - an attrset: that can specify `name`, `path`, `package`, `comment`
   #   (either of `name`, `path` is required, the rest are optional).
-  genRelatedPackages = packages:
+  genRelatedPackages = packages: optName:
     let
       unpack = p: if lib.isString p then { name = p; }
                   else if lib.isList p then { path = p; }
@@ -58,7 +58,7 @@ let
           title = args.title or null;
           name = args.name or (lib.concatStringsSep "." args.path);
           path = args.path or [ args.name ];
-          package = args.package or (lib.attrByPath path (throw "Invalid package attribute path `${toString path}'") pkgs);
+          package = args.package or (lib.attrByPath path (throw "Invalid package attribute path `${toString path}' found while evaluating `relatedPackages' of option `${optName}'") pkgs);
         in "<listitem>"
         + "<para><literal>${lib.optionalString (title != null) "${title} aka "}pkgs.${name} (${package.meta.name})</literal>"
         + lib.optionalString (!package.meta.available) " <emphasis>[UNAVAILABLE]</emphasis>"
diff --git a/nixos/lib/qemu-flags.nix b/nixos/lib/qemu-flags.nix
index 859d9e975fec7..0cf6977af4bbb 100644
--- a/nixos/lib/qemu-flags.nix
+++ b/nixos/lib/qemu-flags.nix
@@ -2,13 +2,18 @@
 { pkgs }:
 
 let
-  zeroPad = n: if n < 10 then "0${toString n}" else toString n;
+  zeroPad = n:
+    pkgs.lib.optionalString (n < 16) "0" +
+      (if n > 255
+       then throw "Can't have more than 255 nets or nodes!"
+       else pkgs.lib.toHexString n);
 in
 
-{
+rec {
+  qemuNicMac = net: machine: "52:54:00:12:${zeroPad net}:${zeroPad machine}";
 
   qemuNICFlags = nic: net: machine:
-    [ "-device virtio-net-pci,netdev=vlan${toString nic},mac=52:54:00:12:${zeroPad net}:${zeroPad machine}"
+    [ "-device virtio-net-pci,netdev=vlan${toString nic},mac=${qemuNicMac net machine}"
       "-netdev vde,id=vlan${toString nic},sock=$QEMU_VDE_SOCKET_${toString net}"
     ];
 
diff --git a/nixos/lib/test-driver/Logger.pm b/nixos/lib/test-driver/Logger.pm
index 080310ea34e08..a3384084a0ef2 100644
--- a/nixos/lib/test-driver/Logger.pm
+++ b/nixos/lib/test-driver/Logger.pm
@@ -8,17 +8,17 @@ use Time::HiRes qw(clock_gettime CLOCK_MONOTONIC);
 
 sub new {
     my ($class) = @_;
-    
+
     my $logFile = defined $ENV{LOGFILE} ? "$ENV{LOGFILE}" : "/dev/null";
     my $log = new XML::Writer(OUTPUT => new IO::File(">$logFile"));
-    
+
     my $self = {
         log => $log,
         logQueue => Thread::Queue->new()
     };
-    
+
     $self->{log}->startTag("logfile");
-    
+
     bless $self, $class;
     return $self;
 }
diff --git a/nixos/lib/test-driver/test-driver.py b/nixos/lib/test-driver/test-driver.py
index e7b05968b0796..7b8d5803aa5a6 100644
--- a/nixos/lib/test-driver/test-driver.py
+++ b/nixos/lib/test-driver/test-driver.py
@@ -3,7 +3,10 @@ from contextlib import contextmanager, _GeneratorContextManager
 from queue import Queue, Empty
 from typing import Tuple, Any, Callable, Dict, Iterator, Optional, List
 from xml.sax.saxutils import XMLGenerator
+import queue
+import io
 import _thread
+import argparse
 import atexit
 import base64
 import codecs
@@ -19,6 +22,7 @@ import subprocess
 import sys
 import tempfile
 import time
+import traceback
 import unicodedata
 
 CHAR_TO_KEY = {
@@ -671,6 +675,22 @@ class Machine:
         with self.nested("waiting for {} to appear on screen".format(regex)):
             retry(screen_matches)
 
+    def wait_for_console_text(self, regex: str) -> None:
+        self.log("waiting for {} to appear on console".format(regex))
+        # Buffer the console output, this is needed
+        # to match multiline regexes.
+        console = io.StringIO()
+        while True:
+            try:
+                console.write(self.last_lines.get())
+            except queue.Empty:
+                self.sleep(1)
+                continue
+            console.seek(0)
+            matches = re.search(regex, console.read())
+            if matches is not None:
+                return
+
     def send_key(self, key: str) -> None:
         key = CHAR_TO_KEY.get(key, key)
         self.send_monitor_command("sendkey {}".format(key))
@@ -734,11 +754,16 @@ class Machine:
         self.monitor, _ = self.monitor_socket.accept()
         self.shell, _ = self.shell_socket.accept()
 
+        # Store last serial console lines for use
+        # of wait_for_console_text
+        self.last_lines: Queue = Queue()
+
         def process_serial_output() -> None:
             assert self.process.stdout is not None
             for _line in self.process.stdout:
                 # Ignore undecodable bytes that may occur in boot menus
                 line = _line.decode(errors="ignore").replace("\r", "").rstrip()
+                self.last_lines.put(line)
                 eprint("{} # {}".format(self.name, line))
                 self.logger.enqueue({"msg": line, "machine": self.name})
 
@@ -751,6 +776,11 @@ class Machine:
 
         self.log("QEMU running (pid {})".format(self.pid))
 
+    def cleanup_statedir(self) -> None:
+        self.log("delete the VM state directory")
+        if os.path.isfile(self.state_dir):
+            shutil.rmtree(self.state_dir)
+
     def shutdown(self) -> None:
         if not self.booted:
             return
@@ -863,7 +893,8 @@ def run_tests() -> None:
             try:
                 exec(tests, globals())
             except Exception as e:
-                eprint("error: {}".format(str(e)))
+                eprint("error: ")
+                traceback.print_exc()
                 sys.exit(1)
     else:
         ptpython.repl.embed(locals(), globals())
@@ -889,6 +920,15 @@ def subtest(name: str) -> Iterator[None]:
 
 
 if __name__ == "__main__":
+    arg_parser = argparse.ArgumentParser()
+    arg_parser.add_argument(
+        "-K",
+        "--keep-vm-state",
+        help="re-use a VM state coming from a previous run",
+        action="store_true",
+    )
+    (cli_args, vm_scripts) = arg_parser.parse_known_args()
+
     log = Logger()
 
     vlan_nrs = list(dict.fromkeys(os.environ.get("VLANS", "").split()))
@@ -896,8 +936,10 @@ if __name__ == "__main__":
     for nr, vde_socket, _, _ in vde_sockets:
         os.environ["QEMU_VDE_SOCKET_{}".format(nr)] = vde_socket
 
-    vm_scripts = sys.argv[1:]
     machines = [create_machine({"startCommand": s}) for s in vm_scripts]
+    for machine in machines:
+        if not cli_args.keep_vm_state:
+            machine.cleanup_statedir()
     machine_eval = [
         "{0} = machines[{1}]".format(m.name, idx) for idx, m in enumerate(machines)
     ]
@@ -911,7 +953,6 @@ if __name__ == "__main__":
                     continue
                 log.log("killing {} (pid {})".format(machine.name, machine.pid))
                 machine.process.kill()
-
             for _, _, process, _ in vde_sockets:
                 process.terminate()
         log.close()
diff --git a/nixos/lib/testing-python.nix b/nixos/lib/testing-python.nix
index 123323711a7ca..c6939c7d6989c 100644
--- a/nixos/lib/testing-python.nix
+++ b/nixos/lib/testing-python.nix
@@ -114,7 +114,7 @@ rec {
 
       imagemagick_tiff = imagemagick_light.override { inherit libtiff; };
 
-      # Generate onvenience wrappers for running the test driver
+      # Generate convenience wrappers for running the test driver
       # interactively with the specified network, and for starting the
       # VMs from the command line.
       driver = let warn = if skipLint then lib.warn "Linting is disabled!" else lib.id; in warn (runCommand testDriverName
diff --git a/nixos/lib/utils.nix b/nixos/lib/utils.nix
index 21f4c7c6988ff..543c8a8882ea6 100644
--- a/nixos/lib/utils.nix
+++ b/nixos/lib/utils.nix
@@ -2,9 +2,11 @@ pkgs: with pkgs.lib;
 
 rec {
 
-  # Check whenever fileSystem is needed for boot
-  fsNeededForBoot = fs: fs.neededForBoot
-                     || elem fs.mountPoint [ "/" "/nix" "/nix/store" "/var" "/var/log" "/var/lib" "/etc" ];
+  # Check whenever fileSystem is needed for boot.  NOTE: Make sure
+  # pathsNeededForBoot is closed under the parent relationship, i.e. if /a/b/c
+  # is in the list, put /a and /a/b in as well.
+  pathsNeededForBoot = [ "/" "/nix" "/nix/store" "/var" "/var/log" "/var/lib" "/etc" ];
+  fsNeededForBoot = fs: fs.neededForBoot || elem fs.mountPoint pathsNeededForBoot;
 
   # Check whenever `b` depends on `a` as a fileSystem
   fsBefore = a: b: a.mountPoint == b.device
diff --git a/nixos/maintainers/scripts/azure-new/.gitignore b/nixos/maintainers/scripts/azure-new/.gitignore
index 26905a8623473..9271abf14a0f0 100644
--- a/nixos/maintainers/scripts/azure-new/.gitignore
+++ b/nixos/maintainers/scripts/azure-new/.gitignore
@@ -1 +1 @@
-azure
\ No newline at end of file
+azure
diff --git a/nixos/maintainers/scripts/azure-new/README.md b/nixos/maintainers/scripts/azure-new/README.md
index 20e81c44ce5db..e5b69dacec08c 100644
--- a/nixos/maintainers/scripts/azure-new/README.md
+++ b/nixos/maintainers/scripts/azure-new/README.md
@@ -20,7 +20,7 @@ $ ./upload-image.sh ./examples/basic/image.nix
 + nix-build ./examples/basic/image.nix --out-link azure
 /nix/store/qdpzknpskzw30vba92mb24xzll1dqsmd-azure-image
 ...
-95.5 %, 0 Done, 0 Failed, 1 Pending, 0 Skipped, 1 Total, 2-sec Throughput (Mb/s): 932.9565 
+95.5 %, 0 Done, 0 Failed, 1 Pending, 0 Skipped, 1 Total, 2-sec Throughput (Mb/s): 932.9565
 ...
 /subscriptions/aff271ee-e9be-4441-b9bb-42f5af4cbaeb/resourceGroups/nixos-images/providers/Microsoft.Compute/images/azure-image-todo-makethisbetter
 ```
diff --git a/nixos/maintainers/scripts/azure-new/upload-image.sh b/nixos/maintainers/scripts/azure-new/upload-image.sh
index 1466dcd1f0a2e..143afbd7f962e 100755
--- a/nixos/maintainers/scripts/azure-new/upload-image.sh
+++ b/nixos/maintainers/scripts/azure-new/upload-image.sh
@@ -37,8 +37,8 @@ if ! az disk show -g "${group}" -n "${img_name}" &>/dev/null; then
   )"
 
   azcopy copy "${img_file}" "${sasurl}" \
-    --blob-type PageBlob 
-    
+    --blob-type PageBlob
+
   az disk revoke-access \
     --resource-group "${group}" \
     --name "${img_name}"
diff --git a/nixos/maintainers/scripts/ec2/amazon-image.nix b/nixos/maintainers/scripts/ec2/amazon-image.nix
index 36f3e7af873d2..b09f4ca47a3fc 100644
--- a/nixos/maintainers/scripts/ec2/amazon-image.nix
+++ b/nixos/maintainers/scripts/ec2/amazon-image.nix
@@ -63,8 +63,8 @@ in {
     fsType = "ext4";
     configFile = pkgs.writeText "configuration.nix"
       ''
-        {
-          imports = [ <nixpkgs/nixos/modules/virtualisation/amazon-image.nix> ];
+        { modulesPath, ... }: {
+          imports = [ "''${modulesPath}/virtualisation/amazon-image.nix" ];
           ${optionalString config.ec2.hvm ''
             ec2.hvm = true;
           ''}
diff --git a/nixos/maintainers/scripts/ec2/create-amis.sh b/nixos/maintainers/scripts/ec2/create-amis.sh
index 145eb49ced7a3..89e24f2ccfd95 100755
--- a/nixos/maintainers/scripts/ec2/create-amis.sh
+++ b/nixos/maintainers/scripts/ec2/create-amis.sh
@@ -29,7 +29,7 @@ log() {
     echo "$@" >&2
 }
 
-if [ -z "$1" ]; then
+if [ "$#" -ne 1 ]; then
     log "Usage: ./upload-amazon-image.sh IMAGE_OUTPUT"
     exit 1
 fi
diff --git a/nixos/modules/config/appstream.nix b/nixos/modules/config/appstream.nix
index 483ac9c3cd762..a72215c2f5613 100644
--- a/nixos/modules/config/appstream.nix
+++ b/nixos/modules/config/appstream.nix
@@ -7,18 +7,18 @@ 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://www.freedesktop.org/software/appstream/docs/index.html">AppStream metadata specification</link>.
       '';
     };
   };
 
   config = mkIf config.appstream.enable {
-    environment.pathsToLink = [ 
+    environment.pathsToLink = [
       # per component metadata
-      "/share/metainfo" 
+      "/share/metainfo"
       # legacy path for above
-      "/share/appdata" 
+      "/share/appdata"
     ];
   };
 
diff --git a/nixos/modules/config/fonts/fontconfig-penultimate.nix b/nixos/modules/config/fonts/fontconfig-penultimate.nix
deleted file mode 100644
index 7e311a21acf69..0000000000000
--- a/nixos/modules/config/fonts/fontconfig-penultimate.nix
+++ /dev/null
@@ -1,292 +0,0 @@
-{ config, pkgs, lib, ... }:
-
-with lib;
-
-let
-  cfg = config.fonts.fontconfig;
-
-  fcBool = x: "<bool>" + (boolToString x) + "</bool>";
-
-  # back-supported fontconfig version and package
-  # version is used for font cache generation
-  supportVersion = "210";
-  supportPkg     = pkgs."fontconfig_${supportVersion}";
-
-  # latest fontconfig version and package
-  # version is used for configuration folder name, /etc/fonts/VERSION/
-  # note: format differs from supportVersion and can not be used with makeCacheConf
-  latestVersion  = pkgs.fontconfig.configVersion;
-  latestPkg      = pkgs.fontconfig;
-
-  # supported version fonts.conf
-  supportFontsConf = pkgs.makeFontsConf { fontconfig = supportPkg; fontDirectories = config.fonts.fonts; };
-
-  # configuration file to read fontconfig cache
-  # version dependent
-  # priority 0
-  cacheConfSupport = makeCacheConf { version = supportVersion; };
-  cacheConfLatest  = makeCacheConf {};
-
-  # generate the font cache setting file for a fontconfig version
-  # use latest when no version is passed
-  makeCacheConf = { version ? null }:
-    let
-      fcPackage = if version == null
-                  then "fontconfig"
-                  else "fontconfig_${version}";
-      makeCache = fontconfig: pkgs.makeFontsCache { inherit fontconfig; fontDirectories = config.fonts.fonts; };
-      cache     = makeCache pkgs.${fcPackage};
-      cache32   = makeCache pkgs.pkgsi686Linux.${fcPackage};
-    in
-    pkgs.writeText "fc-00-nixos-cache.conf" ''
-      <?xml version='1.0'?>
-      <!DOCTYPE fontconfig SYSTEM 'fonts.dtd'>
-      <fontconfig>
-        <!-- Font directories -->
-        ${concatStringsSep "\n" (map (font: "<dir>${font}</dir>") config.fonts.fonts)}
-        <!-- Pre-generated font caches -->
-        <cachedir>${cache}</cachedir>
-        ${optionalString (pkgs.stdenv.isx86_64 && cfg.cache32Bit) ''
-          <cachedir>${cache32}</cachedir>
-        ''}
-      </fontconfig>
-    '';
-
-  # local configuration file
-  localConf = pkgs.writeText "fc-local.conf" cfg.localConf;
-
-  # rendering settings configuration files
-  # priority 10
-  hintingConf = pkgs.writeText "fc-10-hinting.conf" ''
-    <?xml version='1.0'?>
-    <!DOCTYPE fontconfig SYSTEM 'fonts.dtd'>
-    <fontconfig>
-
-      <!-- Default rendering settings -->
-      <match target="pattern">
-        <edit mode="append" name="hinting">
-          ${fcBool cfg.hinting.enable}
-        </edit>
-        <edit mode="append" name="autohint">
-          ${fcBool cfg.hinting.autohint}
-        </edit>
-        <edit mode="append" name="hintstyle">
-          <const>hintslight</const>
-        </edit>
-      </match>
-
-    </fontconfig>
-  '';
-
-  antialiasConf = pkgs.writeText "fc-10-antialias.conf" ''
-    <?xml version='1.0'?>
-    <!DOCTYPE fontconfig SYSTEM 'fonts.dtd'>
-    <fontconfig>
-
-      <!-- Default rendering settings -->
-      <match target="pattern">
-        <edit mode="append" name="antialias">
-          ${fcBool cfg.antialias}
-        </edit>
-      </match>
-
-    </fontconfig>
-  '';
-
-  subpixelConf = pkgs.writeText "fc-10-subpixel.conf" ''
-    <?xml version='1.0'?>
-    <!DOCTYPE fontconfig SYSTEM 'fonts.dtd'>
-    <fontconfig>
-
-      <!-- Default rendering settings -->
-      <match target="pattern">
-        <edit mode="append" name="rgba">
-          <const>${cfg.subpixel.rgba}</const>
-        </edit>
-        <edit mode="append" name="lcdfilter">
-          <const>lcd${cfg.subpixel.lcdfilter}</const>
-        </edit>
-      </match>
-
-    </fontconfig>
-  '';
-
-  dpiConf = pkgs.writeText "fc-11-dpi.conf" ''
-    <?xml version='1.0'?>
-    <!DOCTYPE fontconfig SYSTEM 'fonts.dtd'>
-    <fontconfig>
-
-      <match target="pattern">
-        <edit name="dpi" mode="assign">
-          <double>${toString cfg.dpi}</double>
-        </edit>
-      </match>
-
-    </fontconfig>
-  '';
-
-  # default fonts configuration file
-  # priority 52
-  defaultFontsConf =
-    let genDefault = fonts: name:
-      optionalString (fonts != []) ''
-        <alias>
-          <family>${name}</family>
-          <prefer>
-          ${concatStringsSep ""
-          (map (font: ''
-            <family>${font}</family>
-          '') fonts)}
-          </prefer>
-        </alias>
-      '';
-    in
-    pkgs.writeText "fc-52-nixos-default-fonts.conf" ''
-    <?xml version='1.0'?>
-    <!DOCTYPE fontconfig SYSTEM 'fonts.dtd'>
-    <fontconfig>
-
-      <!-- Default fonts -->
-      ${genDefault cfg.defaultFonts.sansSerif "sans-serif"}
-
-      ${genDefault cfg.defaultFonts.serif     "serif"}
-
-      ${genDefault cfg.defaultFonts.monospace "monospace"}
-
-    </fontconfig>
-  '';
-
-  # reject Type 1 fonts
-  # priority 53
-  rejectType1 = pkgs.writeText "fc-53-nixos-reject-type1.conf" ''
-    <?xml version="1.0"?>
-    <!DOCTYPE fontconfig SYSTEM "fonts.dtd">
-    <fontconfig>
-
-    <!-- Reject Type 1 fonts -->
-    <selectfont>
-      <rejectfont>
-        <pattern>
-          <patelt name="fontformat"><string>Type 1</string></patelt>
-        </pattern>
-      </rejectfont>
-    </selectfont>
-
-    </fontconfig>
-  '';
-
-  # The configuration to be included in /etc/font/
-  penultimateConf = pkgs.runCommand "fontconfig-penultimate-conf" {
-    preferLocalBuild = true;
-  } ''
-    support_folder=$out/etc/fonts/conf.d
-    latest_folder=$out/etc/fonts/${latestVersion}/conf.d
-
-    mkdir -p $support_folder
-    mkdir -p $latest_folder
-
-    # fonts.conf
-    ln -s ${supportFontsConf} $support_folder/../fonts.conf
-    ln -s ${latestPkg.out}/etc/fonts/fonts.conf \
-          $latest_folder/../fonts.conf
-
-    # fontconfig-penultimate various configuration files
-    ln -s ${pkgs.fontconfig-penultimate}/etc/fonts/conf.d/*.conf \
-          $support_folder
-    ln -s ${pkgs.fontconfig-penultimate}/etc/fonts/conf.d/*.conf \
-          $latest_folder
-
-    ln -s ${cacheConfSupport} $support_folder/00-nixos-cache.conf
-    ln -s ${cacheConfLatest}  $latest_folder/00-nixos-cache.conf
-
-    rm $support_folder/10-antialias.conf $latest_folder/10-antialias.conf
-    ln -s ${antialiasConf} $support_folder/10-antialias.conf
-    ln -s ${antialiasConf} $latest_folder/10-antialias.conf
-
-    rm $support_folder/10-hinting.conf $latest_folder/10-hinting.conf
-    ln -s ${hintingConf} $support_folder/10-hinting.conf
-    ln -s ${hintingConf} $latest_folder/10-hinting.conf
-
-    ${optionalString cfg.useEmbeddedBitmaps ''
-    rm $support_folder/10-no-embedded-bitmaps.conf
-    rm $latest_folder/10-no-embedded-bitmaps.conf
-    ''}
-
-    rm $support_folder/10-subpixel.conf $latest_folder/10-subpixel.conf
-    ln -s ${subpixelConf} $support_folder/10-subpixel.conf
-    ln -s ${subpixelConf} $latest_folder/10-subpixel.conf
-
-    ${optionalString (cfg.dpi != 0) ''
-    ln -s ${dpiConf} $support_folder/11-dpi.conf
-    ln -s ${dpiConf} $latest_folder/11-dpi.conf
-    ''}
-
-    # 50-user.conf
-    ${optionalString (!cfg.includeUserConf) ''
-    rm $support_folder/50-user.conf
-    rm $latest_folder/50-user.conf
-    ''}
-
-    # 51-local.conf
-    rm $latest_folder/51-local.conf
-    substitute \
-      ${pkgs.fontconfig-penultimate}/etc/fonts/conf.d/51-local.conf \
-      $latest_folder/51-local.conf \
-      --replace local.conf /etc/fonts/${latestVersion}/local.conf
-
-    # local.conf (indirect priority 51)
-    ${optionalString (cfg.localConf != "") ''
-    ln -s ${localConf}        $support_folder/../local.conf
-    ln -s ${localConf}        $latest_folder/../local.conf
-    ''}
-
-    # 52-nixos-default-fonts.conf
-    ln -s ${defaultFontsConf} $support_folder/52-nixos-default-fonts.conf
-    ln -s ${defaultFontsConf} $latest_folder/52-nixos-default-fonts.conf
-
-    # 53-no-bitmaps.conf
-    ${optionalString cfg.allowBitmaps ''
-    rm $support_folder/53-no-bitmaps.conf
-    rm $latest_folder/53-no-bitmaps.conf
-    ''}
-
-    ${optionalString (!cfg.allowType1) ''
-    # 53-nixos-reject-type1.conf
-    ln -s ${rejectType1} $support_folder/53-nixos-reject-type1.conf
-    ln -s ${rejectType1} $latest_folder/53-nixos-reject-type1.conf
-    ''}
-  '';
-
-in
-{
-
-  options = {
-
-    fonts = {
-
-      fontconfig = {
-
-        penultimate = {
-          enable = mkOption {
-            type = types.bool;
-            default = false;
-            description = ''
-              Enable fontconfig-penultimate settings to supplement the
-              NixOS defaults by providing per-font rendering defaults and
-              metric aliases.
-            '';
-          };
-        };
-
-      };
-    };
-
-  };
-
-  config = mkIf (config.fonts.fontconfig.enable && config.fonts.fontconfig.penultimate.enable) {
-
-    fonts.fontconfig.confPackages = [ penultimateConf ];
-
-  };
-
-}
diff --git a/nixos/modules/config/fonts/fontconfig.nix b/nixos/modules/config/fonts/fontconfig.nix
index ac2a024eaa894..1f1044bc5af82 100644
--- a/nixos/modules/config/fonts/fontconfig.nix
+++ b/nixos/modules/config/fonts/fontconfig.nix
@@ -1,11 +1,6 @@
 /*
 
-NixOS support 2 fontconfig versions, "support" and "latest".
-
-- "latest" refers to default fontconfig package (pkgs.fontconfig).
-  configuration files are linked to /etc/fonts/VERSION/conf.d/
-- "support" refers to supportPkg (pkgs."fontconfig_${supportVersion}").
-  configuration files are linked to /etc/fonts/conf.d/
+Configuration files are linked to /etc/fonts/${pkgs.fontconfig.configVersion}/conf.d/
 
 This module generates a package containing configuration files and link it in /etc/fonts.
 
@@ -22,40 +17,21 @@ let
   cfg = config.fonts.fontconfig;
 
   fcBool = x: "<bool>" + (boolToString x) + "</bool>";
-
-  # back-supported fontconfig version and package
-  # version is used for font cache generation
-  supportVersion = "210";
-  supportPkg     = pkgs."fontconfig_${supportVersion}";
-
-  # latest fontconfig version and package
-  # version is used for configuration folder name, /etc/fonts/VERSION/
-  # note: format differs from supportVersion and can not be used with makeCacheConf
-  latestVersion  = pkgs.fontconfig.configVersion;
-  latestPkg      = pkgs.fontconfig;
-
-  # supported version fonts.conf
-  supportFontsConf = pkgs.makeFontsConf { fontconfig = supportPkg; fontDirectories = config.fonts.fonts; };
+  pkg = pkgs.fontconfig;
 
   # configuration file to read fontconfig cache
-  # version dependent
   # priority 0
-  cacheConfSupport = makeCacheConf { version = supportVersion; };
-  cacheConfLatest  = makeCacheConf {};
+  cacheConf  = makeCacheConf {};
 
-  # generate the font cache setting file for a fontconfig version
-  # use latest when no version is passed
+  # generate the font cache setting file
   # When cross-compiling, we can’t generate the cache, so we skip the
   # <cachedir> part. fontconfig still works but is a little slower in
   # looking things up.
-  makeCacheConf = { version ? null }:
+  makeCacheConf = { }:
     let
-      fcPackage = if version == null
-                  then "fontconfig"
-                  else "fontconfig_${version}";
       makeCache = fontconfig: pkgs.makeFontsCache { inherit fontconfig; fontDirectories = config.fonts.fonts; };
-      cache     = makeCache pkgs.${fcPackage};
-      cache32   = makeCache pkgs.pkgsi686Linux.${fcPackage};
+      cache     = makeCache pkgs.fontconfig;
+      cache32   = makeCache pkgs.pkgsi686Linux.fontconfig;
     in
     pkgs.writeText "fc-00-nixos-cache.conf" ''
       <?xml version='1.0'?>
@@ -200,63 +176,47 @@ let
   confPkg = pkgs.runCommand "fontconfig-conf" {
     preferLocalBuild = true;
   } ''
-    support_folder=$out/etc/fonts/conf.d
-    latest_folder=$out/etc/fonts/${latestVersion}/conf.d
-
-    mkdir -p $support_folder
-    mkdir -p $latest_folder
+    dst=$out/etc/fonts/${pkg.configVersion}/conf.d
+    mkdir -p $dst
 
     # fonts.conf
-    ln -s ${supportFontsConf} $support_folder/../fonts.conf
-    ln -s ${latestPkg.out}/etc/fonts/fonts.conf \
-          $latest_folder/../fonts.conf
+    ln -s ${pkg.out}/etc/fonts/fonts.conf \
+          $dst/../fonts.conf
+    # TODO: remove this legacy symlink once people stop using packages built before #95358 was merged
+    ln -s /etc/fonts/${pkg.configVersion}/fonts.conf \
+          $out/etc/fonts/fonts.conf
 
     # fontconfig default config files
-    ln -s ${supportPkg.out}/etc/fonts/conf.d/*.conf \
-          $support_folder/
-    ln -s ${latestPkg.out}/etc/fonts/conf.d/*.conf \
-          $latest_folder/
-
-    # update latest 51-local.conf path to look at the latest local.conf
-    rm    $latest_folder/51-local.conf
-
-    substitute ${latestPkg.out}/etc/fonts/conf.d/51-local.conf \
-               $latest_folder/51-local.conf \
-               --replace local.conf /etc/fonts/${latestVersion}/local.conf
+    ln -s ${pkg.out}/etc/fonts/conf.d/*.conf \
+          $dst/
 
     # 00-nixos-cache.conf
-    ln -s ${cacheConfSupport} \
-          $support_folder/00-nixos-cache.conf
-    ln -s ${cacheConfLatest}  $latest_folder/00-nixos-cache.conf
+    ln -s ${cacheConf}  $dst/00-nixos-cache.conf
 
     # 10-nixos-rendering.conf
-    ln -s ${renderConf}       $support_folder/10-nixos-rendering.conf
-    ln -s ${renderConf}       $latest_folder/10-nixos-rendering.conf
+    ln -s ${renderConf}       $dst/10-nixos-rendering.conf
 
     # 50-user.conf
-    ${optionalString (!cfg.includeUserConf) ''
-    rm $support_folder/50-user.conf
-    rm $latest_folder/50-user.conf
+    # Since latest fontconfig looks for default files inside the package,
+    # we had to move this one elsewhere to be able to exclude it here.
+    ${optionalString cfg.includeUserConf ''
+    ln -s ${pkg.out}/etc/fonts/conf.d.bak/50-user.conf $dst/50-user.conf
     ''}
 
     # local.conf (indirect priority 51)
     ${optionalString (cfg.localConf != "") ''
-    ln -s ${localConf}        $support_folder/../local.conf
-    ln -s ${localConf}        $latest_folder/../local.conf
+    ln -s ${localConf}        $dst/../local.conf
     ''}
 
     # 52-nixos-default-fonts.conf
-    ln -s ${defaultFontsConf} $support_folder/52-nixos-default-fonts.conf
-    ln -s ${defaultFontsConf} $latest_folder/52-nixos-default-fonts.conf
+    ln -s ${defaultFontsConf} $dst/52-nixos-default-fonts.conf
 
     # 53-no-bitmaps.conf
-    ln -s ${rejectBitmaps} $support_folder/53-no-bitmaps.conf
-    ln -s ${rejectBitmaps} $latest_folder/53-no-bitmaps.conf
+    ln -s ${rejectBitmaps} $dst/53-no-bitmaps.conf
 
     ${optionalString (!cfg.allowType1) ''
     # 53-nixos-reject-type1.conf
-    ln -s ${rejectType1} $support_folder/53-nixos-reject-type1.conf
-    ln -s ${rejectType1} $latest_folder/53-nixos-reject-type1.conf
+    ln -s ${rejectType1} $dst/53-nixos-reject-type1.conf
     ''}
   '';
 
@@ -490,7 +450,7 @@ in
       environment.systemPackages    = [ pkgs.fontconfig ];
       environment.etc.fonts.source  = "${fontconfigEtc}/etc/fonts/";
     })
-    (mkIf (cfg.enable && !cfg.penultimate.enable) {
+    (mkIf cfg.enable {
       fonts.fontconfig.confPackages = [ confPkg ];
     })
   ];
diff --git a/nixos/modules/config/no-x-libs.nix b/nixos/modules/config/no-x-libs.nix
index 873b8073fed9c..941ab78f86321 100644
--- a/nixos/modules/config/no-x-libs.nix
+++ b/nixos/modules/config/no-x-libs.nix
@@ -27,6 +27,7 @@ with lib;
     fonts.fontconfig.enable = false;
 
     nixpkgs.overlays = singleton (const (super: {
+      cairo = super.cairo.override { x11Support = false; };
       dbus = super.dbus.override { x11Support = false; };
       networkmanager-fortisslvpn = super.networkmanager-fortisslvpn.override { withGnome = false; };
       networkmanager-l2tp = super.networkmanager-l2tp.override { withGnome = false; };
@@ -35,6 +36,7 @@ with lib;
       networkmanager-vpnc = super.networkmanager-vpnc.override { withGnome = false; };
       networkmanager-iodine = super.networkmanager-iodine.override { withGnome = false; };
       gobject-introspection = super.gobject-introspection.override { x11Support = false; };
+      qemu = super.qemu.override { gtkSupport = false; spiceSupport = false; sdlSupport = false; };
     }));
   };
 }
diff --git a/nixos/modules/config/system-path.nix b/nixos/modules/config/system-path.nix
index ae9710e3518b6..b3c5c6f93f368 100644
--- a/nixos/modules/config/system-path.nix
+++ b/nixos/modules/config/system-path.nix
@@ -33,14 +33,11 @@ let
       pkgs.ncurses
       pkgs.netcat
       config.programs.ssh.package
-      pkgs.perl
       pkgs.procps
-      pkgs.rsync
-      pkgs.strace
       pkgs.su
       pkgs.time
       pkgs.utillinux
-      pkgs.which # 88K size
+      pkgs.which
       pkgs.zstd
     ];
 
diff --git a/nixos/modules/config/update-users-groups.pl b/nixos/modules/config/update-users-groups.pl
index 15e448b787aaf..e1c7a46e43048 100644
--- a/nixos/modules/config/update-users-groups.pl
+++ b/nixos/modules/config/update-users-groups.pl
@@ -281,3 +281,58 @@ foreach my $u (values %usersOut) {
 }
 
 updateFile("/etc/shadow", \@shadowNew, 0600);
+
+# Rewrite /etc/subuid & /etc/subgid to include default container mappings
+
+my $subUidMapFile = "/var/lib/nixos/auto-subuid-map";
+my $subUidMap = -e $subUidMapFile ? decode_json(read_file($subUidMapFile)) : {};
+
+my (%subUidsUsed, %subUidsPrevUsed);
+
+$subUidsPrevUsed{$_} = 1 foreach values %{$subUidMap};
+
+sub allocSubUid {
+    my ($name, @rest) = @_;
+
+    # TODO: No upper bounds?
+    my ($min, $max, $up) = (100000, 100000 * 100, 1);
+    my $prevId = $subUidMap->{$name};
+    if (defined $prevId && !defined $subUidsUsed{$prevId}) {
+        $subUidsUsed{$prevId} = 1;
+        return $prevId;
+    }
+
+    my $id = allocId(\%subUidsUsed, \%subUidsPrevUsed, $min, $max, $up, sub { my ($uid) = @_; getpwuid($uid) });
+    my $offset = $id - 100000;
+    my $count = $offset * 65536;
+    my $subordinate = 100000 + $count;
+    return $subordinate;
+}
+
+my @subGids;
+my @subUids;
+foreach my $u (values %usersOut) {
+    my $name = $u->{name};
+
+    foreach my $range (@{$u->{subUidRanges}}) {
+        my $value = join(":", ($name, $range->{startUid}, $range->{count}));
+        push @subUids, $value;
+    }
+
+    foreach my $range (@{$u->{subGidRanges}}) {
+        my $value = join(":", ($name, $range->{startGid}, $range->{count}));
+        push @subGids, $value;
+    }
+
+    if($u->{isNormalUser}) {
+        my $subordinate = allocSubUid($name);
+        $subUidMap->{$name} = $subordinate;
+        my $value = join(":", ($name, $subordinate, 65536));
+        push @subUids, $value;
+        push @subGids, $value;
+    }
+}
+
+updateFile("/etc/subuid", join("\n", @subUids) . "\n");
+updateFile("/etc/subgid", join("\n", @subGids) . "\n");
+updateFile($subUidMapFile, encode_json($subUidMap) . "\n");
diff --git a/nixos/modules/config/users-groups.nix b/nixos/modules/config/users-groups.nix
index 7fbbfcec75107..56b7af98b617b 100644
--- a/nixos/modules/config/users-groups.nix
+++ b/nixos/modules/config/users-groups.nix
@@ -6,6 +6,16 @@ let
   ids = config.ids;
   cfg = config.users;
 
+  # Check whether a password hash will allow login.
+  allowsLogin = hash:
+    hash == "" # login without password
+    || !(lib.elem hash
+      [ null   # password login disabled
+        "!"    # password login disabled
+        "!!"   # a variant of "!"
+        "*"    # password unset
+      ]);
+
   passwordDescription = ''
     The options <option>hashedPassword</option>,
     <option>password</option> and <option>passwordFile</option>
@@ -25,8 +35,19 @@ let
   '';
 
   hashedPasswordDescription = ''
-    To generate hashed password install <literal>mkpasswd</literal>
+    To generate a hashed password install the <literal>mkpasswd</literal>
     package and run <literal>mkpasswd -m sha-512</literal>.
+
+    If set to an empty string (<literal>""</literal>), this user will
+    be able to log in without being asked for a password (but not via remote
+    services such as SSH, or indirectly via <command>su</command> or
+    <command>sudo</command>). This should only be used for e.g. bootable
+    live systems. Note: this is different from setting an empty password,
+    which ca be achieved using <option>users.users.&lt;name?&gt;.password</option>.
+
+    If set to <literal>null</literal> (default) this user will not
+    be able to log in using a password (i.e. via <command>login</command>
+    command).
   '';
 
   userOpts = { name, config, ... }: {
@@ -354,18 +375,6 @@ let
     };
   };
 
-  mkSubuidEntry = user: concatStrings (
-    map (range: "${user.name}:${toString range.startUid}:${toString range.count}\n")
-      user.subUidRanges);
-
-  subuidFile = concatStrings (map mkSubuidEntry (attrValues cfg.users));
-
-  mkSubgidEntry = user: concatStrings (
-    map (range: "${user.name}:${toString range.startGid}:${toString range.count}\n")
-        user.subGidRanges);
-
-  subgidFile = concatStrings (map mkSubgidEntry (attrValues cfg.users));
-
   idsAreUnique = set: idAttr: !(fold (name: args@{ dup, acc }:
     let
       id = builtins.toString (builtins.getAttr idAttr (builtins.getAttr name set));
@@ -385,6 +394,7 @@ let
       { inherit (u)
           name uid group description home createHome isSystemUser
           password passwordFile hashedPassword
+          isNormalUser subUidRanges subGidRanges
           initialPassword initialHashedPassword;
         shell = utils.toShellPath u.shell;
       }) cfg.users;
@@ -406,6 +416,12 @@ in {
   imports = [
     (mkAliasOptionModule [ "users" "extraUsers" ] [ "users" "users" ])
     (mkAliasOptionModule [ "users" "extraGroups" ] [ "users" "groups" ])
+    (mkChangedOptionModule
+      [ "security" "initialRootPassword" ]
+      [ "users" "users" "root" "initialHashedPassword" ]
+      (cfg: if cfg.security.initialRootPassword == "!"
+            then null
+            else cfg.security.initialRootPassword))
   ];
 
   ###### interface
@@ -477,14 +493,6 @@ in {
       '';
     };
 
-    # FIXME: obsolete - will remove.
-    security.initialRootPassword = mkOption {
-      type = types.str;
-      default = "!";
-      example = "";
-      visible = false;
-    };
-
   };
 
 
@@ -499,7 +507,6 @@ in {
         home = "/root";
         shell = mkDefault cfg.defaultUserShell;
         group = "root";
-        initialHashedPassword = mkDefault config.security.initialRootPassword;
       };
       nobody = {
         uid = ids.uids.nobody;
@@ -549,16 +556,7 @@ in {
     # Install all the user shells
     environment.systemPackages = systemShells;
 
-    environment.etc = {
-      subuid = {
-        text = subuidFile;
-        mode = "0644";
-      };
-      subgid = {
-        text = subgidFile;
-        mode = "0644";
-      };
-    } // (mapAttrs' (name: { packages, ... }: {
+    environment.etc = (mapAttrs' (name: { packages, ... }: {
       name = "profiles/per-user/${name}";
       value.source = pkgs.buildEnv {
         name = "user-environment";
@@ -583,22 +581,34 @@ in {
         # password or an SSH authorized key. Privileged accounts are
         # root and users in the wheel group.
         assertion = !cfg.mutableUsers ->
-          any id (mapAttrsToList (name: cfg:
+          any id ((mapAttrsToList (name: cfg:
             (name == "root"
              || cfg.group == "wheel"
              || elem "wheel" cfg.extraGroups)
             &&
-            ((cfg.hashedPassword != null && cfg.hashedPassword != "!")
+            (allowsLogin cfg.hashedPassword
              || cfg.password != null
              || cfg.passwordFile != null
              || cfg.openssh.authorizedKeys.keys != []
              || cfg.openssh.authorizedKeys.keyFiles != [])
-          ) cfg.users);
+          ) cfg.users) ++ [
+            config.security.googleOsLogin.enable
+          ]);
         message = ''
           Neither the root account nor any wheel user has a password or SSH authorized key.
           You must set one to prevent being locked out of your system.'';
       }
-    ];
+    ] ++ flip mapAttrsToList cfg.users (name: user:
+      {
+        assertion = (user.hashedPassword != null)
+                    -> (builtins.match ".*:.*" user.hashedPassword == null);
+        message = ''
+          The password hash of user "${name}" contains a ":" character.
+          This is invalid and would break the login system because the fields
+          of /etc/shadow (file where hashes are stored) are colon-separated.
+          Please check the value of option `users.users."${name}".hashedPassword`.'';
+      }
+    );
 
     warnings =
       builtins.filter (x: x != null) (
@@ -621,14 +631,13 @@ in {
           content = "${base64}${sep}${base64}";
           mcf = "^${sep}${scheme}${sep}${content}$";
         in
-        if (user.hashedPassword != null
+        if (allowsLogin user.hashedPassword
+            && user.hashedPassword != ""  # login without password
             && builtins.match mcf user.hashedPassword == null)
-        then
-        ''
+        then ''
           The password hash of user "${name}" may be invalid. You must set a
-          valid hash or the user will be locked out of his account. Please
-          check the value of option `users.users."${name}".hashedPassword`.
-        ''
+          valid hash or the user will be locked out of their account. Please
+          check the value of option `users.users."${name}".hashedPassword`.''
         else null
       ));
 
diff --git a/nixos/modules/hardware/bladeRF.nix b/nixos/modules/hardware/bladeRF.nix
index 9254434771431..35b74b8382e3a 100644
--- a/nixos/modules/hardware/bladeRF.nix
+++ b/nixos/modules/hardware/bladeRF.nix
@@ -25,4 +25,4 @@ in
     services.udev.packages = [ pkgs.libbladeRF ];
     users.groups.bladerf = {};
   };
-}
\ No newline at end of file
+}
diff --git a/nixos/modules/hardware/ckb-next.nix b/nixos/modules/hardware/ckb-next.nix
index fe0ca9f26d54d..6932be1c54cad 100644
--- a/nixos/modules/hardware/ckb-next.nix
+++ b/nixos/modules/hardware/ckb-next.nix
@@ -43,7 +43,6 @@ in
         serviceConfig = {
           ExecStart = "${cfg.package}/bin/ckb-next-daemon ${optionalString (cfg.gid != null) "--gid=${builtins.toString cfg.gid}"}";
           Restart = "on-failure";
-          StandardOutput = "syslog";
         };
       };
     };
diff --git a/nixos/modules/hardware/device-tree.nix b/nixos/modules/hardware/device-tree.nix
index cf553497c89b3..b3f1dda98c893 100644
--- a/nixos/modules/hardware/device-tree.nix
+++ b/nixos/modules/hardware/device-tree.nix
@@ -22,11 +22,22 @@ in {
           example = literalExample "pkgs.device-tree_rpi";
           type = types.path;
           description = ''
-            The package containing the base device-tree (.dtb) to boot. Contains
+            The path containing the base device-tree (.dtb) to boot. Contains
             device trees bundled with the Linux kernel by default.
           '';
         };
 
+        name = mkOption {
+          default = null;
+          example = "some-dtb.dtb";
+          type = types.nullOr types.str;
+          description = ''
+            The name of an explicit dtb to be loaded, relative to the dtb base.
+            Useful in extlinux scenarios if the bootloader doesn't pick the
+            right .dtb file from FDTDIR.
+          '';
+        };
+
         overlays = mkOption {
           default = [];
           example = literalExample
diff --git a/nixos/modules/hardware/logitech.nix b/nixos/modules/hardware/logitech.nix
index d6f43bdddcc8c..3ebe6aacf5d68 100644
--- a/nixos/modules/hardware/logitech.nix
+++ b/nixos/modules/hardware/logitech.nix
@@ -5,24 +5,92 @@ with lib;
 let
   cfg = config.hardware.logitech;
 
-in {
+  vendor = "046d";
+
+  daemon = "g15daemon";
+
+in
+{
+  imports = [
+    (mkRenamedOptionModule [ "hardware" "logitech" "enable" ] [ "hardware" "logitech" "wireless" "enable" ])
+    (mkRenamedOptionModule [ "hardware" "logitech" "enableGraphical" ] [ "hardware" "logitech" "wireless" "enableGraphical" ])
+  ];
+
   options.hardware.logitech = {
-    enable = mkEnableOption "Logitech Devices";
 
-    enableGraphical = mkOption {
-      type = types.bool;
-      default = false;
-      description = "Enable graphical support applications.";
+    lcd = {
+      enable = mkEnableOption "Logitech LCD Devices";
+
+      startWhenNeeded = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Only run the service when an actual supported device is plugged.
+        '';
+      };
+
+      devices = mkOption {
+        type = types.listOf types.str;
+        default = [ "0a07" "c222" "c225" "c227" "c251" ];
+        description = ''
+          List of USB device ids supported by g15daemon.
+          </para>
+          <para>
+          You most likely do not need to change this.
+        '';
+      };
+    };
+
+    wireless = {
+      enable = mkEnableOption "Logitech Wireless Devices";
+
+      enableGraphical = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Enable graphical support applications.";
+      };
     };
   };
 
-  config = lib.mkIf cfg.enable {
-    environment.systemPackages = [
-      pkgs.ltunify
-    ] ++ lib.optional cfg.enableGraphical pkgs.solaar;
+  config = lib.mkIf (cfg.wireless.enable || cfg.lcd.enable) {
+    environment.systemPackages = []
+      ++ lib.optional cfg.wireless.enable pkgs.ltunify
+      ++ lib.optional cfg.wireless.enableGraphical pkgs.solaar;
+
+    services.udev = {
+      # ltunifi and solaar both provide udev rules but the most up-to-date have been split
+      # out into a dedicated derivation
 
-    # ltunifi and solaar both provide udev rules but the most up-to-date have been split
-    # out into a dedicated derivation
-    services.udev.packages = with pkgs; [ logitech-udev-rules ];
+      packages = []
+      ++ lib.optional cfg.wireless.enable pkgs.logitech-udev-rules
+      ++ lib.optional cfg.lcd.enable pkgs.g15daemon;
+
+      extraRules = ''
+        # nixos: hardware.logitech.lcd
+      '' + lib.concatMapStringsSep "\n" (
+        dev:
+          ''ACTION=="add", SUBSYSTEMS=="usb", ATTRS{idVendor}=="${vendor}", ATTRS{idProduct}=="${dev}", TAG+="systemd", ENV{SYSTEMD_WANTS}+="${daemon}.service"''
+      ) cfg.lcd.devices;
+    };
+
+    systemd.services."${daemon}" = lib.mkIf cfg.lcd.enable {
+      description = "Logitech LCD Support Daemon";
+      documentation = [ "man:g15daemon(1)" ];
+      wantedBy = lib.mkIf (! cfg.lcd.startWhenNeeded) "multi-user.target";
+
+      serviceConfig = {
+        Type = "forking";
+        ExecStart = "${pkgs.g15daemon}/bin/g15daemon";
+        # we patch it to write to /run/g15daemon/g15daemon.pid instead of
+        # /run/g15daemon.pid so systemd will do the cleanup for us.
+        PIDFile = "/run/${daemon}/g15daemon.pid";
+        PrivateTmp = true;
+        PrivateNetwork = true;
+        ProtectHome = "tmpfs";
+        ProtectSystem = "full"; # strict doesn't work
+        RuntimeDirectory = daemon;
+        Restart = "on-failure";
+      };
+    };
   };
 }
diff --git a/nixos/modules/hardware/onlykey.nix b/nixos/modules/hardware/onlykey.nix
index b6820fe01911f..07358c8a87820 100644
--- a/nixos/modules/hardware/onlykey.nix
+++ b/nixos/modules/hardware/onlykey.nix
@@ -26,7 +26,7 @@ with lib;
   ####### implementation
 
   config = mkIf config.hardware.onlykey.enable {
-    services.udev.extraRules = builtin.readFile ./onlykey.udev;
+    services.udev.extraRules = builtins.readFile ./onlykey.udev;
   };
 
 
diff --git a/nixos/modules/hardware/tuxedo-keyboard.nix b/nixos/modules/hardware/tuxedo-keyboard.nix
index 898eed2449355..97af7c61f3c9a 100644
--- a/nixos/modules/hardware/tuxedo-keyboard.nix
+++ b/nixos/modules/hardware/tuxedo-keyboard.nix
@@ -2,7 +2,7 @@
 
 with lib;
 
-let 
+let
   cfg = config.hardware.tuxedo-keyboard;
   tuxedo-keyboard = config.boot.kernelPackages.tuxedo-keyboard;
 in
@@ -27,7 +27,7 @@ in
       '';
     };
 
-    config = mkIf cfg.enable 
+    config = mkIf cfg.enable
     {
       boot.kernelModules = ["tuxedo_keyboard"];
       boot.extraModulePackages = [ tuxedo-keyboard ];
diff --git a/nixos/modules/hardware/video/hidpi.nix b/nixos/modules/hardware/video/hidpi.nix
new file mode 100644
index 0000000000000..ac72b652504ec
--- /dev/null
+++ b/nixos/modules/hardware/video/hidpi.nix
@@ -0,0 +1,16 @@
+{ lib, pkgs, config, ...}:
+with lib;
+
+{
+  options.hardware.video.hidpi.enable = mkEnableOption "Font/DPI configuration optimized for HiDPI displays";
+
+  config = mkIf config.hardware.video.hidpi.enable {
+    console.font = lib.mkDefault "${pkgs.terminus_font}/share/consolefonts/ter-v32n.psf.gz";
+
+    # Needed when typing in passwords for full disk encryption
+    console.earlySetup = mkDefault true;
+    boot.loader.systemd-boot.consoleMode = mkDefault "1";
+
+    # TODO Find reasonable defaults X11 & wayland
+  };
+}
diff --git a/nixos/modules/hardware/video/uvcvideo/default.nix b/nixos/modules/hardware/video/uvcvideo/default.nix
index 7e3e94fdf2bd6..cf6aa052abb05 100644
--- a/nixos/modules/hardware/video/uvcvideo/default.nix
+++ b/nixos/modules/hardware/video/uvcvideo/default.nix
@@ -26,7 +26,7 @@ in
           Whether to enable <command>uvcvideo</command> dynamic controls.
 
           Note that enabling this brings the <command>uvcdynctrl</command> tool
-          into your environement and register all dynamic controls from
+          into your environment and register all dynamic controls from
           specified <command>packages</command> to the <command>uvcvideo</command> driver.
         '';
       };
diff --git a/nixos/modules/hardware/xpadneo.nix b/nixos/modules/hardware/xpadneo.nix
new file mode 100644
index 0000000000000..d504697e61fd9
--- /dev/null
+++ b/nixos/modules/hardware/xpadneo.nix
@@ -0,0 +1,29 @@
+{ config, lib, ... }:
+
+with lib;
+let
+  cfg = config.hardware.xpadneo;
+in
+{
+  options.hardware.xpadneo = {
+    enable = mkEnableOption "the xpadneo driver for Xbox One wireless controllers";
+  };
+
+  config = mkIf cfg.enable {
+    boot = {
+      # Must disable Enhanced Retransmission Mode to support bluetooth pairing
+      # https://wiki.archlinux.org/index.php/Gamepad#Connect_Xbox_Wireless_Controller_with_Bluetooth
+      extraModprobeConfig =
+        mkIf
+          config.hardware.bluetooth.enable
+          "options bluetooth disable_ertm=1";
+
+      extraModulePackages = with config.boot.kernelPackages; [ xpadneo ];
+      kernelModules = [ "hid_xpadneo" ];
+    };
+  };
+
+  meta = {
+    maintainers = with maintainers; [ metadark ];
+  };
+}
diff --git a/nixos/modules/i18n/input-method/ibus.nix b/nixos/modules/i18n/input-method/ibus.nix
index b4746b21b653f..cf24ecf586316 100644
--- a/nixos/modules/i18n/input-method/ibus.nix
+++ b/nixos/modules/i18n/input-method/ibus.nix
@@ -64,7 +64,7 @@ in
     # Without dconf enabled it is impossible to use IBus
     programs.dconf.enable = true;
 
-    programs.dconf.profiles.ibus = "${ibusPackage}/etc/dconf/profile/ibus";
+    programs.dconf.packages = [ ibusPackage ];
 
     services.dbus.packages = [
       ibusAutostart
diff --git a/nixos/modules/i18n/input-method/uim.nix b/nixos/modules/i18n/input-method/uim.nix
index 7ad68bf851feb..459294657e0a6 100644
--- a/nixos/modules/i18n/input-method/uim.nix
+++ b/nixos/modules/i18n/input-method/uim.nix
@@ -2,7 +2,7 @@
 
 with lib;
 
-let 
+let
   cfg = config.i18n.inputMethod.uim;
 in
 {
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 3707c4b7ec62b..8c98691116dc9 100644
--- a/nixos/modules/installer/cd-dvd/installation-cd-graphical-gnome.nix
+++ b/nixos/modules/installer/cd-dvd/installation-cd-graphical-gnome.nix
@@ -11,15 +11,17 @@ with lib;
 
   services.xserver.desktopManager.gnome3.enable = true;
 
-  services.xserver.displayManager.gdm = {
-    enable = true;
-    # autoSuspend makes the machine automatically suspend after inactivity.
-    # It's possible someone could/try to ssh'd into the machine and obviously
-    # have issues because it's inactive.
-    # See:
-    # * https://github.com/NixOS/nixpkgs/pull/63790
-    # * https://gitlab.gnome.org/GNOME/gnome-control-center/issues/22
-    autoSuspend = false;
+  services.xserver.displayManager = {
+    gdm = {
+      enable = true;
+      # autoSuspend makes the machine automatically suspend after inactivity.
+      # It's possible someone could/try to ssh'd into the machine and obviously
+      # have issues because it's inactive.
+      # See:
+      # * https://github.com/NixOS/nixpkgs/pull/63790
+      # * https://gitlab.gnome.org/GNOME/gnome-control-center/issues/22
+      autoSuspend = false;
+    };
     autoLogin = {
       enable = true;
       user = "nixos";
diff --git a/nixos/modules/installer/cd-dvd/installation-cd-graphical-plasma5.nix b/nixos/modules/installer/cd-dvd/installation-cd-graphical-plasma5.nix
index e76e06654aca0..098c2b2870b04 100644
--- a/nixos/modules/installer/cd-dvd/installation-cd-graphical-plasma5.nix
+++ b/nixos/modules/installer/cd-dvd/installation-cd-graphical-plasma5.nix
@@ -16,8 +16,8 @@ with lib;
     };
 
     # Automatically login as nixos.
-    displayManager.sddm = {
-      enable = true;
+    displayManager = {
+      sddm.enable = true;
       autoLogin = {
         enable = true;
         user = "nixos";
diff --git a/nixos/modules/installer/cd-dvd/iso-image.nix b/nixos/modules/installer/cd-dvd/iso-image.nix
index 1cd2252ecf24f..405fbfa10dbfd 100644
--- a/nixos/modules/installer/cd-dvd/iso-image.nix
+++ b/nixos/modules/installer/cd-dvd/iso-image.nix
@@ -417,6 +417,14 @@ in
       '';
     };
 
+    isoImage.squashfsCompression = mkOption {
+      default = "xz -Xdict-size 100%";
+      description = ''
+        Compression settings to use for the squashfs nix store.
+      '';
+      example = "zstd -Xcompression-level 6";
+    };
+
     isoImage.edition = mkOption {
       default = "";
       description = ''
@@ -614,6 +622,7 @@ in
     # Create the squashfs image that contains the Nix store.
     system.build.squashfsStore = pkgs.callPackage ../../../lib/make-squashfs.nix {
       storeContents = config.isoImage.storeContents;
+      comp = config.isoImage.squashfsCompression;
     };
 
     # Individual files to be included on the CD, outside of the Nix
diff --git a/nixos/modules/installer/cd-dvd/sd-image-aarch64.nix b/nixos/modules/installer/cd-dvd/sd-image-aarch64.nix
index 2d34406a0320e..bef6cd2fb5a22 100644
--- a/nixos/modules/installer/cd-dvd/sd-image-aarch64.nix
+++ b/nixos/modules/installer/cd-dvd/sd-image-aarch64.nix
@@ -2,12 +2,6 @@
 # nix-build nixos -I nixos-config=nixos/modules/installer/cd-dvd/sd-image-aarch64.nix -A config.system.build.sdImage
 { config, lib, pkgs, ... }:
 
-let
-  extlinux-conf-builder =
-    import ../../system/boot/loader/generic-extlinux-compatible/extlinux-conf-builder.nix {
-      pkgs = pkgs.buildPackages;
-    };
-in
 {
   imports = [
     ../../profiles/base.nix
@@ -56,7 +50,7 @@ in
       '';
     populateRootCommands = ''
       mkdir -p ./files/boot
-      ${extlinux-conf-builder} -t 3 -c ${config.system.build.toplevel} -d ./files/boot
+      ${config.boot.loader.generic-extlinux-compatible.populateCmd} -c ${config.system.build.toplevel} -d ./files/boot
     '';
   };
 
diff --git a/nixos/modules/installer/cd-dvd/sd-image-armv7l-multiplatform.nix b/nixos/modules/installer/cd-dvd/sd-image-armv7l-multiplatform.nix
index 651d1a36dc11b..d2ba611532e0b 100644
--- a/nixos/modules/installer/cd-dvd/sd-image-armv7l-multiplatform.nix
+++ b/nixos/modules/installer/cd-dvd/sd-image-armv7l-multiplatform.nix
@@ -2,12 +2,6 @@
 # nix-build nixos -I nixos-config=nixos/modules/installer/cd-dvd/sd-image-armv7l-multiplatform.nix -A config.system.build.sdImage
 { config, lib, pkgs, ... }:
 
-let
-  extlinux-conf-builder =
-    import ../../system/boot/loader/generic-extlinux-compatible/extlinux-conf-builder.nix {
-      pkgs = pkgs.buildPackages;
-    };
-in
 {
   imports = [
     ../../profiles/base.nix
@@ -53,7 +47,7 @@ in
       '';
     populateRootCommands = ''
       mkdir -p ./files/boot
-      ${extlinux-conf-builder} -t 3 -c ${config.system.build.toplevel} -d ./files/boot
+      ${config.boot.loader.generic-extlinux-compatible.populateCmd} -c ${config.system.build.toplevel} -d ./files/boot
     '';
   };
 
diff --git a/nixos/modules/installer/cd-dvd/sd-image-raspberrypi.nix b/nixos/modules/installer/cd-dvd/sd-image-raspberrypi.nix
index ba4127eaa0e85..40a01f9617710 100644
--- a/nixos/modules/installer/cd-dvd/sd-image-raspberrypi.nix
+++ b/nixos/modules/installer/cd-dvd/sd-image-raspberrypi.nix
@@ -2,12 +2,6 @@
 # nix-build nixos -I nixos-config=nixos/modules/installer/cd-dvd/sd-image-raspberrypi.nix -A config.system.build.sdImage
 { config, lib, pkgs, ... }:
 
-let
-  extlinux-conf-builder =
-    import ../../system/boot/loader/generic-extlinux-compatible/extlinux-conf-builder.nix {
-      pkgs = pkgs.buildPackages;
-    };
-in
 {
   imports = [
     ../../profiles/base.nix
@@ -42,7 +36,7 @@ in
       '';
     populateRootCommands = ''
       mkdir -p ./files/boot
-      ${extlinux-conf-builder} -t 3 -c ${config.system.build.toplevel} -d ./files/boot
+      ${config.boot.loader.generic-extlinux-compatible.populateCmd} -c ${config.system.build.toplevel} -d ./files/boot
     '';
   };
 
diff --git a/nixos/modules/installer/cd-dvd/sd-image.nix b/nixos/modules/installer/cd-dvd/sd-image.nix
index c15befa59e262..ddad1116c94af 100644
--- a/nixos/modules/installer/cd-dvd/sd-image.nix
+++ b/nixos/modules/installer/cd-dvd/sd-image.nix
@@ -99,7 +99,7 @@ in
     };
 
     populateRootCommands = mkOption {
-      example = literalExample "''\${extlinux-conf-builder} -t 3 -c \${config.system.build.toplevel} -d ./files/boot''";
+      example = literalExample "''\${config.boot.loader.generic-extlinux-compatible.populateCmd} -c \${config.system.build.toplevel} -d ./files/boot''";
       description = ''
         Shell commands to populate the ./files directory.
         All files in that directory are copied to the
diff --git a/nixos/modules/installer/cd-dvd/system-tarball-pc-readme.txt b/nixos/modules/installer/cd-dvd/system-tarball-pc-readme.txt
index 84252f292c54a..887bf60d0fbe4 100644
--- a/nixos/modules/installer/cd-dvd/system-tarball-pc-readme.txt
+++ b/nixos/modules/installer/cd-dvd/system-tarball-pc-readme.txt
@@ -63,7 +63,7 @@ Activate the system: look for a directory in nix/store similar to:
 Having found it, activate that nixos system *twice*:
   chroot . /nix/store/SOMETHING-nixos-SOMETHING/activate
   chroot . /nix/store/SOMETHING-nixos-SOMETHING/activate
-  
+
 This runs a 'hostname' command. Restore your old hostname with:
   hostname OLDHOSTNAME
 
diff --git a/nixos/modules/installer/tools/nix-fallback-paths.nix b/nixos/modules/installer/tools/nix-fallback-paths.nix
index 35a7b382b7cf7..a15a2dbadb8c6 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/j8dbv5w6jl34caywh2ygdy88knx1mdf7-nix-2.3.6";
-  i686-linux = "/nix/store/9fqvbdisahqp0238vrs7wn5anpri0a65-nix-2.3.6";
-  aarch64-linux = "/nix/store/72pwn0nm9bjqx9vpi8sgh4bl6g5wh814-nix-2.3.6";
-  x86_64-darwin = "/nix/store/g37vk77m90p5zcl5nixjlzp3vqpisfn5-nix-2.3.6";
+  x86_64-linux = "/nix/store/4vz8sh9ngx34ivi0bw5hlycxdhvy5hvz-nix-2.3.7";
+  i686-linux = "/nix/store/dzxkg9lpp60bjmzvagns42vqlz3yq5kx-nix-2.3.7";
+  aarch64-linux = "/nix/store/cfvf8nl8mwyw817by5y8zd3s8pnf5m9f-nix-2.3.7";
+  x86_64-darwin = "/nix/store/5ira7xgs92inqz1x8l0n1wci4r79hnd0-nix-2.3.7";
 }
diff --git a/nixos/modules/installer/tools/nixos-build-vms/nixos-build-vms.sh b/nixos/modules/installer/tools/nixos-build-vms/nixos-build-vms.sh
index 25106733087ef..2a6c3ab11497f 100644
--- a/nixos/modules/installer/tools/nixos-build-vms/nixos-build-vms.sh
+++ b/nixos/modules/installer/tools/nixos-build-vms/nixos-build-vms.sh
@@ -1,4 +1,4 @@
-#! @shell@ -e
+#! @runtimeShell@ -e
 
 # Shows the usage of this command to the user
 
diff --git a/nixos/modules/installer/tools/nixos-enter.sh b/nixos/modules/installer/tools/nixos-enter.sh
index 1fdd4627a902c..c72ef6e9c28b3 100644
--- a/nixos/modules/installer/tools/nixos-enter.sh
+++ b/nixos/modules/installer/tools/nixos-enter.sh
@@ -1,4 +1,4 @@
-#! @shell@
+#! @runtimeShell@
 
 set -e
 
diff --git a/nixos/modules/installer/tools/nixos-generate-config.pl b/nixos/modules/installer/tools/nixos-generate-config.pl
index 157dc28e0a831..c8303a6eb6020 100644
--- a/nixos/modules/installer/tools/nixos-generate-config.pl
+++ b/nixos/modules/installer/tools/nixos-generate-config.pl
@@ -497,8 +497,8 @@ if (-f $fb_modes_file && -r $fb_modes_file) {
     $modes =~ m/([0-9]+)x([0-9]+)/;
     my $console_width = $1, my $console_height = $2;
     if ($console_width > 1920) {
-        push @attrs, "# High-DPI console";
-        push @attrs, 'console.font = lib.mkDefault "${pkgs.terminus_font}/share/consolefonts/ter-u28n.psf.gz";';
+        push @attrs, "# high-resolution display";
+        push @attrs, 'hardware.video.hidpi.enable = lib.mkDefault true;';
     }
 }
 
diff --git a/nixos/modules/installer/tools/nixos-install.sh b/nixos/modules/installer/tools/nixos-install.sh
index 1bccbbfaf245e..e0252befdfdcb 100644
--- a/nixos/modules/installer/tools/nixos-install.sh
+++ b/nixos/modules/installer/tools/nixos-install.sh
@@ -1,4 +1,4 @@
-#! @shell@
+#! @runtimeShell@
 
 set -e
 shopt -s nullglob
@@ -71,6 +71,17 @@ if ! test -e "$mountPoint"; then
     exit 1
 fi
 
+# Verify permissions are okay-enough
+checkPath="$(realpath "$mountPoint")"
+while [[ "$checkPath" != "/" ]]; do
+    mode="$(stat -c '%a' "$checkPath")"
+    if [[ "${mode: -1}" -lt "5" ]]; then
+        echo "path $checkPath should have permissions 755, but had permissions $mode. Consider running 'chmod o+rx $checkPath'."
+        exit 1
+    fi
+    checkPath="$(dirname "$checkPath")"
+done
+
 # Get the path of the NixOS configuration file.
 if [[ -z $NIXOS_CONFIG ]]; then
     NIXOS_CONFIG=$mountPoint/etc/nixos/configuration.nix
diff --git a/nixos/modules/installer/tools/nixos-rebuild.sh b/nixos/modules/installer/tools/nixos-rebuild.sh
index 354274478a384..437199bae1d1c 100644
--- a/nixos/modules/installer/tools/nixos-rebuild.sh
+++ b/nixos/modules/installer/tools/nixos-rebuild.sh
@@ -1,6 +1,6 @@
-#! @shell@
+#! @runtimeShell@
 
-if [ -x "@shell@" ]; then export SHELL="@shell@"; fi;
+if [ -x "@runtimeShell@" ]; then export SHELL="@runtimeShell@"; fi;
 
 set -e
 set -o pipefail
diff --git a/nixos/modules/installer/tools/nixos-version.sh b/nixos/modules/installer/tools/nixos-version.sh
index fb0fe26116a6e..f5e3f32b3c632 100644
--- a/nixos/modules/installer/tools/nixos-version.sh
+++ b/nixos/modules/installer/tools/nixos-version.sh
@@ -1,4 +1,4 @@
-#! @shell@
+#! @runtimeShell@
 
 case "$1" in
   -h|--help)
diff --git a/nixos/modules/installer/tools/tools.nix b/nixos/modules/installer/tools/tools.nix
index 111286214248d..1582f04930948 100644
--- a/nixos/modules/installer/tools/tools.nix
+++ b/nixos/modules/installer/tools/tools.nix
@@ -14,11 +14,13 @@ let
   nixos-build-vms = makeProg {
     name = "nixos-build-vms";
     src = ./nixos-build-vms/nixos-build-vms.sh;
+    inherit (pkgs) runtimeShell;
   };
 
   nixos-install = makeProg {
     name = "nixos-install";
     src = ./nixos-install.sh;
+    inherit (pkgs) runtimeShell;
     nix = config.nix.package.out;
     path = makeBinPath [ nixos-enter ];
   };
@@ -28,6 +30,7 @@ let
     makeProg {
       name = "nixos-rebuild";
       src = ./nixos-rebuild.sh;
+      inherit (pkgs) runtimeShell;
       nix = config.nix.package.out;
       nix_x86_64_linux = fallback.x86_64-linux;
       nix_i686_linux = fallback.i686-linux;
@@ -50,6 +53,7 @@ let
   nixos-version = makeProg {
     name = "nixos-version";
     src = ./nixos-version.sh;
+    inherit (pkgs) runtimeShell;
     inherit (config.system.nixos) version codeName revision;
     inherit (config.system) configurationRevision;
     json = builtins.toJSON ({
@@ -64,6 +68,7 @@ let
   nixos-enter = makeProg {
     name = "nixos-enter";
     src = ./nixos-enter.sh;
+    inherit (pkgs) runtimeShell;
   };
 
 in
diff --git a/nixos/modules/misc/documentation.nix b/nixos/modules/misc/documentation.nix
index 7ad4be9a02e69..71a40b4f4d6e2 100644
--- a/nixos/modules/misc/documentation.nix
+++ b/nixos/modules/misc/documentation.nix
@@ -102,6 +102,16 @@ in
         '';
       };
 
+      man.generateCaches = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to generate the manual page index caches using
+          <literal>mandb(8)</literal>. This allows searching for a page or
+          keyword using utilities like <literal>apropos(1)</literal>.
+        '';
+      };
+
       info.enable = mkOption {
         type = types.bool;
         default = true;
@@ -187,7 +197,33 @@ in
       environment.systemPackages = [ pkgs.man-db ];
       environment.pathsToLink = [ "/share/man" ];
       environment.extraOutputsToInstall = [ "man" ] ++ optional cfg.dev.enable "devman";
-      environment.etc."man.conf".source = "${pkgs.man-db}/etc/man_db.conf";
+      environment.etc."man_db.conf".text =
+        let
+          manualPages = pkgs.buildEnv {
+            name = "man-paths";
+            paths = config.environment.systemPackages;
+            pathsToLink = [ "/share/man" ];
+            extraOutputsToInstall = ["man"];
+            ignoreCollisions = true;
+          };
+          manualCache = pkgs.runCommandLocal "man-cache" { }
+          ''
+            echo "MANDB_MAP ${manualPages}/share/man $out" > man.conf
+            ${pkgs.man-db}/bin/mandb -C man.conf -psc
+          '';
+        in
+        ''
+          # Manual pages paths for NixOS
+          MANPATH_MAP /run/current-system/sw/bin /run/current-system/sw/share/man
+          MANPATH_MAP /run/wrappers/bin          /run/current-system/sw/share/man
+
+          ${optionalString cfg.man.generateCaches ''
+          # Generated manual pages cache for NixOS (immutable)
+          MANDB_MAP /run/current-system/sw/share/man ${manualCache}
+          ''}
+          # Manual pages caches for NixOS
+          MANDB_MAP /run/current-system/sw/share/man /var/cache/man/nixos
+        '';
     })
 
     (mkIf cfg.info.enable {
diff --git a/nixos/modules/misc/ids.nix b/nixos/modules/misc/ids.nix
index 3409e7ba22ea4..394da9a388922 100644
--- a/nixos/modules/misc/ids.nix
+++ b/nixos/modules/misc/ids.nix
@@ -198,7 +198,7 @@ in
       bosun = 161;
       kubernetes = 162;
       peerflix = 163;
-      chronos = 164;
+      #chronos = 164; # removed 2020-08-15
       gitlab = 165;
       tox-bootstrapd = 166;
       cadvisor = 167;
@@ -247,7 +247,7 @@ in
       bepasty = 215;
       # pumpio = 216; # unused, removed 2018-02-24
       nm-openvpn = 217;
-      mathics = 218;
+      # mathics = 218; # unused, removed 2020-08-15
       ejabberd = 219;
       postsrsd = 220;
       opendkim = 221;
@@ -321,7 +321,7 @@ in
       monetdb = 290;
       restic = 291;
       openvpn = 292;
-      meguca = 293;
+      # meguca = 293; # removed 2020-08-21
       yarn = 294;
       hdfs = 295;
       mapred = 296;
@@ -345,6 +345,7 @@ in
       zoneminder = 314;
       paperless = 315;
       #mailman = 316;  # removed 2019-08-30
+      zigbee2mqtt = 317;
 
       # When adding a uid, make sure it doesn't match an existing gid. And don't use uids above 399!
 
@@ -621,7 +622,7 @@ in
       monetdb = 290;
       restic = 291;
       openvpn = 292;
-      meguca = 293;
+      # meguca = 293; # removed 2020-08-21
       yarn = 294;
       hdfs = 295;
       mapred = 296;
@@ -645,6 +646,7 @@ in
       zoneminder = 314;
       paperless = 315;
       #mailman = 316;  # removed 2019-08-30
+      zigbee2mqtt = 317;
 
       # 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/module-list.nix b/nixos/modules/module-list.nix
index 0dba92f60c7e7..aee1fdb368d39 100644
--- a/nixos/modules/module-list.nix
+++ b/nixos/modules/module-list.nix
@@ -1,7 +1,6 @@
 [
   ./config/debug-info.nix
   ./config/fonts/fontconfig.nix
-  ./config/fonts/fontconfig-penultimate.nix
   ./config/fonts/fontdir.nix
   ./config/fonts/fonts.nix
   ./config/fonts/ghostscript.nix
@@ -72,9 +71,11 @@
   ./hardware/video/capture/mwprocapture.nix
   ./hardware/video/bumblebee.nix
   ./hardware/video/displaylink.nix
+  ./hardware/video/hidpi.nix
   ./hardware/video/nvidia.nix
   ./hardware/video/uvcvideo/default.nix
   ./hardware/video/webcam/facetimehd.nix
+  ./hardware/xpadneo.nix
   ./i18n/input-method/default.nix
   ./i18n/input-method/fcitx.nix
   ./i18n/input-method/ibus.nix
@@ -154,6 +155,7 @@
   ./programs/ssmtp.nix
   ./programs/sysdig.nix
   ./programs/systemtap.nix
+  ./programs/steam.nix
   ./programs/sway.nix
   ./programs/system-config-printer.nix
   ./programs/thefuck.nix
@@ -328,6 +330,7 @@
   ./services/development/bloop.nix
   ./services/development/hoogle.nix
   ./services/development/jupyter/default.nix
+  ./services/development/jupyterhub/default.nix
   ./services/development/lorri.nix
   ./services/editors/emacs.nix
   ./services/editors/infinoted.nix
@@ -462,14 +465,11 @@
   ./services/misc/leaps.nix
   ./services/misc/lidarr.nix
   ./services/misc/mame.nix
-  ./services/misc/mathics.nix
   ./services/misc/matrix-appservice-discord.nix
   ./services/misc/matrix-synapse.nix
   ./services/misc/mautrix-telegram.nix
   ./services/misc/mbpfan.nix
   ./services/misc/mediatomb.nix
-  ./services/misc/mesos-master.nix
-  ./services/misc/mesos-slave.nix
   ./services/misc/metabase.nix
   ./services/misc/mwlib.nix
   ./services/misc/nix-daemon.nix
@@ -485,6 +485,7 @@
   ./services/misc/parsoid.nix
   ./services/misc/plex.nix
   ./services/misc/tautulli.nix
+  ./services/misc/pinnwand.nix
   ./services/misc/pykms.nix
   ./services/misc/radarr.nix
   ./services/misc/redmine.nix
@@ -510,6 +511,7 @@
   ./services/misc/uhub.nix
   ./services/misc/weechat.nix
   ./services/misc/xmr-stak.nix
+  ./services/misc/zigbee2mqtt.nix
   ./services/misc/zoneminder.nix
   ./services/misc/zookeeper.nix
   ./services/monitoring/alerta.nix
@@ -589,6 +591,7 @@
   ./services/networking/autossh.nix
   ./services/networking/bird.nix
   ./services/networking/bitlbee.nix
+  ./services/networking/blockbook-frontend.nix
   ./services/networking/charybdis.nix
   ./services/networking/cjdns.nix
   ./services/networking/cntlm.nix
@@ -606,6 +609,7 @@
   ./services/networking/dnscrypt-wrapper.nix
   ./services/networking/dnsdist.nix
   ./services/networking/dnsmasq.nix
+  ./services/networking/ncdns.nix
   ./services/networking/ejabberd.nix
   ./services/networking/epmd.nix
   ./services/networking/ergo.nix
@@ -640,6 +644,8 @@
   ./services/networking/iperf3.nix
   ./services/networking/ircd-hybrid/default.nix
   ./services/networking/iwd.nix
+  ./services/networking/jicofo.nix
+  ./services/networking/jitsi-videobridge.nix
   ./services/networking/keepalived/default.nix
   ./services/networking/keybase.nix
   ./services/networking/kippo.nix
@@ -668,6 +674,7 @@
   ./services/networking/nat.nix
   ./services/networking/ndppd.nix
   ./services/networking/networkmanager.nix
+  ./services/networking/nextdns.nix
   ./services/networking/nftables.nix
   ./services/networking/ngircd.nix
   ./services/networking/nghttpx/default.nix
@@ -685,6 +692,7 @@
   ./services/networking/ocserv.nix
   ./services/networking/ofono.nix
   ./services/networking/oidentd.nix
+  ./services/networking/onedrive.nix
   ./services/networking/openfire.nix
   ./services/networking/openvpn.nix
   ./services/networking/ostinato.nix
@@ -774,10 +782,8 @@
   ./services/networking/znc/default.nix
   ./services/printing/cupsd.nix
   ./services/scheduling/atd.nix
-  ./services/scheduling/chronos.nix
   ./services/scheduling/cron.nix
   ./services/scheduling/fcron.nix
-  ./services/scheduling/marathon.nix
   ./services/search/elasticsearch.nix
   ./services/search/elasticsearch-curator.nix
   ./services/search/hound.nix
@@ -808,6 +814,7 @@
   ./services/security/torsocks.nix
   ./services/security/usbguard.nix
   ./services/security/vault.nix
+  ./services/security/yubikey-agent.nix
   ./services/system/cloud-init.nix
   ./services/system/dbus.nix
   ./services/system/earlyoom.nix
@@ -827,10 +834,12 @@
   ./services/ttys/gpm.nix
   ./services/ttys/kmscon.nix
   ./services/wayland/cage.nix
+  ./services/video/mirakurun.nix
   ./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/convos.nix
   ./services/web-apps/cryptpad.nix
   ./services/web-apps/documize.nix
   ./services/web-apps/dokuwiki.nix
@@ -843,6 +852,7 @@
   ./services/web-apps/icingaweb2/module-monitoring.nix
   ./services/web-apps/ihatemoney
   ./services/web-apps/jirafeau.nix
+  ./services/web-apps/jitsi-meet.nix
   ./services/web-apps/limesurvey.nix
   ./services/web-apps/mattermost.nix
   ./services/web-apps/mediawiki.nix
@@ -855,6 +865,7 @@
   ./services/web-apps/moinmoin.nix
   ./services/web-apps/restya-board.nix
   ./services/web-apps/sogo.nix
+  ./services/web-apps/rss-bridge.nix
   ./services/web-apps/tt-rss.nix
   ./services/web-apps/trac.nix
   ./services/web-apps/trilium.nix
@@ -875,9 +886,9 @@
   ./services/web-servers/lighttpd/collectd.nix
   ./services/web-servers/lighttpd/default.nix
   ./services/web-servers/lighttpd/gitweb.nix
-  ./services/web-servers/meguca.nix
   ./services/web-servers/mighttpd2.nix
   ./services/web-servers/minio.nix
+  ./services/web-servers/molly-brown.nix
   ./services/web-servers/nginx/default.nix
   ./services/web-servers/nginx/gitweb.nix
   ./services/web-servers/phpfpm/default.nix
@@ -912,6 +923,7 @@
   ./services/x11/gdk-pixbuf.nix
   ./services/x11/imwheel.nix
   ./services/x11/redshift.nix
+  ./services/x11/urserver.nix
   ./services/x11/urxvtd.nix
   ./services/x11/window-managers/awesome.nix
   ./services/x11/window-managers/default.nix
@@ -935,6 +947,7 @@
   ./system/boot/grow-partition.nix
   ./system/boot/initrd-network.nix
   ./system/boot/initrd-ssh.nix
+  ./system/boot/initrd-openvpn.nix
   ./system/boot/kernel.nix
   ./system/boot/kexec.nix
   ./system/boot/loader/efi.nix
diff --git a/nixos/modules/profiles/base.nix b/nixos/modules/profiles/base.nix
index 2a2fe119d30cb..3b67d628f9fd7 100644
--- a/nixos/modules/profiles/base.nix
+++ b/nixos/modules/profiles/base.nix
@@ -26,6 +26,7 @@
     pkgs.fuse
     pkgs.fuse3
     pkgs.sshfs-fuse
+    pkgs.rsync
     pkgs.socat
     pkgs.screen
 
diff --git a/nixos/modules/profiles/demo.nix b/nixos/modules/profiles/demo.nix
index 18f190071bad8..4e8c74deedba3 100644
--- a/nixos/modules/profiles/demo.nix
+++ b/nixos/modules/profiles/demo.nix
@@ -11,9 +11,11 @@
       uid = 1000;
     };
 
-  services.xserver.displayManager.sddm.autoLogin = {
-    enable = true;
-    relogin = true;
-    user = "demo";
+  services.xserver.displayManager = {
+    autoLogin = {
+      enable = true;
+      user = "demo";
+    };
+    sddm.autoLogin.relogin = true;
   };
 }
diff --git a/nixos/modules/programs/autojump.nix b/nixos/modules/programs/autojump.nix
index 3a8feec4bb45f..ecfc2f658079d 100644
--- a/nixos/modules/programs/autojump.nix
+++ b/nixos/modules/programs/autojump.nix
@@ -18,7 +18,7 @@ in
         '';
       };
     };
-  }; 
+  };
 
   ###### implementation
 
@@ -26,7 +26,7 @@ in
     environment.pathsToLink = [ "/share/autojump" ];
     environment.systemPackages = [ pkgs.autojump ];
 
-    programs.bash.interactiveShellInit = "source ${pkgs.autojump}/share/autojump/autojump.bash"; 
+    programs.bash.interactiveShellInit = "source ${pkgs.autojump}/share/autojump/autojump.bash";
     programs.zsh.interactiveShellInit = mkIf prg.zsh.enable "source ${pkgs.autojump}/share/autojump/autojump.zsh";
     programs.fish.interactiveShellInit = mkIf prg.fish.enable "source ${pkgs.autojump}/share/autojump/autojump.fish";
   };
diff --git a/nixos/modules/programs/ccache.nix b/nixos/modules/programs/ccache.nix
index 874774c72b47c..3c9e64932f16e 100644
--- a/nixos/modules/programs/ccache.nix
+++ b/nixos/modules/programs/ccache.nix
@@ -80,4 +80,4 @@ in {
       ];
     })
   ];
-}
\ No newline at end of file
+}
diff --git a/nixos/modules/programs/dconf.nix b/nixos/modules/programs/dconf.nix
index 6702e8efd1cb4..ec85cb9d18c91 100644
--- a/nixos/modules/programs/dconf.nix
+++ b/nixos/modules/programs/dconf.nix
@@ -4,13 +4,24 @@ with lib;
 
 let
   cfg = config.programs.dconf;
-
-  mkDconfProfile = name: path:
-    {
-      name = "dconf/profile/${name}";
-      value.source = path; 
-    };
-
+  cfgDir = pkgs.symlinkJoin {
+    name = "dconf-system-config";
+    paths = map (x: "${x}/etc/dconf") cfg.packages;
+    postBuild = ''
+      mkdir -p $out/profile
+      mkdir -p $out/db
+    '' + (
+      concatStringsSep "\n" (
+        mapAttrsToList (
+          name: path: ''
+            ln -s ${path} $out/profile/${name}
+          ''
+        ) cfg.profiles
+      )
+    ) + ''
+      ${pkgs.dconf}/bin/dconf update $out/db
+    '';
+  };
 in
 {
   ###### interface
@@ -22,18 +33,24 @@ in
       profiles = mkOption {
         type = types.attrsOf types.path;
         default = {};
-        description = "Set of dconf profile files.";
+        description = "Set of dconf profile files, installed at <filename>/etc/dconf/profiles/<replaceable>name</replaceable></filename>.";
         internal = true;
       };
 
+      packages = mkOption {
+        type = types.listOf types.package;
+        default = [];
+        description = "A list of packages which provide dconf profiles and databases in <filename>/etc/dconf</filename>.";
+      };
     };
   };
 
   ###### implementation
 
   config = mkIf (cfg.profiles != {} || cfg.enable) {
-    environment.etc = optionalAttrs (cfg.profiles != {})
-      (mapAttrs' mkDconfProfile cfg.profiles);
+    environment.etc.dconf = mkIf (cfg.profiles != {} || cfg.packages != []) {
+      source = cfgDir;
+    };
 
     services.dbus.packages = [ pkgs.dconf ];
 
diff --git a/nixos/modules/programs/fish.nix b/nixos/modules/programs/fish.nix
index 48b324a0fe83c..39b92edf2ac2e 100644
--- a/nixos/modules/programs/fish.nix
+++ b/nixos/modules/programs/fish.nix
@@ -102,6 +102,9 @@ in
 
     programs.fish.shellAliases = mapAttrs (name: mkDefault) cfge.shellAliases;
 
+    # Required for man completions
+    documentation.man.generateCaches = true;
+
     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;
diff --git a/nixos/modules/programs/freetds.nix b/nixos/modules/programs/freetds.nix
index e0860a242b741..b4b657e391bf9 100644
--- a/nixos/modules/programs/freetds.nix
+++ b/nixos/modules/programs/freetds.nix
@@ -25,7 +25,7 @@ in
           ''';
         }
       '';
-      description = 
+      description =
         ''
         Configure freetds database entries. Each attribute denotes
         a section within freetds.conf, and the value (a string) is the config
@@ -47,7 +47,7 @@ in
     environment.variables.FREETDS = "/etc/freetds.conf";
     environment.variables.SYBASE = "${pkgs.freetds}";
 
-    environment.etc."freetds.conf" = { text = 
+    environment.etc."freetds.conf" = { text =
       (concatStrings (mapAttrsToList (name: value:
         ''
         [${name}]
diff --git a/nixos/modules/programs/gnupg.nix b/nixos/modules/programs/gnupg.nix
index 7a3cb588ee719..ce8799b21d692 100644
--- a/nixos/modules/programs/gnupg.nix
+++ b/nixos/modules/programs/gnupg.nix
@@ -70,6 +70,7 @@ in
     agent.pinentryFlavor = mkOption {
       type = types.nullOr (types.enum pkgs.pinentry.flavors);
       example = "gnome3";
+      default = defaultPinentryFlavor;
       description = ''
         Which pinentry interface to use. If not null, the path to the
         pinentry binary will be passed to gpg-agent via commandline and
@@ -91,8 +92,6 @@ in
   };
 
   config = mkIf cfg.agent.enable {
-    programs.gnupg.agent.pinentryFlavor = mkDefault defaultPinentryFlavor;
-
     # This overrides the systemd user unit shipped with the gnupg package
     systemd.user.services.gpg-agent = mkIf (cfg.agent.pinentryFlavor != null) {
       serviceConfig.ExecStart = [ "" ''
diff --git a/nixos/modules/programs/steam.nix b/nixos/modules/programs/steam.nix
new file mode 100644
index 0000000000000..3c919c47a0c61
--- /dev/null
+++ b/nixos/modules/programs/steam.nix
@@ -0,0 +1,25 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.programs.steam;
+in {
+  options.programs.steam.enable = mkEnableOption "steam";
+
+  config = mkIf cfg.enable {
+    hardware.opengl = { # this fixes the "glXChooseVisual failed" bug, context: https://github.com/NixOS/nixpkgs/issues/47932
+      enable = true;
+      driSupport32Bit = true;
+    };
+
+    # optionally enable 32bit pulseaudio support if pulseaudio is enabled
+    hardware.pulseaudio.support32Bit = config.hardware.pulseaudio.enable;
+
+    hardware.steam-hardware.enable = true;
+
+    environment.systemPackages = [ pkgs.steam ];
+  };
+
+  meta.maintainers = with maintainers; [ mkg20001 ];
+}
diff --git a/nixos/modules/rename.nix b/nixos/modules/rename.nix
index 1dc7f85331769..1fe00e9142bae 100644
--- a/nixos/modules/rename.nix
+++ b/nixos/modules/rename.nix
@@ -17,8 +17,12 @@ with lib;
     (mkAliasOptionModule [ "environment" "checkConfigurationOptions" ] [ "_module" "check" ])
 
     # Completely removed modules
+    (mkRemovedOptionModule [ "fonts" "fontconfig" "penultimate" ] "The corresponding package has removed from nixpkgs.")
+    (mkRemovedOptionModule [ "services" "chronos" ] "The corresponding package was removed from nixpkgs.")
     (mkRemovedOptionModule [ "services" "firefox" "syncserver" "user" ] "")
     (mkRemovedOptionModule [ "services" "firefox" "syncserver" "group" ] "")
+    (mkRemovedOptionModule [ "services" "marathon" ] "The corresponding package was removed from nixpkgs.")
+    (mkRemovedOptionModule [ "services" "mesos" ] "The corresponding package was removed from nixpkgs.")
     (mkRemovedOptionModule [ "services" "winstone" ] "The corresponding package was removed from nixpkgs.")
     (mkRemovedOptionModule [ "networking" "vpnc" ] "Use environment.etc.\"vpnc/service.conf\" instead.")
     (mkRemovedOptionModule [ "environment" "blcr" "enable" ] "The BLCR module has been removed")
@@ -28,6 +32,7 @@ with lib;
     (mkRemovedOptionModule [ "services" "osquery" ] "The osquery module has been removed")
     (mkRemovedOptionModule [ "services" "fourStore" ] "The fourStore module has been removed")
     (mkRemovedOptionModule [ "services" "fourStoreEndpoint" ] "The fourStoreEndpoint module has been removed")
+    (mkRemovedOptionModule [ "services" "mathics" ] "The Mathics module has been removed")
     (mkRemovedOptionModule [ "programs" "way-cooler" ] ("way-cooler is abandoned by its author: " +
       "https://way-cooler.org/blog/2020/01/09/way-cooler-post-mortem.html"))
     (mkRemovedOptionModule [ "services" "xserver" "multitouch" ] ''
@@ -39,10 +44,11 @@ with lib;
       The services.xserver.displayManager.auto module has been removed
       because it was only intended for use in internal NixOS tests, and gave the
       false impression of it being a special display manager when it's actually
-      LightDM. Please use the services.xserver.displayManager.lightdm.autoLogin options
+      LightDM. Please use the services.xserver.displayManager.autoLogin options
       instead, or any other display manager in NixOS as they all support auto-login.
     '')
     (mkRemovedOptionModule [ "services" "dnscrypt-proxy" ] "Use services.dnscrypt-proxy2 instead")
+    (mkRemovedOptionModule [ "services" "meguca" ] "Use meguca has been removed from nixpkgs")
     (mkRemovedOptionModule ["hardware" "brightnessctl" ] ''
       The brightnessctl module was removed because newer versions of
       brightnessctl don't require the udev rules anymore (they can use the
diff --git a/nixos/modules/security/acme.nix b/nixos/modules/security/acme.nix
index 776ef07d716c6..29635dbe86430 100644
--- a/nixos/modules/security/acme.nix
+++ b/nixos/modules/security/acme.nix
@@ -23,16 +23,16 @@ let
         type = types.nullOr types.str;
         default = null;
         description = ''
-          ACME Directory Resource URI. Defaults to let's encrypt
+          ACME Directory Resource URI. Defaults to Let's Encrypt's
           production endpoint,
-          https://acme-v02.api.letsencrypt.org/directory, if unset.
+          <link xlink:href="https://acme-v02.api.letsencrypt.org/directory"/>, if unset.
         '';
       };
 
       domain = mkOption {
         type = types.str;
         default = name;
-        description = "Domain to fetch certificate for (defaults to the entry name)";
+        description = "Domain to fetch certificate for (defaults to the entry name).";
       };
 
       email = mkOption {
@@ -103,7 +103,7 @@ let
         description = ''
           Key type to use for private keys.
           For an up to date list of supported values check the --key-type option
-          at https://go-acme.github.io/lego/usage/cli/#usage.
+          at <link xlink:href="https://go-acme.github.io/lego/usage/cli/#usage"/>.
         '';
       };
 
@@ -113,7 +113,7 @@ let
         example = "route53";
         description = ''
           DNS Challenge provider. For a list of supported providers, see the "code"
-          field of the DNS providers listed at https://go-acme.github.io/lego/dns/.
+          field of the DNS providers listed at <link xlink:href="https://go-acme.github.io/lego/dns/"/>.
         '';
       };
 
@@ -123,7 +123,7 @@ let
           Path to an EnvironmentFile for the cert's service containing any required and
           optional environment variables for your selected dnsProvider.
           To find out what values you need to set, consult the documentation at
-          https://go-acme.github.io/lego/dns/ for the corresponding dnsProvider.
+          <link xlink:href="https://go-acme.github.io/lego/dns/"/> for the corresponding dnsProvider.
         '';
         example = "/var/src/secrets/example.org-route53-api-token";
       };
@@ -150,6 +150,14 @@ let
         '';
       };
 
+      extraLegoFlags = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        description = ''
+          Additional global flags to pass to all lego commands.
+        '';
+      };
+
       extraLegoRenewFlags = mkOption {
         type = types.listOf types.str;
         default = [];
@@ -157,6 +165,14 @@ let
           Additional flags to pass to lego renew.
         '';
       };
+
+      extraLegoRunFlags = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        description = ''
+          Additional flags to pass to lego run.
+        '';
+      };
     };
   };
 
@@ -169,7 +185,7 @@ in
     (mkRemovedOptionModule [ "security" "acme" "production" ] ''
       Use security.acme.server to define your staging ACME server URL instead.
 
-      To use the let's encrypt staging server, use security.acme.server =
+      To use Let's Encrypt's staging server, use security.acme.server =
       "https://acme-staging-v02.api.letsencrypt.org/directory".
     ''
     )
@@ -207,9 +223,9 @@ in
         type = types.nullOr types.str;
         default = null;
         description = ''
-          ACME Directory Resource URI. Defaults to let's encrypt
+          ACME Directory Resource URI. Defaults to Let's Encrypt's
           production endpoint,
-          <literal>https://acme-v02.api.letsencrypt.org/directory</literal>, if unset.
+          <link xlink:href="https://acme-v02.api.letsencrypt.org/directory"/>, if unset.
         '';
       };
 
@@ -230,8 +246,8 @@ in
         type = types.bool;
         default = false;
         description = ''
-          Accept the CA's terms of service. The default provier is Let's Encrypt,
-          you can find their ToS at https://letsencrypt.org/repository/
+          Accept the CA's terms of service. The default provider is Let's Encrypt,
+          you can find their ToS at <link xlink:href="https://letsencrypt.org/repository/"/>.
         '';
       };
 
@@ -302,20 +318,27 @@ in
                 lpath = "acme/${cert}";
                 apath = "/var/lib/${lpath}";
                 spath = "/var/lib/acme/.lego/${cert}";
+                keyName = builtins.replaceStrings ["*"] ["_"] data.domain;
+                requestedDomains = pipe ([ data.domain ] ++ (attrNames data.extraDomains)) [
+                  (domains: sort builtins.lessThan domains)
+                  (domains: concatStringsSep "," domains)
+                ];
                 fileMode = if data.allowKeysForGroup then "640" else "600";
                 globalOpts = [ "-d" data.domain "--email" data.email "--path" "." "--key-type" data.keyType ]
                           ++ optionals (cfg.acceptTerms) [ "--accept-tos" ]
                           ++ optionals (data.dnsProvider != null && !data.dnsPropagationCheck) [ "--dns.disable-cp" ]
                           ++ concatLists (mapAttrsToList (name: root: [ "-d" name ]) data.extraDomains)
                           ++ (if data.dnsProvider != null then [ "--dns" data.dnsProvider ] else [ "--http" "--http.webroot" data.webroot ])
-                          ++ optionals (cfg.server != null || data.server != null) ["--server" (if data.server == null then cfg.server else data.server)];
+                          ++ optionals (cfg.server != null || data.server != null) ["--server" (if data.server == null then cfg.server else data.server)]
+                          ++ data.extraLegoFlags;
                 certOpts = optionals data.ocspMustStaple [ "--must-staple" ];
-                runOpts = escapeShellArgs (globalOpts ++ [ "run" ] ++ certOpts);
+                runOpts = escapeShellArgs (globalOpts ++ [ "run" ] ++ certOpts ++ data.extraLegoRunFlags);
                 renewOpts = escapeShellArgs (globalOpts ++
                   [ "renew" "--days" (toString cfg.validMinDays) ] ++
                   certOpts ++ data.extraLegoRenewFlags);
                 acmeService = {
                   description = "Renew ACME Certificate for ${cert}";
+                  path = with pkgs; [ openssl ];
                   after = [ "network.target" "network-online.target" ];
                   wants = [ "network-online.target" ];
                   wantedBy = mkIf (!config.boot.isContainer) [ "multi-user.target" ];
@@ -332,11 +355,18 @@ in
                     ExecStart = pkgs.writeScript "acme-start" ''
                       #!${pkgs.runtimeShell} -e
                       test -L ${spath}/accounts -o -d ${spath}/accounts || ln -s ../accounts ${spath}/accounts
-                      ${pkgs.lego}/bin/lego ${renewOpts} || ${pkgs.lego}/bin/lego ${runOpts}
+                      LEGO_ARGS=(${runOpts})
+                      if [ -e ${spath}/certificates/${keyName}.crt ]; then
+                        REQUESTED_DOMAINS="${requestedDomains}"
+                        EXISTING_DOMAINS="$(openssl x509 -in ${spath}/certificates/${keyName}.crt -noout -ext subjectAltName | tail -n1 | sed -e 's/ *DNS://g')"
+                        if [ "''${REQUESTED_DOMAINS}" == "''${EXISTING_DOMAINS}" ]; then
+                          LEGO_ARGS=(${renewOpts})
+                        fi
+                      fi
+                      ${pkgs.lego}/bin/lego ''${LEGO_ARGS[@]}
                     '';
                     ExecStartPost =
                       let
-                        keyName = builtins.replaceStrings ["*"] ["_"] data.domain;
                         script = pkgs.writeScript "acme-post-start" ''
                           #!${pkgs.runtimeShell} -e
                           cd ${apath}
diff --git a/nixos/modules/security/pam.nix b/nixos/modules/security/pam.nix
index 688344852aeb2..565c15dec24b1 100644
--- a/nixos/modules/security/pam.nix
+++ b/nixos/modules/security/pam.nix
@@ -36,6 +36,17 @@ let
         '';
       };
 
+      p11Auth = mkOption {
+        default = config.security.pam.p11.enable;
+        type = types.bool;
+        description = ''
+          If set, keys listed in
+          <filename>~/.ssh/authorized_keys</filename> and
+          <filename>~/.eid/authorized_certificates</filename>
+          can be used to log in with the associated PKCS#11 tokens.
+        '';
+      };
+
       u2fAuth = mkOption {
         default = config.security.pam.u2f.enable;
         type = types.bool;
@@ -352,6 +363,8 @@ let
               "auth sufficient ${pkgs.pam_ssh_agent_auth}/libexec/pam_ssh_agent_auth.so file=~/.ssh/authorized_keys:~/.ssh/authorized_keys2:/etc/ssh/authorized_keys.d/%u"}
           ${optionalString cfg.fprintAuth
               "auth sufficient ${pkgs.fprintd}/lib/security/pam_fprintd.so"}
+          ${let p11 = config.security.pam.p11; in optionalString cfg.p11Auth
+              "auth ${p11.control} ${pkgs.pam_p11}/lib/security/pam_p11.so ${pkgs.opensc}/lib/opensc-pkcs11.so"}
           ${let u2f = config.security.pam.u2f; in optionalString cfg.u2fAuth
               "auth ${u2f.control} ${pkgs.pam_u2f}/lib/security/pam_u2f.so ${optionalString u2f.debug "debug"} ${optionalString (u2f.authFile != null) "authfile=${u2f.authFile}"} ${optionalString u2f.interactive "interactive"} ${optionalString u2f.cue "cue"}"}
           ${optionalString cfg.usbAuth
@@ -566,6 +579,39 @@ in
 
     security.pam.enableOTPW = mkEnableOption "the OTPW (one-time password) PAM module";
 
+    security.pam.p11 = {
+      enable = mkOption {
+        default = false;
+        type = types.bool;
+        description = ''
+          Enables P11 PAM (<literal>pam_p11</literal>) module.
+
+          If set, users can log in with SSH keys and PKCS#11 tokens.
+
+          More information can be found <link
+          xlink:href="https://github.com/OpenSC/pam_p11">here</link>.
+        '';
+      };
+
+      control = mkOption {
+        default = "sufficient";
+        type = types.enum [ "required" "requisite" "sufficient" "optional" ];
+        description = ''
+          This option sets pam "control".
+          If you want to have multi factor authentication, use "required".
+          If you want to use the PKCS#11 device instead of the regular password,
+          use "sufficient".
+
+          Read
+          <citerefentry>
+            <refentrytitle>pam.conf</refentrytitle>
+            <manvolnum>5</manvolnum>
+          </citerefentry>
+          for better understanding of this option.
+        '';
+      };
+    };
+
     security.pam.u2f = {
       enable = mkOption {
         default = false;
@@ -747,6 +793,7 @@ in
       ++ optionals config.krb5.enable [pam_krb5 pam_ccreds]
       ++ optionals config.security.pam.enableOTPW [ pkgs.otpw ]
       ++ optionals config.security.pam.oath.enable [ pkgs.oathToolkit ]
+      ++ optionals config.security.pam.p11.enable [ pkgs.pam_p11 ]
       ++ optionals config.security.pam.u2f.enable [ pkgs.pam_u2f ];
 
     boot.supportedFilesystems = optionals config.security.pam.enableEcryptfs [ "ecryptfs" ];
diff --git a/nixos/modules/security/tpm2.nix b/nixos/modules/security/tpm2.nix
index 13804fb82cbaa..27f9b58c9755a 100644
--- a/nixos/modules/security/tpm2.nix
+++ b/nixos/modules/security/tpm2.nix
@@ -170,7 +170,6 @@ in {
           Restart = "always";
           RestartSec = 30;
           BusName = "com.intel.tss2.Tabrmd";
-          StandardOutput = "syslog";
           ExecStart = "${cfg.abrmd.package}/bin/tpm2-abrmd";
           User = "tss";
           Group = "nogroup";
diff --git a/nixos/modules/security/wrappers/default.nix b/nixos/modules/security/wrappers/default.nix
index a0fadb018ecaa..2def74f85353b 100644
--- a/nixos/modules/security/wrappers/default.nix
+++ b/nixos/modules/security/wrappers/default.nix
@@ -160,8 +160,11 @@ in
   config = {
 
     security.wrappers = {
+      # These are mount related wrappers that require the +s permission.
       fusermount.source = "${pkgs.fuse}/bin/fusermount";
       fusermount3.source = "${pkgs.fuse3}/bin/fusermount3";
+      mount.source = "${lib.getBin pkgs.utillinux}/bin/mount";
+      umount.source = "${lib.getBin pkgs.utillinux}/bin/umount";
     };
 
     boot.specialFileSystems.${parentWrapperDir} = {
diff --git a/nixos/modules/services/audio/icecast.nix b/nixos/modules/services/audio/icecast.nix
index 6a8a0f9975b3b..f40ea6be29d45 100644
--- a/nixos/modules/services/audio/icecast.nix
+++ b/nixos/modules/services/audio/icecast.nix
@@ -23,7 +23,7 @@ let
       <listen-socket>
         <port>${toString cfg.listen.port}</port>
         <bind-address>${cfg.listen.address}</bind-address>
-      </listen-socket>   
+      </listen-socket>
 
       <security>
         <chroot>0</chroot>
@@ -70,7 +70,7 @@ in {
         description = "Base directory used for logging.";
         default = "/var/log/icecast";
       };
-      
+
       listen = {
         port = mkOption {
           type = types.int;
diff --git a/nixos/modules/services/audio/roon-server.nix b/nixos/modules/services/audio/roon-server.nix
index 6aed485638ccf..eceb65044c5b7 100644
--- a/nixos/modules/services/audio/roon-server.nix
+++ b/nixos/modules/services/audio/roon-server.nix
@@ -45,14 +45,14 @@ in {
       environment.ROON_DATAROOT = "/var/lib/${name}";
 
       serviceConfig = {
-        ExecStart = "${pkgs.roon-server}/opt/start.sh";
+        ExecStart = "${pkgs.roon-server}/start.sh";
         LimitNOFILE = 8192;
         User = cfg.user;
         Group = cfg.group;
         StateDirectory = name;
       };
     };
-    
+
     networking.firewall = mkIf cfg.openFirewall {
       allowedTCPPortRanges = [
         { from = 9100; to = 9200; }
@@ -60,7 +60,7 @@ in {
       allowedUDPPorts = [ 9003 ];
     };
 
-    
+
     users.groups.${cfg.group} = {};
     users.users.${cfg.user} =
       if cfg.user == "roon-server" then {
diff --git a/nixos/modules/services/audio/snapserver.nix b/nixos/modules/services/audio/snapserver.nix
index b0b9264e81666..f614f0ba3e10c 100644
--- a/nixos/modules/services/audio/snapserver.nix
+++ b/nixos/modules/services/audio/snapserver.nix
@@ -31,27 +31,42 @@ let
     let
       os = val:
         optionalString (val != null) "${val}";
-      os' = prefixx: val:
-        optionalString (val != null) (prefixx + "${val}");
+      os' = prefix: val:
+        optionalString (val != null) (prefix + "${val}");
       flatten = key: value:
         "&${key}=${value}";
     in
-      "-s ${opt.type}://" + os opt.location + "?" + os' "name=" name
-        + concatStrings (mapAttrsToList flatten opt.query);
+      "--stream.stream=\"${opt.type}://" + os opt.location + "?" + os' "name=" name
+        + concatStrings (mapAttrsToList flatten opt.query) + "\"";
 
   optionalNull = val: ret:
     optional (val != null) ret;
 
   optionString = concatStringsSep " " (mapAttrsToList streamToOption cfg.streams
-             ++ ["-p ${toString cfg.port}"]
-             ++ ["--controlPort ${toString cfg.controlPort}"]
-             ++ optionalNull cfg.sampleFormat "--sampleFormat ${cfg.sampleFormat}"
-             ++ optionalNull cfg.codec "-c ${cfg.codec}"
-             ++ optionalNull cfg.streamBuffer "--streamBuffer ${cfg.streamBuffer}"
-             ++ optionalNull cfg.buffer "-b ${cfg.buffer}"
-             ++ optional cfg.sendToMuted "--sendToMuted");
+    # global options
+    ++ [ "--stream.bind_to_address ${cfg.listenAddress}" ]
+    ++ [ "--stream.port ${toString cfg.port}" ]
+    ++ optionalNull cfg.sampleFormat "--stream.sampleformat ${cfg.sampleFormat}"
+    ++ optionalNull cfg.codec "--stream.codec ${cfg.codec}"
+    ++ optionalNull cfg.streamBuffer "--stream.stream_buffer ${cfg.streamBuffer}"
+    ++ optionalNull cfg.buffer "--stream.buffer ${cfg.buffer}"
+    ++ optional cfg.sendToMuted "--stream.send_to_muted"
+    # tcp json rpc
+    ++ [ "--tcp.enabled ${toString cfg.tcp.enable}" ]
+    ++ optionals cfg.tcp.enable [
+      "--tcp.address ${cfg.tcp.listenAddress}"
+      "--tcp.port ${toString cfg.tcp.port}" ]
+     # http json rpc
+    ++ [ "--http.enabled ${toString cfg.http.enable}" ]
+    ++ optionals cfg.http.enable [
+      "--http.address ${cfg.http.listenAddress}"
+      "--http.port ${toString cfg.http.port}"
+    ] ++ optional (cfg.http.docRoot != null) "--http.doc_root \"${toString cfg.http.docRoot}\"");
 
 in {
+  imports = [
+    (mkRenamedOptionModule [ "services" "snapserver" "controlPort"] [ "services" "snapserver" "tcp" "port" ])
+  ];
 
   ###### interface
 
@@ -67,6 +82,15 @@ in {
         '';
       };
 
+      listenAddress = mkOption {
+        type = types.str;
+        default = "::";
+        example = "0.0.0.0";
+        description = ''
+          The address where snapclients can connect.
+        '';
+      };
+
       port = mkOption {
         type = types.port;
         default = 1704;
@@ -75,24 +99,100 @@ in {
         '';
       };
 
-      controlPort = mkOption {
+      openFirewall = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Whether to automatically open the specified ports in the firewall.
+        '';
+      };
+
+      inherit sampleFormat;
+      inherit codec;
+
+      streamBuffer = mkOption {
+        type = with types; nullOr int;
+        default = null;
+        description = ''
+          Stream read (input) buffer in ms.
+        '';
+        example = 20;
+      };
+
+      buffer = mkOption {
+        type = with types; nullOr int;
+        default = null;
+        description = ''
+          Network buffer in ms.
+        '';
+        example = 1000;
+      };
+
+      sendToMuted = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Send audio to muted clients.
+        '';
+      };
+
+      tcp.enable = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Whether to enable the JSON-RPC via TCP.
+        '';
+      };
+
+      tcp.listenAddress = mkOption {
+        type = types.str;
+        default = "::";
+        example = "0.0.0.0";
+        description = ''
+          The address where the TCP JSON-RPC listens on.
+        '';
+      };
+
+      tcp.port = mkOption {
         type = types.port;
         default = 1705;
         description = ''
-          The port for control connections (JSON-RPC).
+          The port where the TCP JSON-RPC listens on.
         '';
       };
 
-      openFirewall = mkOption {
+      http.enable = mkOption {
         type = types.bool;
         default = true;
         description = ''
-          Whether to automatically open the specified ports in the firewall.
+          Whether to enable the JSON-RPC via HTTP.
         '';
       };
 
-      inherit sampleFormat;
-      inherit codec;
+      http.listenAddress = mkOption {
+        type = types.str;
+        default = "::";
+        example = "0.0.0.0";
+        description = ''
+          The address where the HTTP JSON-RPC listens on.
+        '';
+      };
+
+      http.port = mkOption {
+        type = types.port;
+        default = 1780;
+        description = ''
+          The port where the HTTP JSON-RPC listens on.
+        '';
+      };
+
+      http.docRoot = mkOption {
+        type = with types; nullOr path;
+        default = null;
+        description = ''
+          Path to serve from the HTTP servers root.
+        '';
+      };
 
       streams = mkOption {
         type = with types; attrsOf (submodule {
@@ -147,34 +247,7 @@ in {
           };
         '';
       };
-
-      streamBuffer = mkOption {
-        type = with types; nullOr int;
-        default = null;
-        description = ''
-          Stream read (input) buffer in ms.
-        '';
-        example = 20;
-      };
-
-      buffer = mkOption {
-        type = with types; nullOr int;
-        default = null;
-        description = ''
-          Network buffer in ms.
-        '';
-        example = 1000;
-      };
-
-      sendToMuted = mkOption {
-        type = types.bool;
-        default = false;
-        description = ''
-          Send audio to muted clients.
-        '';
-      };
     };
-
   };
 
 
@@ -206,7 +279,10 @@ in {
       };
     };
 
-    networking.firewall.allowedTCPPorts = optionals cfg.openFirewall [ cfg.port cfg.controlPort ];
+    networking.firewall.allowedTCPPorts =
+      optionals cfg.openFirewall [ cfg.port ]
+      ++ optional cfg.tcp.enable cfg.tcp.port
+      ++ optional cfg.http.enable cfg.http.port;
   };
 
   meta = {
diff --git a/nixos/modules/services/audio/spotifyd.nix b/nixos/modules/services/audio/spotifyd.nix
index 4b74e75327955..a589153248fe9 100644
--- a/nixos/modules/services/audio/spotifyd.nix
+++ b/nixos/modules/services/audio/spotifyd.nix
@@ -16,7 +16,7 @@ in
         type = types.lines;
         description = ''
           Configuration for Spotifyd. For syntax and directives, see
-          https://github.com/Spotifyd/spotifyd#Configuration.
+          <link xlink:href="https://github.com/Spotifyd/spotifyd#Configuration"/>.
         '';
       };
     };
diff --git a/nixos/modules/services/backup/bacula.nix b/nixos/modules/services/backup/bacula.nix
index cef304734aeee..3d69a69038a3c 100644
--- a/nixos/modules/services/backup/bacula.nix
+++ b/nixos/modules/services/backup/bacula.nix
@@ -18,7 +18,7 @@ let
         Pid Directory = "/run";
         ${fd_cfg.extraClientConfig}
       }
-     
+
       ${concatStringsSep "\n" (mapAttrsToList (name: value: ''
       Director {
         Name = "${name}";
@@ -26,7 +26,7 @@ let
         Monitor = "${value.monitor}";
       }
       '') fd_cfg.director)}
-     
+
       Messages {
         Name = Standard;
         syslog = all, !skipped, !restored
@@ -35,7 +35,7 @@ let
     '';
 
   sd_cfg = config.services.bacula-sd;
-  sd_conf = pkgs.writeText "bacula-sd.conf" 
+  sd_conf = pkgs.writeText "bacula-sd.conf"
     ''
       Storage {
         Name = "${sd_cfg.name}";
@@ -80,7 +80,7 @@ let
     '';
 
   dir_cfg = config.services.bacula-dir;
-  dir_conf = pkgs.writeText "bacula-dir.conf" 
+  dir_conf = pkgs.writeText "bacula-dir.conf"
     ''
     Director {
       Name = "${dir_cfg.name}";
@@ -125,10 +125,10 @@ let
 
           The password is plain text. It is not generated through any special
           process but as noted above, it is better to use random text for
-          security reasons. 
+          security reasons.
         '';
       };
-      
+
       monitor = mkOption {
         default = "no";
         example = "yes";
@@ -140,7 +140,7 @@ let
 
           Please note that if this director is being used by a Monitor, we
           highly recommend to set this directive to yes to avoid serious
-          security problems. 
+          security problems.
         '';
       };
     };
@@ -163,7 +163,7 @@ let
           type of autochanger, what you specify here can vary. This directive
           is optional. See the Using AutochangersAutochangersChapter chapter of
           this manual for more details of using this and the following
-          autochanger directives.         
+          autochanger directives.
           '';
       };
 
@@ -200,7 +200,7 @@ let
           Extra configuration to be passed in Autochanger directive.
         '';
         example = ''
-   
+
         '';
       };
     };
@@ -222,7 +222,7 @@ let
           if you are archiving to disk storage. In this case, you must supply
           the full absolute path to the directory. When specifying a tape
           device, it is preferable that the "non-rewind" variant of the device
-          file name be given. 
+          file name be given.
         '';
       };
 
@@ -290,7 +290,7 @@ in {
           Whether to enable the Bacula File Daemon.
         '';
       };
- 
+
       name = mkOption {
         default = "${config.networking.hostName}-fd";
         description = ''
@@ -300,7 +300,7 @@ in {
           Clients. This directive is required.
         '';
       };
- 
+
       port = mkOption {
         default = 9102;
         type = types.int;
@@ -310,7 +310,7 @@ in {
           the Client resource of the Director's configuration file.
         '';
       };
- 
+
       director = mkOption {
         default = {};
         description = ''
@@ -349,14 +349,14 @@ in {
           Whether to enable Bacula Storage Daemon.
         '';
       };
- 
+
       name = mkOption {
         default = "${config.networking.hostName}-sd";
         description = ''
           Specifies the Name of the Storage daemon.
         '';
       };
- 
+
       port = mkOption {
         default = 9103;
         type = types.int;
@@ -410,7 +410,7 @@ in {
           console = all
         '';
       };
- 
+
     };
 
     services.bacula-dir = {
@@ -429,7 +429,7 @@ in {
           required.
         '';
       };
- 
+
       port = mkOption {
         default = 9101;
         type = types.int;
@@ -442,7 +442,7 @@ in {
           specify DirAddresses (N.B plural) directive.
         '';
       };
- 
+
       password = mkOption {
         # TODO: required?
         description = ''
diff --git a/nixos/modules/services/backup/restic.nix b/nixos/modules/services/backup/restic.nix
index 2388f1d6ca137..d869835bf07e6 100644
--- a/nixos/modules/services/backup/restic.nix
+++ b/nixos/modules/services/backup/restic.nix
@@ -31,6 +31,59 @@ in
           '';
         };
 
+        rcloneOptions = mkOption {
+          type = with types; nullOr (attrsOf (oneOf [ str bool ]));
+          default = null;
+          description = ''
+            Options to pass to rclone to control its behavior.
+            See <link xlink:href="https://rclone.org/docs/#options"/> for
+            available options. When specifying option names, strip the
+            leading <literal>--</literal>. To set a flag such as
+            <literal>--drive-use-trash</literal>, which does not take a value,
+            set the value to the Boolean <literal>true</literal>.
+          '';
+          example = {
+            bwlimit = "10M";
+            drive-use-trash = "true";
+          };
+        };
+
+        rcloneConfig = mkOption {
+          type = with types; nullOr (attrsOf (oneOf [ str bool ]));
+          default = null;
+          description = ''
+            Configuration for the rclone remote being used for backup.
+            See the remote's specific options under rclone's docs at
+            <link xlink:href="https://rclone.org/docs/"/>. When specifying
+            option names, use the "config" name specified in the docs.
+            For example, to set <literal>--b2-hard-delete</literal> for a B2
+            remote, use <literal>hard_delete = true</literal> in the
+            attribute set.
+            Warning: Secrets set in here will be world-readable in the Nix
+            store! Consider using the <literal>rcloneConfigFile</literal>
+            option instead to specify secret values separately. Note that
+            options set here will override those set in the config file.
+          '';
+          example = {
+            type = "b2";
+            account = "xxx";
+            key = "xxx";
+            hard_delete = true;
+          };
+        };
+
+        rcloneConfigFile = mkOption {
+          type = with types; nullOr path;
+          default = null;
+          description = ''
+            Path to the file containing rclone configuration. This file
+            must contain configuration for the remote specified in this backup
+            set and also must be readable by root. Options set in
+            <literal>rcloneConfig</literal> will override those set in this
+            file.
+          '';
+        };
+
         repository = mkOption {
           type = types.str;
           description = ''
@@ -170,11 +223,22 @@ in
             ( resticCmd + " forget --prune " + (concatStringsSep " " backup.pruneOpts) )
             ( resticCmd + " check" )
           ];
+          # Helper functions for rclone remotes
+          rcloneRemoteName = builtins.elemAt (splitString ":" backup.repository) 1;
+          rcloneAttrToOpt = v: "RCLONE_" + toUpper (builtins.replaceStrings [ "-" ] [ "_" ] v);
+          rcloneAttrToConf = v: "RCLONE_CONFIG_" + toUpper (rcloneRemoteName + "_" + v);
+          toRcloneVal = v: if lib.isBool v then lib.boolToString v else v;
         in nameValuePair "restic-backups-${name}" ({
           environment = {
             RESTIC_PASSWORD_FILE = backup.passwordFile;
             RESTIC_REPOSITORY = backup.repository;
-          };
+          } // optionalAttrs (backup.rcloneOptions != null) (mapAttrs' (name: value:
+            nameValuePair (rcloneAttrToOpt name) (toRcloneVal value)
+          ) backup.rcloneOptions) // optionalAttrs (backup.rcloneConfigFile != null) {
+            RCLONE_CONFIG = backup.rcloneConfigFile;
+          } // optionalAttrs (backup.rcloneConfig != null) (mapAttrs' (name: value:
+            nameValuePair (rcloneAttrToConf name) (toRcloneVal value)
+          ) backup.rcloneConfig);
           path = [ pkgs.openssh ];
           restartIfChanged = false;
           serviceConfig = {
diff --git a/nixos/modules/services/backup/zfs-replication.nix b/nixos/modules/services/backup/zfs-replication.nix
index 5a64304275d54..6d75774c78f91 100644
--- a/nixos/modules/services/backup/zfs-replication.nix
+++ b/nixos/modules/services/backup/zfs-replication.nix
@@ -18,7 +18,7 @@ in {
       };
 
       host = mkOption {
-        description = "Remote host where snapshots should be sent.";
+        description = "Remote host where snapshots should be sent. <literal>lz4</literal> is expected to be installed on this host.";
         example = "example.com";
         type = types.str;
       };
diff --git a/nixos/modules/services/computing/slurm/slurm.nix b/nixos/modules/services/computing/slurm/slurm.nix
index 050872e933fb1..705390a21d4ee 100644
--- a/nixos/modules/services/computing/slurm/slurm.nix
+++ b/nixos/modules/services/computing/slurm/slurm.nix
@@ -67,7 +67,7 @@ in
           type = types.bool;
           default = false;
           description = ''
-            Wether to enable the slurm control daemon.
+            Whether to enable the slurm control daemon.
             Note that the standard authentication method is "munge".
             The "munge" service needs to be provided with a password file in order for
             slurm to work properly (see <literal>services.munge.password</literal>).
@@ -135,7 +135,7 @@ in
         type = types.bool;
         default = false;
         description = ''
-          Wether to provide a slurm.conf file.
+          Whether to provide a slurm.conf file.
           Enable this option if you do not run a slurm daemon on this host
           (i.e. <literal>server.enable</literal> and <literal>client.enable</literal> are <literal>false</literal>)
           but you still want to run slurm commands from this host.
diff --git a/nixos/modules/services/computing/torque/mom.nix b/nixos/modules/services/computing/torque/mom.nix
index 83772539a7abc..0c5f43cf3e6a2 100644
--- a/nixos/modules/services/computing/torque/mom.nix
+++ b/nixos/modules/services/computing/torque/mom.nix
@@ -60,4 +60,4 @@ in
     };
 
   };
-}      
+}
diff --git a/nixos/modules/services/computing/torque/server.nix b/nixos/modules/services/computing/torque/server.nix
index 655d1500497e6..21c5a4f46724d 100644
--- a/nixos/modules/services/computing/torque/server.nix
+++ b/nixos/modules/services/computing/torque/server.nix
@@ -93,4 +93,4 @@ in
     };
 
   };
-}      
+}
diff --git a/nixos/modules/services/continuous-integration/buildbot/worker.nix b/nixos/modules/services/continuous-integration/buildbot/worker.nix
index 52f24b8cee3c3..7b8a35f54bfa3 100644
--- a/nixos/modules/services/continuous-integration/buildbot/worker.nix
+++ b/nixos/modules/services/continuous-integration/buildbot/worker.nix
@@ -29,7 +29,7 @@ let
 
     with open('${cfg.workerPassFile}', 'r', encoding='utf-8') as passwd_file:
         passwd = passwd_file.read().strip('\r\n')
-    keepalive = 600
+    keepalive = ${toString cfg.keepalive}
     umask = None
     maxdelay = 300
     numcpus = None
@@ -116,6 +116,15 @@ in {
         description = "Specifies the Buildbot Worker connection string.";
       };
 
+      keepalive = mkOption {
+        default = 600;
+        type = types.int;
+        description = "
+          This is a number that indicates how frequently keepalive messages should be sent
+          from the worker to the buildmaster, expressed in seconds.
+        ";
+      };
+
       package = mkOption {
         type = types.package;
         default = pkgs.python3Packages.buildbot-worker;
diff --git a/nixos/modules/services/continuous-integration/gitlab-runner.nix b/nixos/modules/services/continuous-integration/gitlab-runner.nix
index eacfed85ddffc..431555309cc9c 100644
--- a/nixos/modules/services/continuous-integration/gitlab-runner.nix
+++ b/nixos/modules/services/continuous-integration/gitlab-runner.nix
@@ -1,14 +1,16 @@
 { config, lib, pkgs, ... }:
+with builtins;
 with lib;
 let
   cfg = config.services.gitlab-runner;
   hasDocker = config.virtualisation.docker.enable;
-  hashedServices = with builtins; (mapAttrs' (name: service: nameValuePair
-    "${name}_${config.networking.hostName}_${
+  hashedServices = mapAttrs'
+    (name: service: nameValuePair
+      "${name}_${config.networking.hostName}_${
         substring 0 12
         (hashString "md5" (unsafeDiscardStringContext (toJSON service)))}"
       service)
-    cfg.services);
+    cfg.services;
   configPath = "$HOME/.gitlab-runner/config.toml";
   configureScript = pkgs.writeShellScriptBin "gitlab-runner-configure" (
     if (cfg.configFile != null) then ''
@@ -47,6 +49,8 @@ let
           ] ++ service.registrationFlags
             ++ optional (service.buildsDir != null)
             "--builds-dir ${service.buildsDir}"
+            ++ optional (service.cloneUrl != null)
+            "--clone-url ${service.cloneUrl}"
             ++ optional (service.preCloneScript != null)
             "--pre-clone-script ${service.preCloneScript}"
             ++ optional (service.preBuildScript != null)
@@ -76,7 +80,7 @@ let
               ++ map (v: "--docker-allowed-images ${escapeShellArg v}") service.dockerAllowedImages
               ++ map (v: "--docker-allowed-services ${escapeShellArg v}") service.dockerAllowedServices
             )
-          ))} && sleep 1
+          ))} && sleep 1 || exit 1
         fi
       '') hashedServices)}
 
@@ -89,8 +93,17 @@ let
 
       # update global options
       remarshal --if toml --of json ${configPath} \
-        | jq -cM '.check_interval = ${toString cfg.checkInterval} |
-                  .concurrent = ${toString cfg.concurrent}' \
+        | jq -cM ${escapeShellArg (concatStringsSep " | " [
+            ".check_interval = ${toJSON cfg.checkInterval}"
+            ".concurrent = ${toJSON cfg.concurrent}"
+            ".sentry_dsn = ${toJSON cfg.sentryDSN}"
+            ".listen_address = ${toJSON cfg.prometheusListenAddress}"
+            ".session_server.listen_address = ${toJSON cfg.sessionServer.listenAddress}"
+            ".session_server.advertise_address = ${toJSON cfg.sessionServer.advertiseAddress}"
+            ".session_server.session_timeout = ${toJSON cfg.sessionServer.sessionTimeout}"
+            "del(.[] | nulls)"
+            "del(.session_server[] | nulls)"
+          ])} \
         | remarshal --if json --of toml \
         | sponge ${configPath}
 
@@ -141,6 +154,66 @@ in
         0 does not mean unlimited.
       '';
     };
+    sentryDSN = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      example = "https://public:private@host:port/1";
+      description = ''
+        Data Source Name for tracking of all system level errors to Sentry.
+      '';
+    };
+    prometheusListenAddress = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      example = "localhost:8080";
+      description = ''
+        Address (&lt;host&gt;:&lt;port&gt;) on which the Prometheus metrics HTTP server
+        should be listening.
+      '';
+    };
+    sessionServer = mkOption {
+      type = types.submodule {
+        options = {
+          listenAddress = mkOption {
+            type = types.nullOr types.str;
+            default = null;
+            example = "0.0.0.0:8093";
+            description = ''
+              An internal URL to be used for the session server.
+            '';
+          };
+          advertiseAddress = mkOption {
+            type = types.nullOr types.str;
+            default = null;
+            example = "runner-host-name.tld:8093";
+            description = ''
+              The URL that the Runner will expose to GitLab to be used
+              to access the session server.
+              Fallbacks to <option>listenAddress</option> if not defined.
+            '';
+          };
+          sessionTimeout = mkOption {
+            type = types.int;
+            default = 1800;
+            description = ''
+              How long in seconds the session can stay active after
+              the job completes (which will block the job from finishing).
+            '';
+          };
+        };
+      };
+      default = { };
+      example = literalExample ''
+        {
+          listenAddress = "0.0.0.0:8093";
+        }
+      '';
+      description = ''
+        The session server allows the user to interact with jobs
+        that the Runner is responsible for. A good example of this is the
+        <link xlink:href="https://docs.gitlab.com/ee/ci/interactive_web_terminal/index.html">interactive web terminal</link>.
+      '';
+    };
     gracefulTermination = mkOption {
       type = types.bool;
       default = false;
@@ -306,6 +379,14 @@ in
               in context of selected executor (Locally, Docker, SSH).
             '';
           };
+          cloneUrl = mkOption {
+            type = types.nullOr types.str;
+            default = null;
+            example = "http://gitlab.example.local";
+            description = ''
+              Overwrite the URL for the GitLab instance. Used if the Runner can’t connect to GitLab on the URL GitLab exposes itself.
+            '';
+          };
           dockerImage = mkOption {
             type = types.nullOr types.str;
             default = null;
diff --git a/nixos/modules/services/databases/mysql.nix b/nixos/modules/services/databases/mysql.nix
index 2e8c5b7640b2c..7d0a3f9afc48c 100644
--- a/nixos/modules/services/databases/mysql.nix
+++ b/nixos/modules/services/databases/mysql.nix
@@ -6,12 +6,10 @@ let
 
   cfg = config.services.mysql;
 
-  mysql = cfg.package;
-
-  isMariaDB = lib.getName mysql == lib.getName pkgs.mariadb;
+  isMariaDB = lib.getName cfg.package == lib.getName pkgs.mariadb;
 
   mysqldOptions =
-    "--user=${cfg.user} --datadir=${cfg.dataDir} --basedir=${mysql}";
+    "--user=${cfg.user} --datadir=${cfg.dataDir} --basedir=${cfg.package}";
 
   settingsFile = pkgs.writeText "my.cnf" (
     generators.toINI { listsAsDuplicateKeys = true; } cfg.settings +
@@ -22,7 +20,7 @@ in
 
 {
   imports = [
-    (mkRemovedOptionModule [ "services" "mysql" "pidDir" ] "Don't wait for pidfiles, describe dependencies through systemd")
+    (mkRemovedOptionModule [ "services" "mysql" "pidDir" ] "Don't wait for pidfiles, describe dependencies through systemd.")
     (mkRemovedOptionModule [ "services" "mysql" "rootPassword" ] "Use socket authentication or set the password outside of the nix store.")
   ];
 
@@ -46,25 +44,31 @@ in
         type = types.nullOr types.str;
         default = null;
         example = literalExample "0.0.0.0";
-        description = "Address to bind to. The default is to bind to all addresses";
+        description = "Address to bind to. The default is to bind to all addresses.";
       };
 
       port = mkOption {
         type = types.int;
         default = 3306;
-        description = "Port of MySQL";
+        description = "Port of MySQL.";
       };
 
       user = mkOption {
         type = types.str;
         default = "mysql";
-        description = "User account under which MySQL runs";
+        description = "User account under which MySQL runs.";
+      };
+
+      group = mkOption {
+        type = types.str;
+        default = "mysql";
+        description = "Group under which MySQL runs.";
       };
 
       dataDir = mkOption {
         type = types.path;
         example = "/var/lib/mysql";
-        description = "Location where MySQL stores its table files";
+        description = "Location where MySQL stores its table files.";
       };
 
       configFile = mkOption {
@@ -171,7 +175,7 @@ in
       initialScript = mkOption {
         type = types.nullOr types.path;
         default = null;
-        description = "A file containing SQL statements to be executed on the first startup. Can be used for granting certain permissions on the database";
+        description = "A file containing SQL statements to be executed on the first startup. Can be used for granting certain permissions on the database.";
       };
 
       ensureDatabases = mkOption {
@@ -259,33 +263,33 @@ in
         serverId = mkOption {
           type = types.int;
           default = 1;
-          description = "Id of the MySQL server instance. This number must be unique for each instance";
+          description = "Id of the MySQL server instance. This number must be unique for each instance.";
         };
 
         masterHost = mkOption {
           type = types.str;
-          description = "Hostname of the MySQL master server";
+          description = "Hostname of the MySQL master server.";
         };
 
         slaveHost = mkOption {
           type = types.str;
-          description = "Hostname of the MySQL slave server";
+          description = "Hostname of the MySQL slave server.";
         };
 
         masterUser = mkOption {
           type = types.str;
-          description = "Username of the MySQL replication user";
+          description = "Username of the MySQL replication user.";
         };
 
         masterPassword = mkOption {
           type = types.str;
-          description = "Password of the MySQL replication user";
+          description = "Password of the MySQL replication user.";
         };
 
         masterPort = mkOption {
           type = types.int;
           default = 3306;
-          description = "Port number on which the MySQL master server runs";
+          description = "Port number on which the MySQL master server runs.";
         };
       };
     };
@@ -317,29 +321,33 @@ in
         binlog-ignore-db = [ "information_schema" "performance_schema" "mysql" ];
       })
       (mkIf (!isMariaDB) {
-        plugin-load-add = optional (cfg.ensureUsers != []) "auth_socket.so";
+        plugin-load-add = "auth_socket.so";
       })
     ];
 
-    users.users.mysql = {
-      description = "MySQL server user";
-      group = "mysql";
-      uid = config.ids.uids.mysql;
+    users.users = optionalAttrs (cfg.user == "mysql") {
+      mysql = {
+        description = "MySQL server user";
+        group = cfg.group;
+        uid = config.ids.uids.mysql;
+      };
     };
 
-    users.groups.mysql.gid = config.ids.gids.mysql;
+    users.groups = optionalAttrs (cfg.group == "mysql") {
+      mysql.gid = config.ids.gids.mysql;
+    };
 
-    environment.systemPackages = [mysql];
+    environment.systemPackages = [ cfg.package ];
 
     environment.etc."my.cnf".source = cfg.configFile;
 
     systemd.tmpfiles.rules = [
-      "d '${cfg.dataDir}' 0700 ${cfg.user} mysql - -"
-      "z '${cfg.dataDir}' 0700 ${cfg.user} mysql - -"
+      "d '${cfg.dataDir}' 0700 '${cfg.user}' '${cfg.group}' - -"
+      "z '${cfg.dataDir}' 0700 '${cfg.user}' '${cfg.group}' - -"
     ];
 
     systemd.services.mysql = let
-      hasNotify = (cfg.package == pkgs.mariadb);
+      hasNotify = isMariaDB;
     in {
         description = "MySQL Server";
 
@@ -357,125 +365,127 @@ in
 
         preStart = if isMariaDB then ''
           if ! test -e ${cfg.dataDir}/mysql; then
-            ${mysql}/bin/mysql_install_db --defaults-file=/etc/my.cnf ${mysqldOptions}
+            ${cfg.package}/bin/mysql_install_db --defaults-file=/etc/my.cnf ${mysqldOptions}
             touch ${cfg.dataDir}/mysql_init
           fi
         '' else ''
           if ! test -e ${cfg.dataDir}/mysql; then
-            ${mysql}/bin/mysqld --defaults-file=/etc/my.cnf ${mysqldOptions} --initialize-insecure
+            ${cfg.package}/bin/mysqld --defaults-file=/etc/my.cnf ${mysqldOptions} --initialize-insecure
             touch ${cfg.dataDir}/mysql_init
           fi
         '';
 
-        serviceConfig = {
-          Type = if hasNotify then "notify" else "simple";
-          Restart = "on-abort";
-          RestartSec = "5s";
-          # The last two environment variables are used for starting Galera clusters
-          ExecStart = "${mysql}/bin/mysqld --defaults-file=/etc/my.cnf ${mysqldOptions} $_WSREP_NEW_CLUSTER $_WSREP_START_POSITION";
-          ExecStartPost =
-            let
-              setupScript = pkgs.writeScript "mysql-setup" ''
-                #!${pkgs.runtimeShell} -e
-
-                ${optionalString (!hasNotify) ''
-                  # Wait until the MySQL server is available for use
-                  count=0
-                  while [ ! -e /run/mysqld/mysqld.sock ]
-                  do
-                      if [ $count -eq 30 ]
-                      then
-                          echo "Tried 30 times, giving up..."
-                          exit 1
-                      fi
-
-                      echo "MySQL daemon not yet started. Waiting for 1 second..."
-                      count=$((count++))
-                      sleep 1
-                  done
-                ''}
-
-                if [ -f ${cfg.dataDir}/mysql_init ]
+        postStart = let
+          # The super user account to use on *first* run of MySQL server
+          superUser = if isMariaDB then cfg.user else "root";
+        in ''
+          ${optionalString (!hasNotify) ''
+            # Wait until the MySQL server is available for use
+            count=0
+            while [ ! -e /run/mysqld/mysqld.sock ]
+            do
+                if [ $count -eq 30 ]
                 then
-                    ${concatMapStrings (database: ''
-                      # Create initial databases
-                      if ! test -e "${cfg.dataDir}/${database.name}"; then
-                          echo "Creating initial database: ${database.name}"
-                          ( echo 'create database `${database.name}`;'
-
-                            ${optionalString (database.schema != null) ''
-                            echo 'use `${database.name}`;'
-
-                            # TODO: this silently falls through if database.schema does not exist,
-                            # we should catch this somehow and exit, but can't do it here because we're in a subshell.
-                            if [ -f "${database.schema}" ]
-                            then
-                                cat ${database.schema}
-                            elif [ -d "${database.schema}" ]
-                            then
-                                cat ${database.schema}/mysql-databases/*.sql
-                            fi
-                            ''}
-                          ) | ${mysql}/bin/mysql -u root -N
-                      fi
-                    '') cfg.initialDatabases}
-
-                    ${optionalString (cfg.replication.role == "master")
-                      ''
-                        # Set up the replication master
+                    echo "Tried 30 times, giving up..."
+                    exit 1
+                fi
 
-                        ( echo "use mysql;"
-                          echo "CREATE USER '${cfg.replication.masterUser}'@'${cfg.replication.slaveHost}' IDENTIFIED WITH mysql_native_password;"
-                          echo "SET PASSWORD FOR '${cfg.replication.masterUser}'@'${cfg.replication.slaveHost}' = PASSWORD('${cfg.replication.masterPassword}');"
-                          echo "GRANT REPLICATION SLAVE ON *.* TO '${cfg.replication.masterUser}'@'${cfg.replication.slaveHost}';"
-                        ) | ${mysql}/bin/mysql -u root -N
+                echo "MySQL daemon not yet started. Waiting for 1 second..."
+                count=$((count++))
+                sleep 1
+            done
+          ''}
+
+          if [ -f ${cfg.dataDir}/mysql_init ]
+          then
+              # While MariaDB comes with a 'mysql' super user account since 10.4.x, MySQL does not
+              # Since we don't want to run this service as 'root' we need to ensure the account exists on first run
+              ( echo "CREATE USER IF NOT EXISTS '${cfg.user}'@'localhost' IDENTIFIED WITH ${if isMariaDB then "unix_socket" else "auth_socket"};"
+                echo "GRANT ALL PRIVILEGES ON *.* TO '${cfg.user}'@'localhost' WITH GRANT OPTION;"
+              ) | ${cfg.package}/bin/mysql -u ${superUser} -N
+
+              ${concatMapStrings (database: ''
+                # Create initial databases
+                if ! test -e "${cfg.dataDir}/${database.name}"; then
+                    echo "Creating initial database: ${database.name}"
+                    ( echo 'create database `${database.name}`;'
+
+                      ${optionalString (database.schema != null) ''
+                      echo 'use `${database.name}`;'
+
+                      # TODO: this silently falls through if database.schema does not exist,
+                      # we should catch this somehow and exit, but can't do it here because we're in a subshell.
+                      if [ -f "${database.schema}" ]
+                      then
+                          cat ${database.schema}
+                      elif [ -d "${database.schema}" ]
+                      then
+                          cat ${database.schema}/mysql-databases/*.sql
+                      fi
                       ''}
+                    ) | ${cfg.package}/bin/mysql -u ${superUser} -N
+                fi
+              '') cfg.initialDatabases}
 
-                    ${optionalString (cfg.replication.role == "slave")
-                      ''
-                        # Set up the replication slave
+              ${optionalString (cfg.replication.role == "master")
+                ''
+                  # Set up the replication master
 
-                        ( echo "stop slave;"
-                          echo "change master to master_host='${cfg.replication.masterHost}', master_user='${cfg.replication.masterUser}', master_password='${cfg.replication.masterPassword}';"
-                          echo "start slave;"
-                        ) | ${mysql}/bin/mysql -u root -N
-                      ''}
+                  ( echo "use mysql;"
+                    echo "CREATE USER '${cfg.replication.masterUser}'@'${cfg.replication.slaveHost}' IDENTIFIED WITH mysql_native_password;"
+                    echo "SET PASSWORD FOR '${cfg.replication.masterUser}'@'${cfg.replication.slaveHost}' = PASSWORD('${cfg.replication.masterPassword}');"
+                    echo "GRANT REPLICATION SLAVE ON *.* TO '${cfg.replication.masterUser}'@'${cfg.replication.slaveHost}';"
+                  ) | ${cfg.package}/bin/mysql -u ${superUser} -N
+                ''}
 
-                    ${optionalString (cfg.initialScript != null)
-                      ''
-                        # Execute initial script
-                        # using toString to avoid copying the file to nix store if given as path instead of string,
-                        # as it might contain credentials
-                        cat ${toString cfg.initialScript} | ${mysql}/bin/mysql -u root -N
-                      ''}
+              ${optionalString (cfg.replication.role == "slave")
+                ''
+                  # Set up the replication slave
 
-                    rm ${cfg.dataDir}/mysql_init
-                fi
+                  ( echo "stop slave;"
+                    echo "change master to master_host='${cfg.replication.masterHost}', master_user='${cfg.replication.masterUser}', master_password='${cfg.replication.masterPassword}';"
+                    echo "start slave;"
+                  ) | ${cfg.package}/bin/mysql -u ${superUser} -N
+                ''}
 
-                ${optionalString (cfg.ensureDatabases != []) ''
-                  (
-                  ${concatMapStrings (database: ''
-                    echo "CREATE DATABASE IF NOT EXISTS \`${database}\`;"
-                  '') cfg.ensureDatabases}
-                  ) | ${mysql}/bin/mysql -u root -N
+              ${optionalString (cfg.initialScript != null)
+                ''
+                  # Execute initial script
+                  # using toString to avoid copying the file to nix store if given as path instead of string,
+                  # as it might contain credentials
+                  cat ${toString cfg.initialScript} | ${cfg.package}/bin/mysql -u ${superUser} -N
                 ''}
 
-                ${concatMapStrings (user:
-                  ''
-                    ( echo "CREATE USER IF NOT EXISTS '${user.name}'@'localhost' IDENTIFIED WITH ${if isMariaDB then "unix_socket" else "auth_socket"};"
-                      ${concatStringsSep "\n" (mapAttrsToList (database: permission: ''
-                        echo "GRANT ${permission} ON ${database} TO '${user.name}'@'localhost';"
-                      '') user.ensurePermissions)}
-                    ) | ${mysql}/bin/mysql -u root -N
-                  '') cfg.ensureUsers}
-              '';
-            in
-              # ensureDatbases & ensureUsers depends on this script being run as root
-              # when the user has secured their mysql install
-              "+${setupScript}";
+              rm ${cfg.dataDir}/mysql_init
+          fi
+
+          ${optionalString (cfg.ensureDatabases != []) ''
+            (
+            ${concatMapStrings (database: ''
+              echo "CREATE DATABASE IF NOT EXISTS \`${database}\`;"
+            '') cfg.ensureDatabases}
+            ) | ${cfg.package}/bin/mysql -N
+          ''}
+
+          ${concatMapStrings (user:
+            ''
+              ( echo "CREATE USER IF NOT EXISTS '${user.name}'@'localhost' IDENTIFIED WITH ${if isMariaDB then "unix_socket" else "auth_socket"};"
+                ${concatStringsSep "\n" (mapAttrsToList (database: permission: ''
+                  echo "GRANT ${permission} ON ${database} TO '${user.name}'@'localhost';"
+                '') user.ensurePermissions)}
+              ) | ${cfg.package}/bin/mysql -N
+            '') cfg.ensureUsers}
+        '';
+
+        serviceConfig = {
+          Type = if hasNotify then "notify" else "simple";
+          Restart = "on-abort";
+          RestartSec = "5s";
+          # The last two environment variables are used for starting Galera clusters
+          ExecStart = "${cfg.package}/bin/mysqld --defaults-file=/etc/my.cnf ${mysqldOptions} $_WSREP_NEW_CLUSTER $_WSREP_START_POSITION";
           # User and group
           User = cfg.user;
-          Group = "mysql";
+          Group = cfg.group;
           # Runtime directory and mode
           RuntimeDirectory = "mysqld";
           RuntimeDirectoryMode = "0755";
diff --git a/nixos/modules/services/databases/openldap.nix b/nixos/modules/services/databases/openldap.nix
index 9b4d9a98b745e..7472538b887e3 100644
--- a/nixos/modules/services/databases/openldap.nix
+++ b/nixos/modules/services/databases/openldap.nix
@@ -5,14 +5,14 @@ with lib;
 let
 
   cfg = config.services.openldap;
-  openldap = pkgs.openldap;
+  openldap = cfg.package;
 
   dataFile = pkgs.writeText "ldap-contents.ldif" cfg.declarativeContents;
   configFile = pkgs.writeText "slapd.conf" ((optionalString cfg.defaultSchemas ''
-    include ${pkgs.openldap.out}/etc/schema/core.schema
-    include ${pkgs.openldap.out}/etc/schema/cosine.schema
-    include ${pkgs.openldap.out}/etc/schema/inetorgperson.schema
-    include ${pkgs.openldap.out}/etc/schema/nis.schema
+    include ${openldap.out}/etc/schema/core.schema
+    include ${openldap.out}/etc/schema/cosine.schema
+    include ${openldap.out}/etc/schema/inetorgperson.schema
+    include ${openldap.out}/etc/schema/nis.schema
   '') + ''
     ${cfg.extraConfig}
     database ${cfg.database}
@@ -46,6 +46,18 @@ in
         ";
       };
 
+      package = mkOption {
+        type = types.package;
+        default = pkgs.openldap;
+        description = ''
+          OpenLDAP package to use.
+
+          This can be used to, for example, set an OpenLDAP package
+          with custom overrides to enable modules or other
+          functionality.
+        '';
+      };
+
       user = mkOption {
         type = types.str;
         default = "openldap";
@@ -152,10 +164,10 @@ in
         ";
         example = literalExample ''
             '''
-            include ${pkgs.openldap.out}/etc/schema/core.schema
-            include ${pkgs.openldap.out}/etc/schema/cosine.schema
-            include ${pkgs.openldap.out}/etc/schema/inetorgperson.schema
-            include ${pkgs.openldap.out}/etc/schema/nis.schema
+            include ${openldap.out}/etc/schema/core.schema
+            include ${openldap.out}/etc/schema/cosine.schema
+            include ${openldap.out}/etc/schema/inetorgperson.schema
+            include ${openldap.out}/etc/schema/nis.schema
 
             database bdb
             suffix dc=example,dc=org
diff --git a/nixos/modules/services/databases/postgresql.nix b/nixos/modules/services/databases/postgresql.nix
index 579b6a4d9c672..c726a08e34f2c 100644
--- a/nixos/modules/services/databases/postgresql.nix
+++ b/nixos/modules/services/databases/postgresql.nix
@@ -21,7 +21,7 @@ let
       listen_addresses = '${if cfg.enableTCPIP then "*" else "localhost"}'
       port = ${toString cfg.port}
       ${cfg.extraConfig}
-    ''; 
+    '';
 
   groupAccessAvailable = versionAtLeast postgresql.version "11.0";
 
@@ -55,9 +55,13 @@ in
 
       dataDir = mkOption {
         type = types.path;
+        defaultText = "/var/lib/postgresql/\${config.services.postgresql.package.psqlSchema}";
         example = "/var/lib/postgresql/11";
         description = ''
-          Data directory for PostgreSQL.
+          The data directory for PostgreSQL. If left as the default value
+          this directory will automatically be created before the PostgreSQL server starts, otherwise
+          the sysadmin is responsible for ensuring the directory exists with appropriate ownership
+          and permissions.
         '';
       };
 
@@ -221,14 +225,15 @@ in
           Contents of the <filename>recovery.conf</filename> file.
         '';
       };
+
       superUser = mkOption {
         type = types.str;
-        default= if versionAtLeast config.system.stateVersion "17.09" then "postgres" else "root";
+        default = "postgres";
         internal = true;
+        readOnly = true;
         description = ''
-          NixOS traditionally used 'root' as superuser, most other distros use 'postgres'.
-          From 17.09 we also try to follow this standard. Internal since changing this value
-          would lead to breakage while setting up databases.
+          PostgreSQL superuser account to use for various operations. Internal since changing
+          this value would lead to breakage while setting up databases.
         '';
         };
     };
@@ -249,10 +254,7 @@ in
             else if versionAtLeast config.system.stateVersion "16.03" then pkgs.postgresql_9_5
             else throw "postgresql_9_4 was removed, please upgrade your postgresql version.");
 
-    services.postgresql.dataDir =
-      mkDefault (if versionAtLeast config.system.stateVersion "17.09"
-                  then "/var/lib/postgresql/${cfg.package.psqlSchema}"
-                  else "/var/db/postgresql");
+    services.postgresql.dataDir = mkDefault "/var/lib/postgresql/${cfg.package.psqlSchema}";
 
     services.postgresql.authentication = mkAfter
       ''
@@ -291,59 +293,28 @@ in
 
         preStart =
           ''
-            # Create data directory.
             if ! test -e ${cfg.dataDir}/PG_VERSION; then
-              mkdir -m 0700 -p ${cfg.dataDir}
+              # Cleanup the data directory.
               rm -f ${cfg.dataDir}/*.conf
-              chown -R postgres:postgres ${cfg.dataDir}
-            fi
-          ''; # */
 
-        script =
-          ''
-            # Initialise the database.
-            if ! test -e ${cfg.dataDir}/PG_VERSION; then
+              # Initialise the database.
               initdb -U ${cfg.superUser} ${concatStringsSep " " cfg.initdbArgs}
+
               # See postStart!
               touch "${cfg.dataDir}/.first_startup"
             fi
+
             ln -sfn "${configFile}" "${cfg.dataDir}/postgresql.conf"
             ${optionalString (cfg.recoveryConfig != null) ''
               ln -sfn "${pkgs.writeText "recovery.conf" cfg.recoveryConfig}" \
                 "${cfg.dataDir}/recovery.conf"
             ''}
-            ${optionalString (!groupAccessAvailable) ''
-              # postgresql pre 11.0 doesn't start if state directory mode is group accessible
-              chmod 0700 "${cfg.dataDir}"
-            ''}
-
-            exec postgres
           '';
 
-        serviceConfig =
-          { ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
-            User = "postgres";
-            Group = "postgres";
-            PermissionsStartOnly = true;
-            RuntimeDirectory = "postgresql";
-            Type = if versionAtLeast cfg.package.version "9.6"
-                   then "notify"
-                   else "simple";
-
-            # Shut down Postgres using SIGINT ("Fast Shutdown mode").  See
-            # http://www.postgresql.org/docs/current/static/server-shutdown.html
-            KillSignal = "SIGINT";
-            KillMode = "mixed";
-
-            # Give Postgres a decent amount of time to clean up after
-            # receiving systemd's SIGINT.
-            TimeoutSec = 120;
-          };
-
         # Wait for PostgreSQL to be ready to accept connections.
         postStart =
           ''
-            PSQL="${pkgs.utillinux}/bin/runuser -u ${cfg.superUser} -- psql --port=${toString cfg.port}"
+            PSQL="psql --port=${toString cfg.port}"
 
             while ! $PSQL -d postgres -c "" 2> /dev/null; do
                 if ! kill -0 "$MAINPID"; then exit 1; fi
@@ -369,6 +340,32 @@ in
             '') cfg.ensureUsers}
           '';
 
+        serviceConfig = mkMerge [
+          { ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+            User = "postgres";
+            Group = "postgres";
+            RuntimeDirectory = "postgresql";
+            Type = if versionAtLeast cfg.package.version "9.6"
+                   then "notify"
+                   else "simple";
+
+            # Shut down Postgres using SIGINT ("Fast Shutdown mode").  See
+            # http://www.postgresql.org/docs/current/static/server-shutdown.html
+            KillSignal = "SIGINT";
+            KillMode = "mixed";
+
+            # Give Postgres a decent amount of time to clean up after
+            # receiving systemd's SIGINT.
+            TimeoutSec = 120;
+
+            ExecStart = "${postgresql}/bin/postgres";
+          }
+          (mkIf (cfg.dataDir == "/var/lib/postgresql/${cfg.package.psqlSchema}") {
+            StateDirectory = "postgresql postgresql/${cfg.package.psqlSchema}";
+            StateDirectoryMode = if groupAccessAvailable then "0750" else "0700";
+          })
+        ];
+
         unitConfig.RequiresMountsFor = "${cfg.dataDir}";
       };
 
diff --git a/nixos/modules/services/databases/riak-cs.nix b/nixos/modules/services/databases/riak-cs.nix
index 2cb204f729a73..fa6ac88633185 100644
--- a/nixos/modules/services/databases/riak-cs.nix
+++ b/nixos/modules/services/databases/riak-cs.nix
@@ -35,7 +35,7 @@ in
           Name of the Erlang node.
         '';
       };
-      
+
       anonymousUserCreation = mkOption {
         type = types.bool;
         default = false;
diff --git a/nixos/modules/services/desktops/flatpak.nix b/nixos/modules/services/desktops/flatpak.nix
index 7fb0024f37dc7..7da92cc9f2649 100644
--- a/nixos/modules/services/desktops/flatpak.nix
+++ b/nixos/modules/services/desktops/flatpak.nix
@@ -42,6 +42,7 @@ in {
     # It has been possible since https://github.com/flatpak/flatpak/releases/tag/1.3.2
     # to build a SELinux policy module.
 
+    # TODO: use sysusers.d
     users.users.flatpak = {
       description = "Flatpak system helper";
       group = "flatpak";
diff --git a/nixos/modules/services/desktops/malcontent.nix b/nixos/modules/services/desktops/malcontent.nix
index 5d6912595b52d..1fbeb17e6aeb4 100644
--- a/nixos/modules/services/desktops/malcontent.nix
+++ b/nixos/modules/services/desktops/malcontent.nix
@@ -28,7 +28,10 @@ with lib;
       malcontent-ui
     ];
 
-    services.dbus.packages = [ pkgs.malcontent ];
+    services.dbus.packages = [
+      # D-Bus services are in `out`, not the default `bin` output that would be picked up by `makeDbusConf`.
+      pkgs.malcontent.out
+    ];
 
     services.accounts-daemon.enable = true;
 
diff --git a/nixos/modules/services/development/jupyter/default.nix b/nixos/modules/services/development/jupyter/default.nix
index e598b0186450b..6a5fd6b2940e8 100644
--- a/nixos/modules/services/development/jupyter/default.nix
+++ b/nixos/modules/services/development/jupyter/default.nix
@@ -6,10 +6,7 @@ let
 
   cfg = config.services.jupyter;
 
-  # NOTE: We don't use top-level jupyter because we don't
-  # want to pass in JUPYTER_PATH but use .environment instead,
-  # saving a rebuild.
-  package = pkgs.python3.pkgs.notebook;
+  package = cfg.package;
 
   kernels = (pkgs.jupyter-kernel.create  {
     definitions = if cfg.kernels != null
@@ -37,6 +34,27 @@ in {
       '';
     };
 
+    package = mkOption {
+      type = types.package;
+      # NOTE: We don't use top-level jupyter because we don't
+      # want to pass in JUPYTER_PATH but use .environment instead,
+      # saving a rebuild.
+      default = pkgs.python3.pkgs.notebook;
+      description = ''
+        Jupyter package to use.
+      '';
+    };
+
+    command = mkOption {
+      type = types.str;
+      default = "jupyter-notebook";
+      example = "jupyter-lab";
+      description = ''
+        Which command the service runs. Note that not all jupyter packages
+        have all commands, e.g. jupyter-lab isn't present in the default package.
+       '';
+    };
+
     port = mkOption {
       type = types.int;
       default = 8888;
@@ -157,7 +175,7 @@ in {
 
         serviceConfig = {
           Restart = "always";
-          ExecStart = ''${package}/bin/jupyter-notebook \
+          ExecStart = ''${package}/bin/${cfg.command} \
             --no-browser \
             --ip=${cfg.ip} \
             --port=${toString cfg.port} --port-retries 0 \
diff --git a/nixos/modules/services/development/jupyterhub/default.nix b/nixos/modules/services/development/jupyterhub/default.nix
new file mode 100644
index 0000000000000..be6aaed93ac69
--- /dev/null
+++ b/nixos/modules/services/development/jupyterhub/default.nix
@@ -0,0 +1,190 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.jupyterhub;
+
+  kernels = (pkgs.jupyter-kernel.create  {
+    definitions = if cfg.kernels != null
+      then cfg.kernels
+      else  pkgs.jupyter-kernel.default;
+  });
+
+  jupyterhubConfig = pkgs.writeText "jupyterhub_config.py" ''
+    c.JupyterHub.bind_url = "http://${cfg.host}:${toString cfg.port}"
+
+    c.JupyterHub.authentication_class = "${cfg.authentication}"
+    c.JupyterHub.spawner_class = "${cfg.spawner}"
+
+    c.SystemdSpawner.default_url = '/lab'
+    c.SystemdSpawner.cmd = "${cfg.jupyterlabEnv}/bin/jupyterhub-singleuser"
+    c.SystemdSpawner.environment = {
+      'JUPYTER_PATH': '${kernels}'
+    }
+
+    ${cfg.extraConfig}
+  '';
+in {
+  meta.maintainers = with maintainers; [ costrouc ];
+
+  options.services.jupyterhub = {
+    enable = mkEnableOption "Jupyterhub development server";
+
+    authentication = mkOption {
+      type = types.str;
+      default = "jupyterhub.auth.PAMAuthenticator";
+      description = ''
+        Jupyterhub authentication to use
+
+        There are many authenticators available including: oauth, pam,
+        ldap, kerberos, etc.
+      '';
+    };
+
+    spawner = mkOption {
+      type = types.str;
+      default = "systemdspawner.SystemdSpawner";
+      description = ''
+        Jupyterhub spawner to use
+
+        There are many spawners available including: local process,
+        systemd, docker, kubernetes, yarn, batch, etc.
+      '';
+    };
+
+    extraConfig = mkOption {
+      type = types.lines;
+      default = "";
+      description = ''
+        Extra contents appended to the jupyterhub configuration
+
+        Jupyterhub configuration is a normal python file using
+        Traitlets. https://jupyterhub.readthedocs.io/en/stable/getting-started/config-basics.html. The
+        base configuration of this module was designed to have sane
+        defaults for configuration but you can override anything since
+        this is a python file.
+      '';
+      example = literalExample ''
+         c.SystemdSpawner.mem_limit = '8G'
+         c.SystemdSpawner.cpu_limit = 2.0
+      '';
+    };
+
+    jupyterhubEnv = mkOption {
+      type = types.package;
+      default = (pkgs.python3.withPackages (p: with p; [
+        jupyterhub
+        jupyterhub-systemdspawner
+      ]));
+      description = ''
+        Python environment to run jupyterhub
+
+        Customizing will affect the packages available in the hub and
+        proxy. This will allow packages to be available for the
+        extraConfig that you may need. This will not normally need to
+        be changed.
+      '';
+    };
+
+    jupyterlabEnv = mkOption {
+      type = types.package;
+      default = (pkgs.python3.withPackages (p: with p; [
+        jupyterhub
+        jupyterlab
+      ]));
+      description = ''
+        Python environment to run jupyterlab
+
+        Customizing will affect the packages available in the
+        jupyterlab server and the default kernel provided. This is the
+        way to customize the jupyterlab extensions and jupyter
+        notebook extensions. This will not normally need to
+        be changed.
+      '';
+    };
+
+    kernels = mkOption {
+      type = types.nullOr (types.attrsOf(types.submodule (import ../jupyter/kernel-options.nix {
+        inherit lib;
+      })));
+
+      default = null;
+      example = literalExample ''
+        {
+          python3 = let
+            env = (pkgs.python3.withPackages (pythonPackages: with pythonPackages; [
+                    ipykernel
+                    pandas
+                    scikitlearn
+                  ]));
+          in {
+            displayName = "Python 3 for machine learning";
+            argv = [
+              "''${env.interpreter}"
+              "-m"
+              "ipykernel_launcher"
+              "-f"
+              "{connection_file}"
+            ];
+            language = "python";
+            logo32 = "''${env}/''${env.sitePackages}/ipykernel/resources/logo-32x32.png";
+            logo64 = "''${env}/''${env.sitePackages}/ipykernel/resources/logo-64x64.png";
+          };
+        }
+      '';
+      description = ''
+        Declarative kernel config
+
+        Kernels can be declared in any language that supports and has
+        the required dependencies to communicate with a jupyter server.
+        In python's case, it means that ipykernel package must always be
+        included in the list of packages of the targeted environment.
+      '';
+    };
+
+    port = mkOption {
+      type = types.port;
+      default = 8000;
+      description = ''
+        Port number Jupyterhub will be listening on
+      '';
+    };
+
+    host = mkOption {
+      type = types.str;
+      default = "0.0.0.0";
+      description = ''
+        Bind IP JupyterHub will be listening on
+      '';
+    };
+
+    stateDirectory = mkOption {
+      type = types.str;
+      default = "jupyterhub";
+      description = ''
+        Directory for jupyterhub state (token + database)
+      '';
+    };
+  };
+
+  config = mkMerge [
+    (mkIf cfg.enable  {
+      systemd.services.jupyterhub = {
+        description = "Jupyterhub development server";
+
+        after = [ "network.target" ];
+        wantedBy = [ "multi-user.target" ];
+
+        serviceConfig = {
+          Restart = "always";
+          ExecStart = "${cfg.jupyterhubEnv}/bin/jupyterhub --config ${jupyterhubConfig}";
+          User = "root";
+          StateDirectory = cfg.stateDirectory;
+          WorkingDirectory = "/var/lib/${cfg.stateDirectory}";
+        };
+      };
+    })
+  ];
+}
diff --git a/nixos/modules/services/editors/emacs.nix b/nixos/modules/services/editors/emacs.nix
index d791b387665fc..00d9eaad9eb9f 100644
--- a/nixos/modules/services/editors/emacs.nix
+++ b/nixos/modules/services/editors/emacs.nix
@@ -15,26 +15,27 @@ let
     fi
   '';
 
-desktopApplicationFile = pkgs.writeTextFile {
-  name = "emacsclient.desktop";
-  destination = "/share/applications/emacsclient.desktop";
-  text = ''
-[Desktop Entry]
-Name=Emacsclient
-GenericName=Text Editor
-Comment=Edit text
-MimeType=text/english;text/plain;text/x-makefile;text/x-c++hdr;text/x-c++src;text/x-chdr;text/x-csrc;text/x-java;text/x-moc;text/x-pascal;text/x-tcl;text/x-tex;application/x-shellscript;text/x-c;text/x-c++;
-Exec=emacseditor %F
-Icon=emacs
-Type=Application
-Terminal=false
-Categories=Development;TextEditor;
-StartupWMClass=Emacs
-Keywords=Text;Editor;
-'';
-};
-
-in {
+  desktopApplicationFile = pkgs.writeTextFile {
+    name = "emacsclient.desktop";
+    destination = "/share/applications/emacsclient.desktop";
+    text = ''
+      [Desktop Entry]
+      Name=Emacsclient
+      GenericName=Text Editor
+      Comment=Edit text
+      MimeType=text/english;text/plain;text/x-makefile;text/x-c++hdr;text/x-c++src;text/x-chdr;text/x-csrc;text/x-java;text/x-moc;text/x-pascal;text/x-tcl;text/x-tex;application/x-shellscript;text/x-c;text/x-c++;
+      Exec=emacseditor %F
+      Icon=emacs
+      Type=Application
+      Terminal=false
+      Categories=Development;TextEditor;
+      StartupWMClass=Emacs
+      Keywords=Text;Editor;
+    '';
+  };
+
+in
+{
 
   options.services.emacs = {
     enable = mkOption {
@@ -86,10 +87,10 @@ in {
       description = "Emacs: the extensible, self-documenting text editor";
 
       serviceConfig = {
-        Type      = "forking";
+        Type = "forking";
         ExecStart = "${pkgs.bash}/bin/bash -c 'source ${config.system.build.setEnvironment}; exec ${cfg.package}/bin/emacs --daemon'";
-        ExecStop  = "${cfg.package}/bin/emacsclient --eval (kill-emacs)";
-        Restart   = "always";
+        ExecStop = "${cfg.package}/bin/emacsclient --eval (kill-emacs)";
+        Restart = "always";
       };
     } // optionalAttrs cfg.enable { wantedBy = [ "default.target" ]; };
 
diff --git a/nixos/modules/services/editors/emacs.xml b/nixos/modules/services/editors/emacs.xml
index 74c60014dcea0..05f87df43bcd2 100644
--- a/nixos/modules/services/editors/emacs.xml
+++ b/nixos/modules/services/editors/emacs.xml
@@ -53,11 +53,11 @@
        <varname>emacs</varname>
       </term>
       <term>
-       <varname>emacs25</varname>
+       <varname>emacs</varname>
       </term>
       <listitem>
        <para>
-        The latest stable version of Emacs 25 using the
+        The latest stable version of Emacs using the
         <link
                 xlink:href="http://www.gtk.org">GTK 2</link>
         widget toolkit.
@@ -66,11 +66,11 @@
      </varlistentry>
      <varlistentry>
       <term>
-       <varname>emacs25-nox</varname>
+       <varname>emacs-nox</varname>
       </term>
       <listitem>
        <para>
-        Emacs 25 built without any dependency on X11 libraries.
+        Emacs built without any dependency on X11 libraries.
        </para>
       </listitem>
      </varlistentry>
@@ -79,11 +79,11 @@
        <varname>emacsMacport</varname>
       </term>
       <term>
-       <varname>emacs25Macport</varname>
+       <varname>emacsMacport</varname>
       </term>
       <listitem>
        <para>
-        Emacs 25 with the "Mac port" patches, providing a more native look and
+        Emacs with the "Mac port" patches, providing a more native look and
         feel under macOS.
        </para>
       </listitem>
diff --git a/nixos/modules/services/games/minetest-server.nix b/nixos/modules/services/games/minetest-server.nix
index 98e69c6dc0ea6..f52079fc1ef62 100644
--- a/nixos/modules/services/games/minetest-server.nix
+++ b/nixos/modules/services/games/minetest-server.nix
@@ -5,12 +5,12 @@ with lib;
 let
   cfg   = config.services.minetest-server;
   flag  = val: name: if val != null then "--${name} ${val} " else "";
-  flags = [ 
-    (flag cfg.gameId "gameid") 
-    (flag cfg.world "world") 
-    (flag cfg.configPath "config") 
-    (flag cfg.logPath "logfile") 
-    (flag cfg.port "port") 
+  flags = [
+    (flag cfg.gameId "gameid")
+    (flag cfg.world "world")
+    (flag cfg.configPath "config")
+    (flag cfg.logPath "logfile")
+    (flag cfg.port "port")
   ];
 in
 {
@@ -26,7 +26,7 @@ in
         type        = types.nullOr types.str;
         default     = null;
         description = ''
-          Id of the game to use. To list available games run 
+          Id of the game to use. To list available games run
           `minetestserver --gameid list`.
 
           If only one game exists, this option can be null.
@@ -59,7 +59,7 @@ in
         type        = types.nullOr types.path;
         default     = null;
         description = ''
-          Path to logfile for logging. 
+          Path to logfile for logging.
 
           If set to null, logging will be output to stdout which means
           all output will be catched by systemd.
diff --git a/nixos/modules/services/games/terraria.nix b/nixos/modules/services/games/terraria.nix
index a59b74c0b4c4b..413660321ec3c 100644
--- a/nixos/modules/services/games/terraria.nix
+++ b/nixos/modules/services/games/terraria.nix
@@ -7,7 +7,7 @@ let
   worldSizeMap = { small = 1; medium = 2; large = 3; };
   valFlag = name: val: optionalString (val != null) "-${name} \"${escape ["\\" "\""] (toString val)}\"";
   boolFlag = name: val: optionalString val "-${name}";
-  flags = [ 
+  flags = [
     (valFlag "port" cfg.port)
     (valFlag "maxPlayers" cfg.maxPlayers)
     (valFlag "password" cfg.password)
diff --git a/nixos/modules/services/hardware/thinkfan.nix b/nixos/modules/services/hardware/thinkfan.nix
index 7c105e99ca54a..3bda61ed1a938 100644
--- a/nixos/modules/services/hardware/thinkfan.nix
+++ b/nixos/modules/services/hardware/thinkfan.nix
@@ -67,7 +67,7 @@ in {
         type = types.bool;
         default = false;
         description = ''
-          Whether to build thinkfan with SMART support to read temperatures 
+          Whether to build thinkfan with SMART support to read temperatures
           directly from hard disks.
         '';
       };
diff --git a/nixos/modules/services/hardware/tlp.nix b/nixos/modules/services/hardware/tlp.nix
index 3962d7b159894..4230f2edd2793 100644
--- a/nixos/modules/services/hardware/tlp.nix
+++ b/nixos/modules/services/hardware/tlp.nix
@@ -8,12 +8,8 @@ let
   mkTlpConfig = tlpConfig: generators.toKeyValue {
     mkKeyValue = generators.mkKeyValueDefault {
       mkValueString = val:
-        if isInt val then toString val
-        else if isString val then val
-        else if true == val then "1"
-        else if false == val then "0"
-        else if isList val then "\"" + (concatStringsSep " " val) + "\""
-        else err "invalid value provided to mkTlpConfig:" (toString val);
+        if isList val then "\"" + (toString val) + "\""
+        else toString val;
     } "=";
   } tlpConfig;
 in
@@ -27,10 +23,24 @@ in
         description = "Whether to enable the TLP power management daemon.";
       };
 
+      settings = mkOption {type = with types; attrsOf (oneOf [bool int float str (listOf str)]);
+        default = {};
+        example = {
+          SATA_LINKPWR_ON_BAT = "med_power_with_dipm";
+          USB_BLACKLIST_PHONE = 1;
+        };
+        description = ''
+          Options passed to TLP. See https://linrunner.de/tlp for all supported options..
+        '';
+      };
+
       extraConfig = mkOption {
         type = types.lines;
         default = "";
-        description = "Additional configuration variables for TLP";
+        description = ''
+          Verbatim additional configuration variables for TLP.
+          DEPRECATED: use services.tlp.config instead.
+        '';
       };
     };
   };
@@ -39,8 +49,20 @@ in
   config = mkIf cfg.enable {
     boot.kernelModules = [ "msr" ];
 
+    warnings = optional (cfg.extraConfig != "") ''
+      Using config.services.tlp.extraConfig is deprecated and will become unsupported in a future release. Use config.services.tlp.settings instead.
+    '';
+
+    assertions = [{
+      assertion = cfg.enable -> config.powerManagement.scsiLinkPolicy == null;
+      message = ''
+        `services.tlp.enable` and `config.powerManagement.scsiLinkPolicy` cannot be set both.
+        Set `services.tlp.settings.SATA_LINKPWR_ON_AC` and `services.tlp.settings.SATA_LINKPWR_ON_BAT` instead.
+      '';
+    }];
+
     environment.etc = {
-      "tlp.conf".text = cfg.extraConfig;
+      "tlp.conf".text = (mkTlpConfig cfg.settings) + cfg.extraConfig;
     } // optionalAttrs enableRDW {
       "NetworkManager/dispatcher.d/99tlp-rdw-nm".source =
         "${tlp}/etc/NetworkManager/dispatcher.d/99tlp-rdw-nm";
@@ -48,18 +70,25 @@ in
 
     environment.systemPackages = [ tlp ];
 
-    # FIXME: When the config is parametrized we need to move these into a
-    # conditional on the relevant options being enabled.
-    powerManagement = {
-      scsiLinkPolicy = null;
-      cpuFreqGovernor = null;
-      cpufreq.max = null;
-      cpufreq.min = null;
+
+    services.tlp.settings = let
+      cfg = config.powerManagement;
+      maybeDefault = val: lib.mkIf (val != null) (lib.mkDefault val);
+    in {
+      CPU_SCALING_GOVERNOR_ON_AC = maybeDefault cfg.cpuFreqGovernor;
+      CPU_SCALING_GOVERNOR_ON_BAT = maybeDefault cfg.cpuFreqGovernor;
+      CPU_SCALING_MIN_FREQ_ON_AC = maybeDefault cfg.cpufreq.min;
+      CPU_SCALING_MAX_FREQ_ON_AC = maybeDefault cfg.cpufreq.max;
+      CPU_SCALING_MIN_FREQ_ON_BAT = maybeDefault cfg.cpufreq.min;
+      CPU_SCALING_MAX_FREQ_ON_BAT = maybeDefault cfg.cpufreq.max;
     };
 
     services.udev.packages = [ tlp ];
 
     systemd = {
+      # use native tlp instead because it can also differentiate between AC/BAT
+      services.cpufreq.enable = false;
+
       packages = [ tlp ];
       # XXX: These must always be disabled/masked according to [1].
       #
diff --git a/nixos/modules/services/hardware/trezord.nix b/nixos/modules/services/hardware/trezord.nix
index c517e9fbb2bda..2594ac7437109 100644
--- a/nixos/modules/services/hardware/trezord.nix
+++ b/nixos/modules/services/hardware/trezord.nix
@@ -10,7 +10,7 @@ in {
   meta = {
     doc = ./trezord.xml;
   };
-  
+
   ### interface
 
   options = {
@@ -40,7 +40,7 @@ in {
       };
     };
   };
-  
+
   ### implementation
 
   config = mkIf cfg.enable {
diff --git a/nixos/modules/services/hardware/undervolt.nix b/nixos/modules/services/hardware/undervolt.nix
index da627af73bc68..054ffa35050a9 100644
--- a/nixos/modules/services/hardware/undervolt.nix
+++ b/nixos/modules/services/hardware/undervolt.nix
@@ -25,8 +25,11 @@ let
 in
 {
   options.services.undervolt = {
-    enable = mkEnableOption
-      "Intel CPU undervolting service (WARNING: may permanently damage your hardware!)";
+    enable = mkEnableOption ''
+       Undervolting service for Intel CPUs.
+
+       Warning: This service is not endorsed by Intel and may permanently damage your hardware. Use at your own risk!
+    '';
 
     verbose = mkOption {
       type = types.bool;
@@ -100,6 +103,17 @@ in
         The temperature target on battery power in Celsius degrees.
       '';
     };
+
+    useTimer = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Whether to set a timer that applies the undervolt settings every 30s.
+        This will cause spam in the journal but might be required for some
+        hardware under specific conditions.
+        Enable this if your undervolt settings don't hold.
+      '';
+    };
   };
 
   config = mkIf cfg.enable {
@@ -111,6 +125,11 @@ in
       path = [ pkgs.undervolt ];
 
       description = "Intel Undervolting Service";
+
+      # Apply undervolt on boot, nixos generation switch and resume
+      wantedBy = [ "multi-user.target" "post-resume.target" ];
+      after = [ "post-resume.target" ]; # Not sure why but it won't work without this
+
       serviceConfig = {
         Type = "oneshot";
         Restart = "no";
@@ -118,7 +137,7 @@ in
       };
     };
 
-    systemd.timers.undervolt = {
+    systemd.timers.undervolt = mkIf cfg.useTimer {
       description = "Undervolt timer to ensure voltage settings are always applied";
       partOf = [ "undervolt.service" ];
       wantedBy = [ "multi-user.target" ];
diff --git a/nixos/modules/services/logging/logrotate.nix b/nixos/modules/services/logging/logrotate.nix
index 565618b27a87b..7d6102b82557e 100644
--- a/nixos/modules/services/logging/logrotate.nix
+++ b/nixos/modules/services/logging/logrotate.nix
@@ -5,54 +5,93 @@ with lib;
 let
   cfg = config.services.logrotate;
 
-  pathOptions = {
+  pathOpts = {
     options = {
+      enable = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Whether to enable log rotation for this path. This can be used to explicitly disable
+          logging that has been configured by NixOS.
+        '';
+      };
+
       path = mkOption {
         type = types.str;
-        description = "The path to log files to be rotated";
+        description = ''
+          The path to log files to be rotated.
+        '';
       };
+
       user = mkOption {
-        type = types.str;
-        description = "The user account to use for rotation";
+        type = with types; nullOr str;
+        default = null;
+        description = ''
+          The user account to use for rotation.
+        '';
       };
+
       group = mkOption {
-        type = types.str;
-        description = "The group to use for rotation";
+        type = with types; nullOr str;
+        default = null;
+        description = ''
+          The group to use for rotation.
+        '';
       };
+
       frequency = mkOption {
-        type = types.enum [
-          "daily" "weekly" "monthly" "yearly"
-        ];
+        type = types.enum [ "daily" "weekly" "monthly" "yearly" ];
         default = "daily";
-        description = "How often to rotate the logs";
+        description = ''
+          How often to rotate the logs.
+        '';
       };
+
       keep = mkOption {
         type = types.int;
         default = 20;
-        description = "How many rotations to keep";
+        description = ''
+          How many rotations to keep.
+        '';
       };
+
       extraConfig = mkOption {
         type = types.lines;
         default = "";
-        description = "Extra logrotate config options for this path";
+        description = ''
+          Extra logrotate config options for this path. Refer to
+          <link xlink:href="https://linux.die.net/man/8/logrotate"/> for details.
+        '';
+      };
+
+      priority = mkOption {
+        type = types.int;
+        default = 1000;
+        description = ''
+          Order of this logrotate block in relation to the others. The semantics are
+          the same as with `lib.mkOrder`. Smaller values have a greater priority.
+        '';
       };
     };
-  };
 
-  pathConfig = options: ''
-    "${options.path}" {
-      su ${options.user} ${options.group}
-      ${options.frequency}
+    config.extraConfig = ''
       missingok
       notifempty
-      rotate ${toString options.keep}
-      ${options.extraConfig}
+    '';
+  };
+
+  mkConf = pathOpts: ''
+    # generated by NixOS using the `services.logrotate.paths.${pathOpts.name}` attribute set
+    "${pathOpts.path}" {
+      ${optionalString (pathOpts.user != null || pathOpts.group != null) "su ${pathOpts.user} ${pathOpts.group}"}
+      ${pathOpts.frequency}
+      rotate ${toString pathOpts.keep}
+      ${pathOpts.extraConfig}
     }
   '';
 
-  configFile = pkgs.writeText "logrotate.conf" (
-    (concatStringsSep "\n" ((map pathConfig cfg.paths) ++ [cfg.extraConfig]))
-  );
+  paths = sortProperties (mapAttrsToList (name: pathOpts: pathOpts // { name = name; }) (filterAttrs (_: pathOpts: pathOpts.enable) cfg.paths));
+  configFile = pkgs.writeText "logrotate.conf" (concatStringsSep "\n" ((map mkConf paths) ++ [ cfg.extraConfig ]));
 
 in
 {
@@ -65,41 +104,66 @@ in
       enable = mkEnableOption "the logrotate systemd service";
 
       paths = mkOption {
-        type = types.listOf (types.submodule pathOptions);
-        default = [];
-        description = "List of attribute sets with paths to rotate";
-        example = {
-          "/var/log/myapp/*.log" = {
-            user = "myuser";
-            group = "mygroup";
-            rotate = "weekly";
-            keep = 5;
-          };
-        };
+        type = with types; attrsOf (submodule pathOpts);
+        default = {};
+        description = ''
+          Attribute set of paths to rotate. The order each block appears in the generated configuration file
+          can be controlled by the <link linkend="opt-services.logrotate.paths._name_.priority">priority</link> option
+          using the same semantics as `lib.mkOrder`. Smaller values have a greater priority.
+        '';
+        example = literalExample ''
+          {
+            httpd = {
+              path = "/var/log/httpd/*.log";
+              user = config.services.httpd.user;
+              group = config.services.httpd.group;
+              keep = 7;
+            };
+
+            myapp = {
+              path = "/var/log/myapp/*.log";
+              user = "myuser";
+              group = "mygroup";
+              frequency = "weekly";
+              keep = 5;
+              priority = 1;
+            };
+          }
+        '';
       };
 
       extraConfig = mkOption {
         default = "";
         type = types.lines;
         description = ''
-          Extra contents to add to the logrotate config file.
-          See https://linux.die.net/man/8/logrotate
+          Extra contents to append to the logrotate configuration file. Refer to
+          <link xlink:href="https://linux.die.net/man/8/logrotate"/> for details.
         '';
       };
     };
   };
 
   config = mkIf cfg.enable {
-    systemd.services.logrotate = {
-      description   = "Logrotate Service";
-      wantedBy      = [ "multi-user.target" ];
-      startAt       = "*-*-* *:05:00";
+    assertions = mapAttrsToList (name: pathOpts:
+      { assertion = (pathOpts.user != null) == (pathOpts.group != null);
+        message = ''
+          If either of `services.logrotate.paths.${name}.user` or `services.logrotate.paths.${name}.group` are specified then *both* must be specified.
+        '';
+      }
+    ) cfg.paths;
 
-      serviceConfig.Restart = "no";
-      serviceConfig.User    = "root";
+    systemd.services.logrotate = {
+      description = "Logrotate Service";
+      wantedBy = [ "multi-user.target" ];
+      startAt = "*-*-* *:05:00";
       script = ''
         exec ${pkgs.logrotate}/sbin/logrotate ${configFile}
       '';
+
+      serviceConfig = {
+        Restart = "no";
+        User = "root";
+      };
     };
   };
 }
diff --git a/nixos/modules/services/logging/logstash.nix b/nixos/modules/services/logging/logstash.nix
index 21a83803fd8c6..bf92425f998bb 100644
--- a/nixos/modules/services/logging/logstash.nix
+++ b/nixos/modules/services/logging/logstash.nix
@@ -4,13 +4,9 @@ with lib;
 
 let
   cfg = config.services.logstash;
-  pluginPath = lib.concatStringsSep ":" cfg.plugins;
-  havePluginPath = lib.length cfg.plugins > 0;
   ops = lib.optionalString;
   verbosityFlag = "--log.level " + cfg.logLevel;
 
-  pluginsPath = "--path.plugins ${pluginPath}";
-
   logstashConf = pkgs.writeText "logstash.conf" ''
     input {
       ${cfg.inputConfig}
@@ -173,7 +169,7 @@ in
         ExecStart = concatStringsSep " " (filter (s: stringLength s != 0) [
           "${cfg.package}/bin/logstash"
           "-w ${toString cfg.filterWorkers}"
-          (ops havePluginPath pluginsPath)
+          (concatMapStringsSep " " (x: "--path.plugins ${x}") cfg.plugins)
           "${verbosityFlag}"
           "-f ${logstashConf}"
           "--path.settings ${logstashSettingsDir}"
diff --git a/nixos/modules/services/mail/dovecot.nix b/nixos/modules/services/mail/dovecot.nix
index 51cbcbf1cbc8a..c166ef68f2929 100644
--- a/nixos/modules/services/mail/dovecot.nix
+++ b/nixos/modules/services/mail/dovecot.nix
@@ -1,4 +1,4 @@
-{ config, lib, pkgs, ... }:
+{ options, config, lib, pkgs, ... }:
 
 with lib;
 
@@ -83,11 +83,11 @@ let
     )
 
     (
-      optionalString (cfg.mailboxes != []) ''
+      optionalString (cfg.mailboxes != {}) ''
         protocol imap {
           namespace inbox {
             inbox=yes
-            ${concatStringsSep "\n" (map mailboxConfig cfg.mailboxes)}
+            ${concatStringsSep "\n" (map mailboxConfig (attrValues cfg.mailboxes))}
           }
         }
       ''
@@ -131,12 +131,13 @@ let
     special_use = \${toString mailbox.specialUse}
   '' + "}";
 
-  mailboxes = { ... }: {
+  mailboxes = { name, ... }: {
     options = {
       name = mkOption {
-        type = types.nullOr (types.strMatching ''[^"]+'');
+        type = types.strMatching ''[^"]+'';
         example = "Spam";
-        default = null;
+        default = name;
+        readOnly = true;
         description = "The name of the mailbox.";
       };
       auto = mkOption {
@@ -335,19 +336,11 @@ in
     };
 
     mailboxes = mkOption {
-      type = with types; let m = submodule mailboxes; in either (listOf m) (attrsOf m);
+      type = with types; coercedTo
+        (listOf unspecified)
+        (list: listToAttrs (map (entry: { name = entry.name; value = removeAttrs entry ["name"]; }) list))
+        (attrsOf (submodule mailboxes));
       default = {};
-      apply = x:
-        if isList x then warn "Declaring `services.dovecot2.mailboxes' as a list is deprecated and will break eval in 21.03!" x
-        else mapAttrsToList (name: value:
-          if value.name != null
-            then throw ''
-              When specifying dovecot2 mailboxes as attributes, declaring
-              a `name'-attribute is prohibited! The name ${value.name} should
-              be the attribute key!
-            ''
-          else value // { inherit name; }
-        ) x;
       example = literalExample ''
         {
           Spam = { specialUse = "Junk"; auto = "create"; };
@@ -471,6 +464,10 @@ in
 
     environment.systemPackages = [ dovecotPkg ];
 
+    warnings = mkIf (any isList options.services.dovecot2.mailboxes.definitions) [
+      "Declaring `services.dovecot2.mailboxes' as a list is deprecated and will break eval in 21.03! See the release notes for more info for migration."
+    ];
+
     assertions = [
       {
         assertion = intersectLists cfg.protocols [ "pop3" "imap" ] != [];
diff --git a/nixos/modules/services/mail/pfix-srsd.nix b/nixos/modules/services/mail/pfix-srsd.nix
index 38984f896d6af..e3dbf2a014f06 100644
--- a/nixos/modules/services/mail/pfix-srsd.nix
+++ b/nixos/modules/services/mail/pfix-srsd.nix
@@ -53,4 +53,4 @@ with lib;
       };
     };
   };
-}
\ No newline at end of file
+}
diff --git a/nixos/modules/services/mail/postfix.nix b/nixos/modules/services/mail/postfix.nix
index 608f64a68fb09..fd4d16cdc37b0 100644
--- a/nixos/modules/services/mail/postfix.nix
+++ b/nixos/modules/services/mail/postfix.nix
@@ -25,6 +25,8 @@ let
 
   clientRestrictions = concatStringsSep ", " (clientAccess ++ dnsBl);
 
+  smtpTlsSecurityLevel = if cfg.useDane then "dane" else "may";
+
   mainCf = let
     escape = replaceStrings ["$"] ["$$"];
     mkList = items: "\n  " + concatStringsSep ",\n  " items;
@@ -280,6 +282,17 @@ in
         description = "Whether to enable smtp submission.";
       };
 
+      enableSubmissions = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to enable smtp submission via smtps.
+
+          According to RFC 8314 this should be preferred
+          over STARTTLS for submission of messages by end user clients.
+        '';
+      };
+
       submissionOptions = mkOption {
         type = types.attrs;
         default = {
@@ -298,6 +311,29 @@ in
         description = "Options for the submission config in master.cf";
       };
 
+      submissionsOptions = mkOption {
+        type = types.attrs;
+        default = {
+          smtpd_sasl_auth_enable = "yes";
+          smtpd_client_restrictions = "permit_sasl_authenticated,reject";
+          milter_macro_daemon_name = "ORIGINATING";
+        };
+        example = {
+          smtpd_sasl_auth_enable = "yes";
+          smtpd_sasl_type = "dovecot";
+          smtpd_client_restrictions = "permit_sasl_authenticated,reject";
+          milter_macro_daemon_name = "ORIGINATING";
+        };
+        description = ''
+          Options for the submission config via smtps in master.cf.
+
+          smtpd_tls_security_level will be set to encrypt, if it is missing
+          or has one of the values "may" or "none".
+
+          smtpd_tls_wrappermode with value "yes" will be added automatically.
+        '';
+      };
+
       setSendmail = mkOption {
         type = types.bool;
         default = true;
@@ -454,7 +490,7 @@ in
         '';
         example = {
           mail_owner = "postfix";
-          smtp_use_tls = true;
+          smtp_tls_security_level = "may";
         };
       };
 
@@ -466,16 +502,26 @@ in
         ";
       };
 
-      sslCert = mkOption {
+      tlsTrustedAuthorities = mkOption {
         type = types.str;
-        default = "";
-        description = "SSL certificate to use.";
+        default = "${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt";
+        description = ''
+          File containing trusted certification authorities (CA) to verify certificates of mailservers contacted for mail delivery. This basically sets smtp_tls_CAfile and enables opportunistic tls. Defaults to NixOS trusted certification authorities.
+        '';
+      };
+
+      useDane = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Sets smtp_tls_security_level to "dane" rather than "may". See postconf(5) for details.
+        '';
       };
 
-      sslCACert = mkOption {
+      sslCert = mkOption {
         type = types.str;
         default = "";
-        description = "SSL certificate of CA.";
+        description = "SSL certificate to use.";
       };
 
       sslKey = mkOption {
@@ -771,18 +817,20 @@ in
         recipient_canonical_classes = [ "envelope_recipient" ];
       }
       // optionalAttrs cfg.enableHeaderChecks { header_checks = [ "regexp:/etc/postfix/header_checks" ]; }
+      // optionalAttrs (cfg.tlsTrustedAuthorities != "") {
+        smtp_tls_CAfile = cfg.tlsTrustedAuthorities;
+        smtp_tls_security_level = smtpTlsSecurityLevel;
+      }
       // optionalAttrs (cfg.sslCert != "") {
-        smtp_tls_CAfile = cfg.sslCACert;
         smtp_tls_cert_file = cfg.sslCert;
         smtp_tls_key_file = cfg.sslKey;
 
-        smtp_use_tls = true;
+        smtp_tls_security_level = smtpTlsSecurityLevel;
 
-        smtpd_tls_CAfile = cfg.sslCACert;
         smtpd_tls_cert_file = cfg.sslCert;
         smtpd_tls_key_file = cfg.sslKey;
 
-        smtpd_use_tls = true;
+        smtpd_tls_security_level = "may";
       };
 
       services.postfix.masterConfig = {
@@ -878,6 +926,23 @@ in
           command = "smtp";
           args = [ "-o" "smtp_fallback_relay=" ];
         };
+      } // optionalAttrs cfg.enableSubmissions {
+        submissions = {
+          type = "inet";
+          private = false;
+          command = "smtpd";
+          args = let
+            mkKeyVal = opt: val: [ "-o" (opt + "=" + val) ];
+            adjustSmtpTlsSecurityLevel = !(cfg.submissionsOptions ? smtpd_tls_security_level) ||
+                                      cfg.submissionsOptions.smtpd_tls_security_level == "none" ||
+                                      cfg.submissionsOptions.smtpd_tls_security_level == "may";
+            submissionsOptions = cfg.submissionsOptions // {
+              smtpd_tls_wrappermode = "yes";
+            } // optionalAttrs adjustSmtpTlsSecurityLevel {
+              smtpd_tls_security_level = "encrypt";
+            };
+          in concatLists (mapAttrsToList mkKeyVal submissionsOptions);
+        };
       };
     }
 
@@ -900,4 +965,9 @@ in
       services.postfix.mapFiles.client_access = checkClientAccessFile;
     })
   ]);
+
+  imports = [
+   (mkRemovedOptionModule [ "services" "postfix" "sslCACert" ]
+     "services.postfix.sslCACert was replaced by services.postfix.tlsTrustedAuthorities. In case you intend that your server should validate requested client certificates use services.postfix.extraConfig.")
+  ];
 }
diff --git a/nixos/modules/services/mail/roundcube.nix b/nixos/modules/services/mail/roundcube.nix
index ed1439745ac99..a0bbab64985b2 100644
--- a/nixos/modules/services/mail/roundcube.nix
+++ b/nixos/modules/services/mail/roundcube.nix
@@ -95,6 +95,18 @@ in
       '';
     };
 
+    maxAttachmentSize = mkOption {
+      type = types.int;
+      default = 18;
+      description = ''
+        The maximum attachment size in MB.
+
+        Note: Since roundcube only uses 70% of max upload values configured in php
+        30% is added automatically to <xref linkend="opt-services.roundcube.maxAttachmentSize"/>.
+      '';
+      apply = configuredMaxAttachmentSize: "${toString (configuredMaxAttachmentSize * 1.3)}M";
+    };
+
     extraConfig = mkOption {
       type = types.lines;
       default = "";
@@ -115,7 +127,7 @@ in
       $config = array();
       $config['db_dsnw'] = 'pgsql://${cfg.database.username}${lib.optionalString (!localDB) ":' . $password . '"}@${if localDB then "unix(/run/postgresql)" else cfg.database.host}/${cfg.database.dbname}';
       $config['log_driver'] = 'syslog';
-      $config['max_message_size'] = '25M';
+      $config['max_message_size'] =  '${cfg.maxAttachmentSize}';
       $config['plugins'] = [${concatMapStringsSep "," (p: "'${p}'") cfg.plugins}];
       $config['des_key'] = file_get_contents('/var/lib/roundcube/des_key');
       $config['mime_types'] = '${pkgs.nginx}/conf/mime.types';
@@ -172,8 +184,8 @@ in
       phpOptions = ''
         error_log = 'stderr'
         log_errors = on
-        post_max_size = 25M
-        upload_max_filesize = 25M
+        post_max_size = ${cfg.maxAttachmentSize}
+        upload_max_filesize = ${cfg.maxAttachmentSize}
       '';
       settings = mapAttrs (name: mkDefault) {
         "listen.owner" = "nginx";
diff --git a/nixos/modules/services/misc/gitea.nix b/nixos/modules/services/misc/gitea.nix
index f8bcedc94fe2c..af80e99746bef 100644
--- a/nixos/modules/services/misc/gitea.nix
+++ b/nixos/modules/services/misc/gitea.nix
@@ -162,6 +162,45 @@ in
             <manvolnum>7</manvolnum></citerefentry>.
           '';
         };
+
+        backupDir = mkOption {
+          type = types.str;
+          default = "${cfg.stateDir}/dump";
+          description = "Path to the dump files.";
+        };
+      };
+
+      ssh = {
+        enable = mkOption {
+          type = types.bool;
+          default = true;
+          description = "Enable external SSH feature.";
+        };
+
+        clonePort = mkOption {
+          type = types.int;
+          default = 22;
+          example = 2222;
+          description = ''
+            SSH port displayed in clone URL.
+            The option is required to configure a service when the external visible port
+            differs from the local listening port i.e. if port forwarding is used.
+          '';
+        };
+      };
+
+      lfs = {
+        enable = mkOption {
+          type = types.bool;
+          default = false;
+          description = "Enables git-lfs support.";
+        };
+
+        contentDir = mkOption {
+          type = types.str;
+          default = "${cfg.stateDir}/data/lfs";
+          description = "Where to store LFS files.";
+        };
       };
 
       appName = mkOption {
@@ -200,6 +239,12 @@ in
         description = "HTTP listen port.";
       };
 
+      enableUnixSocket = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Configure Gitea to listen on a unix socket instead of the default TCP port.";
+      };
+
       cookieSecure = mkOption {
         type = types.bool;
         default = false;
@@ -300,14 +345,34 @@ in
         ROOT = cfg.repositoryRoot;
       };
 
-      server = {
-        DOMAIN = cfg.domain;
-        HTTP_ADDR = cfg.httpAddress;
-        HTTP_PORT = cfg.httpPort;
-        ROOT_URL = cfg.rootUrl;
-        STATIC_ROOT_PATH = cfg.staticRootPath;
-        LFS_JWT_SECRET = "#jwtsecret#";
-      };
+      server = mkMerge [
+        {
+          DOMAIN = cfg.domain;
+          STATIC_ROOT_PATH = cfg.staticRootPath;
+          LFS_JWT_SECRET = "#jwtsecret#";
+          ROOT_URL = cfg.rootUrl;
+        }
+        (mkIf cfg.enableUnixSocket {
+          PROTOCOL = "unix";
+          HTTP_ADDR = "/run/gitea/gitea.sock";
+        })
+        (mkIf (!cfg.enableUnixSocket) {
+          HTTP_ADDR = cfg.httpAddress;
+          HTTP_PORT = cfg.httpPort;
+        })
+        (mkIf cfg.ssh.enable {
+          DISABLE_SSH = false;
+          SSH_PORT = cfg.ssh.clonePort;
+        })
+        (mkIf (!cfg.ssh.enable) {
+          DISABLE_SSH = true;
+        })
+        (mkIf cfg.lfs.enable {
+          LFS_START_SERVER = true;
+          LFS_CONTENT_PATH = cfg.lfs.contentDir;
+        })
+
+      ];
 
       session = {
         COOKIE_NAME = "session";
@@ -357,12 +422,26 @@ in
     };
 
     systemd.tmpfiles.rules = [
-      "d '${cfg.stateDir}' - ${cfg.user} gitea - -"
-      "d '${cfg.stateDir}/conf' - ${cfg.user} gitea - -"
-      "d '${cfg.stateDir}/custom' - ${cfg.user} gitea - -"
-      "d '${cfg.stateDir}/custom/conf' - ${cfg.user} gitea - -"
-      "d '${cfg.stateDir}/log' - ${cfg.user} gitea - -"
-      "d '${cfg.repositoryRoot}' - ${cfg.user} gitea - -"
+      "d '${cfg.dump.backupDir}' 0750 ${cfg.user} gitea - -"
+      "z '${cfg.dump.backupDir}' 0750 ${cfg.user} gitea - -"
+      "Z '${cfg.dump.backupDir}' - ${cfg.user} gitea - -"
+      "d '${cfg.lfs.contentDir}' 0750 ${cfg.user} gitea - -"
+      "z '${cfg.lfs.contentDir}' 0750 ${cfg.user} gitea - -"
+      "Z '${cfg.lfs.contentDir}' - ${cfg.user} gitea - -"
+      "d '${cfg.repositoryRoot}' 0750 ${cfg.user} gitea - -"
+      "z '${cfg.repositoryRoot}' 0750 ${cfg.user} gitea - -"
+      "Z '${cfg.repositoryRoot}' - ${cfg.user} gitea - -"
+      "d '${cfg.stateDir}' 0750 ${cfg.user} gitea - -"
+      "d '${cfg.stateDir}/conf' 0750 ${cfg.user} gitea - -"
+      "d '${cfg.stateDir}/custom' 0750 ${cfg.user} gitea - -"
+      "d '${cfg.stateDir}/custom/conf' 0750 ${cfg.user} gitea - -"
+      "d '${cfg.stateDir}/log' 0750 ${cfg.user} gitea - -"
+      "z '${cfg.stateDir}' 0750 ${cfg.user} gitea - -"
+      "z '${cfg.stateDir}/.ssh' 0700 ${cfg.user} gitea - -"
+      "z '${cfg.stateDir}/conf' 0750 ${cfg.user} gitea - -"
+      "z '${cfg.stateDir}/custom' 0750 ${cfg.user} gitea - -"
+      "z '${cfg.stateDir}/custom/conf' 0750 ${cfg.user} gitea - -"
+      "z '${cfg.stateDir}/log' 0750 ${cfg.user} gitea - -"
       "Z '${cfg.stateDir}' - ${cfg.user} gitea - -"
 
       # If we have a folder or symlink with gitea locales, remove it
@@ -431,28 +510,39 @@ in
         User = cfg.user;
         Group = "gitea";
         WorkingDirectory = cfg.stateDir;
-        ExecStart = "${gitea}/bin/gitea web";
+        ExecStart = "${gitea}/bin/gitea web --pid /run/gitea/gitea.pid";
         Restart = "always";
-
-        # Filesystem
+        # Runtime directory and mode
+        RuntimeDirectory = "gitea";
+        RuntimeDirectoryMode = "0755";
+        # Access write directories
+        ReadWritePaths = [ cfg.dump.backupDir cfg.repositoryRoot cfg.stateDir cfg.lfs.contentDir ];
+        UMask = "0027";
+        # Capabilities
+        CapabilityBoundingSet = "";
+        # Security
+        NoNewPrivileges = true;
+        # Sandboxing
+        ProtectSystem = "strict";
         ProtectHome = true;
+        PrivateTmp = true;
         PrivateDevices = true;
+        PrivateUsers = true;
+        ProtectHostname = true;
+        ProtectClock = true;
         ProtectKernelTunables = true;
         ProtectKernelModules = true;
+        ProtectKernelLogs = true;
         ProtectControlGroups = true;
-        ReadWritePaths = cfg.stateDir;
-        # Caps
-        CapabilityBoundingSet = "";
-        NoNewPrivileges = true;
-        # Misc.
+        RestrictAddressFamilies = [ "AF_UNIX AF_INET AF_INET6" ];
         LockPersonality = true;
+        MemoryDenyWriteExecute = true;
         RestrictRealtime = true;
+        RestrictSUIDSGID = true;
         PrivateMounts = true;
-        PrivateUsers = true;
-        MemoryDenyWriteExecute = true;
-        SystemCallFilter = "~@clock @cpu-emulation @debug @keyring @memlock @module @mount @obsolete @raw-io @reboot @resources @setuid @swap";
+        # System Call Filtering
         SystemCallArchitectures = "native";
-        RestrictAddressFamilies = "AF_UNIX AF_INET AF_INET6";
+        SystemCallFilter = "~@clock @cpu-emulation @debug @keyring @memlock @module @mount @obsolete @raw-io @reboot @resources @setuid @swap";
       };
 
       environment = {
@@ -504,7 +594,7 @@ in
          Type = "oneshot";
          User = cfg.user;
          ExecStart = "${gitea}/bin/gitea dump";
-         WorkingDirectory = cfg.stateDir;
+         WorkingDirectory = cfg.dump.backupDir;
        };
     };
 
diff --git a/nixos/modules/services/misc/gitlab.nix b/nixos/modules/services/misc/gitlab.nix
index 1ada131bd7ba9..425f35f37cb6d 100644
--- a/nixos/modules/services/misc/gitlab.nix
+++ b/nixos/modules/services/misc/gitlab.nix
@@ -54,7 +54,7 @@ let
     '') gitlabConfig.production.repositories.storages))}
   '';
 
-  gitlabShellConfig = {
+  gitlabShellConfig = flip recursiveUpdate cfg.extraShellConfig {
     user = cfg.user;
     gitlab_url = "http+unix://${pathUrlQuote gitlabSocket}";
     http_settings.self_signed_cert = false;
@@ -71,7 +71,7 @@ let
     };
   };
 
-  redisConfig.production.url = "redis://localhost:6379/";
+  redisConfig.production.url = cfg.redisUrl;
 
   gitlabConfig = {
     # These are the default settings from config/gitlab.example.yml
@@ -311,6 +311,12 @@ in {
         description = "Extra configuration in config/database.yml.";
       };
 
+      redisUrl = mkOption {
+        type = types.str;
+        default = "redis://localhost:6379/";
+        description = "Redis URL for all GitLab services except gitlab-shell";
+      };
+
       extraGitlabRb = mkOption {
         type = types.str;
         default = "";
@@ -511,6 +517,12 @@ in {
         '';
       };
 
+      extraShellConfig = mkOption {
+        type = types.attrs;
+        default = {};
+        description = "Extra configuration to merge into shell-config.yml";
+      };
+
       extraConfig = mkOption {
         type = types.attrs;
         default = {};
@@ -612,26 +624,38 @@ in {
       enable = true;
       ensureUsers = singleton { name = cfg.databaseUsername; };
     };
+
     # The postgresql module doesn't currently support concepts like
     # objects owners and extensions; for now we tack on what's needed
     # here.
-    systemd.services.postgresql.postStart = mkAfter (optionalString databaseActuallyCreateLocally ''
-      set -eu
-
-      $PSQL -tAc "SELECT 1 FROM pg_database WHERE datname = '${cfg.databaseName}'" | grep -q 1 || $PSQL -tAc 'CREATE DATABASE "${cfg.databaseName}" OWNER "${cfg.databaseUsername}"'
-      current_owner=$($PSQL -tAc "SELECT pg_catalog.pg_get_userbyid(datdba) FROM pg_catalog.pg_database WHERE datname = '${cfg.databaseName}'")
-      if [[ "$current_owner" != "${cfg.databaseUsername}" ]]; then
-          $PSQL -tAc 'ALTER DATABASE "${cfg.databaseName}" OWNER TO "${cfg.databaseUsername}"'
-          if [[ -e "${config.services.postgresql.dataDir}/.reassigning_${cfg.databaseName}" ]]; then
-              echo "Reassigning ownership of database ${cfg.databaseName} to user ${cfg.databaseUsername} failed on last boot. Failing..."
-              exit 1
-          fi
-          touch "${config.services.postgresql.dataDir}/.reassigning_${cfg.databaseName}"
-          $PSQL "${cfg.databaseName}" -tAc "REASSIGN OWNED BY \"$current_owner\" TO \"${cfg.databaseUsername}\""
-          rm "${config.services.postgresql.dataDir}/.reassigning_${cfg.databaseName}"
-      fi
-      $PSQL '${cfg.databaseName}' -tAc "CREATE EXTENSION IF NOT EXISTS pg_trgm"
-    '');
+    systemd.services.gitlab-postgresql = let pgsql = config.services.postgresql; in mkIf databaseActuallyCreateLocally {
+      after = [ "postgresql.service" ];
+      wantedBy = [ "multi-user.target" ];
+      path = [ pgsql.package ];
+      script = ''
+        set -eu
+
+        PSQL="${pkgs.utillinux}/bin/runuser -u ${pgsql.superUser} -- psql --port=${toString pgsql.port}"
+
+        $PSQL -tAc "SELECT 1 FROM pg_database WHERE datname = '${cfg.databaseName}'" | grep -q 1 || $PSQL -tAc 'CREATE DATABASE "${cfg.databaseName}" OWNER "${cfg.databaseUsername}"'
+        current_owner=$($PSQL -tAc "SELECT pg_catalog.pg_get_userbyid(datdba) FROM pg_catalog.pg_database WHERE datname = '${cfg.databaseName}'")
+        if [[ "$current_owner" != "${cfg.databaseUsername}" ]]; then
+            $PSQL -tAc 'ALTER DATABASE "${cfg.databaseName}" OWNER TO "${cfg.databaseUsername}"'
+            if [[ -e "${config.services.postgresql.dataDir}/.reassigning_${cfg.databaseName}" ]]; then
+                echo "Reassigning ownership of database ${cfg.databaseName} to user ${cfg.databaseUsername} failed on last boot. Failing..."
+                exit 1
+            fi
+            touch "${config.services.postgresql.dataDir}/.reassigning_${cfg.databaseName}"
+            $PSQL "${cfg.databaseName}" -tAc "REASSIGN OWNED BY \"$current_owner\" TO \"${cfg.databaseUsername}\""
+            rm "${config.services.postgresql.dataDir}/.reassigning_${cfg.databaseName}"
+        fi
+        $PSQL '${cfg.databaseName}' -tAc "CREATE EXTENSION IF NOT EXISTS pg_trgm"
+      '';
+
+      serviceConfig = {
+        Type = "oneshot";
+      };
+    };
 
     # Use postfix to send out mails.
     services.postfix.enable = mkDefault true;
@@ -678,7 +702,6 @@ in {
       "L+ /run/gitlab/shell-config.yml - - - - ${pkgs.writeText "config.yml" (builtins.toJSON gitlabShellConfig)}"
 
       "L+ ${cfg.statePath}/config/unicorn.rb - - - - ${./defaultUnicornConfig.rb}"
-      "L+ ${cfg.statePath}/config/initializers/extra-gitlab.rb - - - - ${extraGitlabRb}"
     ];
 
     systemd.services.gitlab-sidekiq = {
@@ -704,7 +727,7 @@ in {
         TimeoutSec = "infinity";
         Restart = "on-failure";
         WorkingDirectory = "${cfg.packages.gitlab}/share/gitlab";
-        ExecStart="${cfg.packages.gitlab.rubyEnv}/bin/sidekiq -C \"${cfg.packages.gitlab}/share/gitlab/config/sidekiq_queues.yml\" -e production -P ${cfg.statePath}/tmp/sidekiq.pid";
+        ExecStart="${cfg.packages.gitlab.rubyEnv}/bin/sidekiq -C \"${cfg.packages.gitlab}/share/gitlab/config/sidekiq_queues.yml\" -e production";
       };
     };
 
@@ -761,7 +784,7 @@ in {
     };
 
     systemd.services.gitlab = {
-      after = [ "gitlab-workhorse.service" "gitaly.service" "network.target" "postgresql.service" "redis.service" ];
+      after = [ "gitlab-workhorse.service" "gitaly.service" "network.target" "gitlab-postgresql.service" "redis.service" ];
       requires = [ "gitlab-sidekiq.service" ];
       wantedBy = [ "multi-user.target" ];
       environment = gitlabEnv;
@@ -798,6 +821,7 @@ in {
             rm -f ${cfg.statePath}/lib
             cp -rf --no-preserve=mode ${cfg.packages.gitlab}/share/gitlab/config.dist/* ${cfg.statePath}/config
             cp -rf --no-preserve=mode ${cfg.packages.gitlab}/share/gitlab/db/* ${cfg.statePath}/db
+            ln -sf ${extraGitlabRb} ${cfg.statePath}/config/initializers/extra-gitlab.rb
 
             ${cfg.packages.gitlab-shell}/bin/install
 
diff --git a/nixos/modules/services/misc/gitolite.nix b/nixos/modules/services/misc/gitolite.nix
index cc69f81bbcc4b..59cbdac319c8b 100644
--- a/nixos/modules/services/misc/gitolite.nix
+++ b/nixos/modules/services/misc/gitolite.nix
@@ -27,7 +27,10 @@ in
         type = types.str;
         default = "/var/lib/gitolite";
         description = ''
-          Gitolite home directory (used to store all the repositories).
+          The gitolite home directory used to store all repositories. If left as the default value
+          this directory will automatically be created before the gitolite server starts, otherwise
+          the sysadmin is responsible for ensuring the directory exists with appropriate ownership
+          and permissions.
         '';
       };
 
@@ -149,14 +152,6 @@ in
     };
     users.groups.${cfg.group}.gid = config.ids.gids.gitolite;
 
-    systemd.tmpfiles.rules = [
-      "d '${cfg.dataDir}' 0750 ${cfg.user} ${cfg.group} - -"
-      "d '${cfg.dataDir}'/.gitolite - ${cfg.user} ${cfg.group} - -"
-      "d '${cfg.dataDir}'/.gitolite/logs - ${cfg.user} ${cfg.group} - -"
-
-      "Z ${cfg.dataDir} 0750 ${cfg.user} ${cfg.group} - -"
-    ];
-
     systemd.services.gitolite-init = {
       description = "Gitolite initialization";
       wantedBy    = [ "multi-user.target" ];
@@ -167,13 +162,19 @@ in
         GITOLITE_RC_DEFAULT = "${rcDir}/gitolite.rc.default";
       };
 
-      serviceConfig = {
-        Type = "oneshot";
-        User = cfg.user;
-        Group = cfg.group;
-        WorkingDirectory = "~";
-        RemainAfterExit = true;
-      };
+      serviceConfig = mkMerge [
+        (mkIf (cfg.dataDir == "/var/lib/gitolite") {
+          StateDirectory = "gitolite gitolite/.gitolite gitolite/.gitolite/logs";
+          StateDirectoryMode = "0750";
+        })
+        {
+          Type = "oneshot";
+          User = cfg.user;
+          Group = cfg.group;
+          WorkingDirectory = "~";
+          RemainAfterExit = true;
+        }
+      ];
 
       path = [ pkgs.gitolite pkgs.git pkgs.perl pkgs.bash pkgs.diffutils config.programs.ssh.package ];
       script =
diff --git a/nixos/modules/services/misc/gollum.nix b/nixos/modules/services/misc/gollum.nix
index f4a9c72b1545e..0c9c7548305ba 100644
--- a/nixos/modules/services/misc/gollum.nix
+++ b/nixos/modules/services/misc/gollum.nix
@@ -50,6 +50,12 @@ in
       description = "Parse and interpret emoji tags";
     };
 
+    h1-title = mkOption {
+      type = types.bool;
+      default = false;
+      description = "Use the first h1 as page title";
+    };
+
     branch = mkOption {
       type = types.str;
       default = "master";
@@ -98,10 +104,11 @@ in
           ${pkgs.gollum}/bin/gollum \
             --port ${toString cfg.port} \
             --host ${cfg.address} \
-            --config ${builtins.toFile "gollum-config.rb" cfg.extraConfig} \
+            --config ${pkgs.writeText "gollum-config.rb" cfg.extraConfig} \
             --ref ${cfg.branch} \
             ${optionalString cfg.mathjax "--mathjax"} \
             ${optionalString cfg.emoji "--emoji"} \
+            ${optionalString cfg.h1-title "--h1-title"} \
             ${optionalString (cfg.allowUploads != null) "--allow-uploads ${cfg.allowUploads}"} \
             ${cfg.stateDir}
         '';
diff --git a/nixos/modules/services/misc/jellyfin.nix b/nixos/modules/services/misc/jellyfin.nix
index 6ecdfb57dc358..0493dadea94ea 100644
--- a/nixos/modules/services/misc/jellyfin.nix
+++ b/nixos/modules/services/misc/jellyfin.nix
@@ -16,6 +16,14 @@ in
         description = "User account under which Jellyfin runs.";
       };
 
+      package = mkOption {
+        type = types.package;
+        example = literalExample "pkgs.jellyfin";
+        description = ''
+          Jellyfin package to use.
+        '';
+      };
+
       group = mkOption {
         type = types.str;
         default = "jellyfin";
@@ -35,11 +43,16 @@ in
         Group = cfg.group;
         StateDirectory = "jellyfin";
         CacheDirectory = "jellyfin";
-        ExecStart = "${pkgs.jellyfin}/bin/jellyfin --datadir '/var/lib/${StateDirectory}' --cachedir '/var/cache/${CacheDirectory}'";
+        ExecStart = "${cfg.package}/bin/jellyfin --datadir '/var/lib/${StateDirectory}' --cachedir '/var/cache/${CacheDirectory}'";
         Restart = "on-failure";
       };
     };
 
+    services.jellyfin.package = mkDefault (
+      if versionAtLeast config.system.stateVersion "20.09" then pkgs.jellyfin
+        else pkgs.jellyfin_10_5
+    );
+
     users.users = mkIf (cfg.user == "jellyfin") {
       jellyfin = {
         group = cfg.group;
diff --git a/nixos/modules/services/misc/mathics.nix b/nixos/modules/services/misc/mathics.nix
deleted file mode 100644
index c588a30d76cd1..0000000000000
--- a/nixos/modules/services/misc/mathics.nix
+++ /dev/null
@@ -1,54 +0,0 @@
-{ pkgs, lib, config, ... }:
-
-with lib;
-
-let
-  cfg = config.services.mathics;
-
-in {
-  options = {
-    services.mathics = {
-      enable = mkEnableOption "Mathics notebook service";
-
-      external = mkOption {
-        type = types.bool;
-        default = false;
-        description = "Listen on all interfaces, rather than just localhost?";
-      };
-
-      port = mkOption {
-        type = types.int;
-        default = 8000;
-        description = "TCP port to listen on.";
-      };
-    };
-  };
-
-  config = mkIf cfg.enable {
-
-    users.users.mathics = {
-      group = config.users.groups.mathics.name;
-      description = "Mathics user";
-      home = "/var/lib/mathics";
-      createHome = true;
-      uid = config.ids.uids.mathics;
-    };
-
-    users.groups.mathics.gid = config.ids.gids.mathics;
-
-    systemd.services.mathics = {
-      description = "Mathics notebook server";
-      wantedBy = [ "multi-user.target" ];
-      after = [ "network.target" ];
-      serviceConfig = {
-        User = config.users.users.mathics.name;
-        Group = config.users.groups.mathics.name;
-        ExecStart = concatStringsSep " " [
-          "${pkgs.mathics}/bin/mathicsserver"
-          "--port" (toString cfg.port)
-          (if cfg.external then "--external" else "")
-        ];
-      };
-    };
-  };
-}
diff --git a/nixos/modules/services/misc/matrix-synapse.nix b/nixos/modules/services/misc/matrix-synapse.nix
index e982eb16fa70f..3eb1073387fec 100644
--- a/nixos/modules/services/misc/matrix-synapse.nix
+++ b/nixos/modules/services/misc/matrix-synapse.nix
@@ -675,7 +675,7 @@ in {
       }
     ];
 
-    users.users.matrix-synapse = { 
+    users.users.matrix-synapse = {
         group = "matrix-synapse";
         home = cfg.dataDir;
         createHome = true;
diff --git a/nixos/modules/services/misc/matrix-synapse.xml b/nixos/modules/services/misc/matrix-synapse.xml
index 2f2ac27eeb9d0..fbfa838b168bf 100644
--- a/nixos/modules/services/misc/matrix-synapse.xml
+++ b/nixos/modules/services/misc/matrix-synapse.xml
@@ -14,9 +14,9 @@
  <para>
   This chapter will show you how to set up your own, self-hosted Matrix
   homeserver using the Synapse reference homeserver, and how to serve your own
-  copy of the Riot web client. See the
+  copy of the Element web client. See the
   <link xlink:href="https://matrix.org/docs/projects/try-matrix-now.html">Try
-  Matrix Now!</link> overview page for links to Riot Apps for Android and iOS,
+  Matrix Now!</link> overview page for links to Element Apps for Android and iOS,
   desktop clients, as well as bridges to other networks and other projects
   around Matrix.
  </para>
@@ -84,7 +84,7 @@ in {
               "m.homeserver" =  { "base_url" = "https://${fqdn}"; };
               "m.identity_server" =  { "base_url" = "https://vector.im"; };
             };
-          # ACAO required to allow riot-web on any URL to request this json file
+          # ACAO required to allow element-web on any URL to request this json file
           in ''
             add_header Content-Type application/json;
             add_header Access-Control-Allow-Origin *;
@@ -98,7 +98,7 @@ in {
         <link linkend="opt-services.nginx.virtualHosts._name_.forceSSL">forceSSL</link> = true;
 
         # Or do a redirect instead of the 404, or whatever is appropriate for you.
-        # But do not put a Matrix Web client here! See the Riot Web section below.
+        # But do not put a Matrix Web client here! See the Element web section below.
         <link linkend="opt-services.nginx.virtualHosts._name_.locations._name_.extraConfig">locations."/".extraConfig</link> = ''
           return 404;
         '';
@@ -171,17 +171,19 @@ Success!
    option until a better solution for NixOS is in place.
   </para>
  </section>
- <section xml:id="module-services-matrix-riot-web">
-  <title>Riot Web Client</title>
+ <section xml:id="module-services-matrix-element-web">
+  <title>Element (formerly known as Riot) Web Client</title>
 
   <para>
-   <link xlink:href="https://github.com/vector-im/riot-web/">Riot Web</link> is
+   <link xlink:href="https://github.com/vector-im/riot-web/">Element Web</link> is
    the reference web client for Matrix and developed by the core team at
-   matrix.org. The following snippet can be optionally added to the code before
+   matrix.org. Element was formerly known as Riot.im, see the
+   <link xlink:href="https://element.io/blog/welcome-to-element/">Element introductory blog post</link>
+   for more information. The following snippet can be optionally added to the code before
    to complete the synapse installation with a web client served at
-   <code>https://riot.myhostname.example.org</code> and
-   <code>https://riot.example.org</code>. Alternatively, you can use the hosted
-   copy at <link xlink:href="https://riot.im/app">https://riot.im/app</link>,
+   <code>https://element.myhostname.example.org</code> and
+   <code>https://element.example.org</code>. Alternatively, you can use the hosted
+   copy at <link xlink:href="https://app.element.io/">https://app.element.io/</link>,
    or use other web clients or native client applications. Due to the
    <literal>/.well-known</literal> urls set up done above, many clients should
    fill in the required connection details automatically when you enter your
@@ -191,14 +193,14 @@ Success!
    featureset.
 <programlisting>
 {
-  services.nginx.virtualHosts."riot.${fqdn}" = {
+  services.nginx.virtualHosts."element.${fqdn}" = {
     <link linkend="opt-services.nginx.virtualHosts._name_.enableACME">enableACME</link> = true;
     <link linkend="opt-services.nginx.virtualHosts._name_.forceSSL">forceSSL</link> = true;
     <link linkend="opt-services.nginx.virtualHosts._name_.serverAliases">serverAliases</link> = [
-      "riot.${config.networking.domain}"
+      "element.${config.networking.domain}"
     ];
 
-    <link linkend="opt-services.nginx.virtualHosts._name_.root">root</link> = pkgs.riot-web.override {
+    <link linkend="opt-services.nginx.virtualHosts._name_.root">root</link> = pkgs.element-web.override {
       conf = {
         default_server_config."m.homeserver" = {
           "base_url" = "${config.networking.domain}";
@@ -212,13 +214,13 @@ Success!
   </para>
 
   <para>
-   Note that the Riot developers do not recommend running Riot and your Matrix
+   Note that the Element developers do not recommend running Element and your Matrix
    homeserver on the same fully-qualified domain name for security reasons. In
    the example, this means that you should not reuse the
-   <literal>myhostname.example.org</literal> virtualHost to also serve Riot,
+   <literal>myhostname.example.org</literal> virtualHost to also serve Element,
    but instead serve it on a different subdomain, like
-   <literal>riot.example.org</literal> in the example. See the
-   <link xlink:href="https://github.com/vector-im/riot-web#important-security-note">Riot
+   <literal>element.example.org</literal> in the example. See the
+   <link xlink:href="https://github.com/vector-im/riot-web#important-security-note">Element
    Important Security Notes</link> for more information on this subject.
   </para>
  </section>
diff --git a/nixos/modules/services/misc/mautrix-telegram.nix b/nixos/modules/services/misc/mautrix-telegram.nix
index 78a42fbb574b4..c5e8a5b85ec2d 100644
--- a/nixos/modules/services/misc/mautrix-telegram.nix
+++ b/nixos/modules/services/misc/mautrix-telegram.nix
@@ -125,7 +125,7 @@ in {
         if [ ! -f '${registrationFile}' ]; then
           ${pkgs.mautrix-telegram}/bin/mautrix-telegram \
             --generate-registration \
-            --base-config='${pkgs.mautrix-telegram}/example-config.yaml' \
+            --base-config='${pkgs.mautrix-telegram}/${pkgs.mautrix-telegram.pythonModule.sitePackages}/mautrix_telegram/example-config.yaml' \
             --config='${settingsFile}' \
             --registration='${registrationFile}'
         fi
diff --git a/nixos/modules/services/misc/mesos-master.nix b/nixos/modules/services/misc/mesos-master.nix
deleted file mode 100644
index 572a9847e46c6..0000000000000
--- a/nixos/modules/services/misc/mesos-master.nix
+++ /dev/null
@@ -1,125 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-with lib;
-
-let
-  cfg = config.services.mesos.master;
-
-in {
-
-  options.services.mesos = {
-
-    master = {
-      enable = mkOption {
-        description = "Whether to enable the Mesos Master.";
-        default = false;
-        type = types.bool;
-      };
-
-      ip = mkOption {
-        description = "IP address to listen on.";
-        default = "0.0.0.0";
-        type = types.str;
-      };
-
-      port = mkOption {
-        description = "Mesos Master port";
-        default = 5050;
-        type = types.int;
-      };
-
-      advertiseIp = mkOption {
-        description = "IP address advertised to reach this master.";
-        default = null;
-        type = types.nullOr types.str;
-      };
-
-      advertisePort = mkOption {
-        description = "Port advertised to reach this Mesos master.";
-        default = null;
-        type = types.nullOr types.int;
-      };
-
-      zk = mkOption {
-        description = ''
-          ZooKeeper URL (used for leader election amongst masters).
-          May be one of:
-            zk://host1:port1,host2:port2,.../mesos
-            zk://username:password@host1:port1,host2:port2,.../mesos
-        '';
-        type = types.str;
-      };
-
-      workDir = mkOption {
-        description = "The Mesos work directory.";
-        default = "/var/lib/mesos/master";
-        type = types.str;
-      };
-
-      extraCmdLineOptions = mkOption {
-        description = ''
-          Extra command line options for Mesos Master.
-
-          See https://mesos.apache.org/documentation/latest/configuration/
-        '';
-        default = [ "" ];
-        type = types.listOf types.str;
-        example = [ "--credentials=VALUE" ];
-      };
-
-      quorum = mkOption {
-        description = ''
-          The size of the quorum of replicas when using 'replicated_log' based
-          registry. It is imperative to set this value to be a majority of
-          masters i.e., quorum > (number of masters)/2.
-
-          If 0 will fall back to --registry=in_memory.
-        '';
-        default = 0;
-        type = types.int;
-      };
-
-      logLevel = mkOption {
-        description = ''
-          The logging level used. Possible values:
-            'INFO', 'WARNING', 'ERROR'
-        '';
-        default = "INFO";
-        type = types.str;
-      };
-
-    };
-
-
-  };
-
-
-  config = mkIf cfg.enable {
-    systemd.tmpfiles.rules = [
-      "d '${cfg.workDir}' 0700 - - - -"
-    ];
-    systemd.services.mesos-master = {
-      description = "Mesos Master";
-      wantedBy = [ "multi-user.target" ];
-      after = [ "network.target" ];
-      serviceConfig = {
-        ExecStart = ''
-          ${pkgs.mesos}/bin/mesos-master \
-            --ip=${cfg.ip} \
-            --port=${toString cfg.port} \
-            ${optionalString (cfg.advertiseIp != null) "--advertise_ip=${cfg.advertiseIp}"} \
-            ${optionalString (cfg.advertisePort  != null) "--advertise_port=${toString cfg.advertisePort}"} \
-            ${if cfg.quorum == 0
-              then "--registry=in_memory"
-              else "--zk=${cfg.zk} --registry=replicated_log --quorum=${toString cfg.quorum}"} \
-            --work_dir=${cfg.workDir} \
-            --logging_level=${cfg.logLevel} \
-            ${toString cfg.extraCmdLineOptions}
-        '';
-        Restart = "on-failure";
-      };
-    };
-  };
-
-}
-
diff --git a/nixos/modules/services/misc/mesos-slave.nix b/nixos/modules/services/misc/mesos-slave.nix
deleted file mode 100644
index 170065d0065e0..0000000000000
--- a/nixos/modules/services/misc/mesos-slave.nix
+++ /dev/null
@@ -1,220 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-with lib;
-
-let
-  cfg = config.services.mesos.slave;
-
-  mkAttributes =
-    attrs: concatStringsSep ";" (mapAttrsToList
-                                   (k: v: "${k}:${v}")
-                                   (filterAttrs (k: v: v != null) attrs));
-  attribsArg = optionalString (cfg.attributes != {})
-                              "--attributes=${mkAttributes cfg.attributes}";
-
-  containerizersArg = concatStringsSep "," (
-    lib.unique (
-      cfg.containerizers ++ (optional cfg.withDocker "docker")
-    )
-  );
-
-  imageProvidersArg = concatStringsSep "," (
-    lib.unique (
-      cfg.imageProviders ++ (optional cfg.withDocker "docker")
-    )
-  );
-
-  isolationArg = concatStringsSep "," (
-    lib.unique (
-      cfg.isolation ++ (optionals cfg.withDocker [ "filesystem/linux" "docker/runtime"])
-    )
-  );
-
-in {
-
-  options.services.mesos = {
-    slave = {
-      enable = mkOption {
-        description = "Whether to enable the Mesos Slave.";
-        default = false;
-        type = types.bool;
-      };
-
-      ip = mkOption {
-        description = "IP address to listen on.";
-        default = "0.0.0.0";
-        type = types.str;
-      };
-
-      port = mkOption {
-        description = "Port to listen on.";
-        default = 5051;
-        type = types.int;
-      };
-
-      advertiseIp = mkOption {
-        description = "IP address advertised to reach this agent.";
-        default = null;
-        type = types.nullOr types.str;
-      };
-
-      advertisePort = mkOption {
-        description = "Port advertised to reach this agent.";
-        default = null;
-        type = types.nullOr types.int;
-      };
-
-      containerizers = mkOption {
-        description = ''
-          List of containerizer implementations to compose in order to provide
-          containerization. Available options are mesos and docker.
-          The order the containerizers are specified is the order they are tried.
-        '';
-        default = [ "mesos" ];
-        type = types.listOf types.str;
-      };
-
-      imageProviders = mkOption {
-        description = "List of supported image providers, e.g., APPC,DOCKER.";
-        default = [ ];
-        type = types.listOf types.str;
-      };
-
-      imageProvisionerBackend = mkOption {
-        description = ''
-          Strategy for provisioning container rootfs from images,
-          e.g., aufs, bind, copy, overlay.
-        '';
-        default = "copy";
-        type = types.str;
-      };
-
-      isolation = mkOption {
-        description = ''
-          Isolation mechanisms to use, e.g., posix/cpu,posix/mem, or
-          cgroups/cpu,cgroups/mem, or network/port_mapping, or `gpu/nvidia` for nvidia
-          specific gpu isolation.
-        '';
-        default = [ "posix/cpu" "posix/mem" ];
-        type = types.listOf types.str;
-      };
-
-      master = mkOption {
-        description = ''
-          May be one of:
-            zk://host1:port1,host2:port2,.../path
-            zk://username:password@host1:port1,host2:port2,.../path
-        '';
-        type = types.str;
-      };
-
-      withHadoop = mkOption {
-        description = "Add the HADOOP_HOME to the slave.";
-        default = false;
-        type = types.bool;
-      };
-
-      withDocker = mkOption {
-        description = "Enable the docker containerizer.";
-        default = config.virtualisation.docker.enable;
-        type = types.bool;
-      };
-
-      dockerRegistry = mkOption {
-        description = ''
-          The default url for pulling Docker images.
-          It could either be a Docker registry server url,
-          or a local path in which Docker image archives are stored.
-        '';
-        default = null;
-        type = types.nullOr (types.either types.str types.path);
-      };
-
-      workDir = mkOption {
-        description = "The Mesos work directory.";
-        default = "/var/lib/mesos/slave";
-        type = types.str;
-      };
-
-      extraCmdLineOptions = mkOption {
-        description = ''
-          Extra command line options for Mesos Slave.
-
-          See https://mesos.apache.org/documentation/latest/configuration/
-        '';
-        default = [ "" ];
-        type = types.listOf types.str;
-        example = [ "--gc_delay=3days" ];
-      };
-
-      logLevel = mkOption {
-        description = ''
-          The logging level used. Possible values:
-            'INFO', 'WARNING', 'ERROR'
-        '';
-        default = "INFO";
-        type = types.str;
-      };
-
-      attributes = mkOption {
-        description = ''
-          Machine attributes for the slave instance.
-
-          Use caution when changing this; you may need to manually reset slave
-          metadata before the slave can re-register.
-        '';
-        default = {};
-        type = types.attrsOf types.str;
-        example = { rack = "aa";
-                    host = "aabc123";
-                    os = "nixos"; };
-      };
-
-      executorEnvironmentVariables = mkOption {
-        description = ''
-          The environment variables that should be passed to the executor, and thus subsequently task(s).
-        '';
-        default = {
-          PATH = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin";
-        };
-        type = types.attrsOf types.str;
-      };
-    };
-
-  };
-
-  config = mkIf cfg.enable {
-    systemd.tmpfiles.rules = [
-      "d '${cfg.workDir}' 0701 - - - -"
-    ];
-    systemd.services.mesos-slave = {
-      description = "Mesos Slave";
-      wantedBy = [ "multi-user.target" ];
-      after = [ "network.target" ] ++ optionals cfg.withDocker [ "docker.service" ] ;
-      path = [ pkgs.runtimeShellPackage ];
-      serviceConfig = {
-        ExecStart = ''
-          ${pkgs.mesos}/bin/mesos-slave \
-            --containerizers=${containerizersArg} \
-            --image_providers=${imageProvidersArg} \
-            --image_provisioner_backend=${cfg.imageProvisionerBackend} \
-            --isolation=${isolationArg} \
-            --ip=${cfg.ip} \
-            --port=${toString cfg.port} \
-            ${optionalString (cfg.advertiseIp != null) "--advertise_ip=${cfg.advertiseIp}"} \
-            ${optionalString (cfg.advertisePort  != null) "--advertise_port=${toString cfg.advertisePort}"} \
-            --master=${cfg.master} \
-            --work_dir=${cfg.workDir} \
-            --logging_level=${cfg.logLevel} \
-            ${attribsArg} \
-            ${optionalString cfg.withHadoop "--hadoop-home=${pkgs.hadoop}"} \
-            ${optionalString cfg.withDocker "--docker=${pkgs.docker}/libexec/docker/docker"} \
-            ${optionalString (cfg.dockerRegistry != null) "--docker_registry=${cfg.dockerRegistry}"} \
-            --executor_environment_variables=${lib.escapeShellArg (builtins.toJSON cfg.executorEnvironmentVariables)} \
-            ${toString cfg.extraCmdLineOptions}
-        '';
-      };
-    };
-  };
-
-}
diff --git a/nixos/modules/services/misc/nix-daemon.nix b/nixos/modules/services/misc/nix-daemon.nix
index 0b3d7f3f03c3d..924a007efc6dd 100644
--- a/nixos/modules/services/misc/nix-daemon.nix
+++ b/nixos/modules/services/misc/nix-daemon.nix
@@ -193,50 +193,111 @@ in
       };
 
       buildMachines = mkOption {
-        type = types.listOf types.attrs;
+        type = types.listOf (types.submodule ({
+          options = {
+            hostName = mkOption {
+              type = types.str;
+              example = "nixbuilder.example.org";
+              description = ''
+                The hostname of the build machine.
+              '';
+            };
+            system = mkOption {
+              type = types.nullOr types.str;
+              default = null;
+              example = "x86_64-linux";
+              description = ''
+                The system type the build machine can execute derivations on.
+                Either this attribute or <varname>systems</varname> must be
+                present, where <varname>system</varname> takes precedence if
+                both are set.
+              '';
+            };
+            systems = mkOption {
+              type = types.listOf types.str;
+              default = [];
+              example = [ "x86_64-linux" "aarch64-linux" ];
+              description = ''
+                The system types the build machine can execute derivations on.
+                Either this attribute or <varname>system</varname> must be
+                present, where <varname>system</varname> takes precedence if
+                both are set.
+              '';
+            };
+            sshUser = mkOption {
+              type = types.nullOr types.str;
+              default = null;
+              example = "builder";
+              description = ''
+                The username to log in as on the remote host. This user must be
+                able to log in and run nix commands non-interactively. It must
+                also be privileged to build derivations, so must be included in
+                <option>nix.trustedUsers</option>.
+              '';
+            };
+            sshKey = mkOption {
+              type = types.nullOr types.str;
+              default = null;
+              example = "/root/.ssh/id_buildhost_builduser";
+              description = ''
+                The path to the SSH private key with which to authenticate on
+                the build machine. The private key must not have a passphrase.
+                If null, the building user (root on NixOS machines) must have an
+                appropriate ssh configuration to log in non-interactively.
+
+                Note that for security reasons, this path must point to a file
+                in the local filesystem, *not* to the nix store.
+              '';
+            };
+            maxJobs = mkOption {
+              type = types.int;
+              default = 1;
+              description = ''
+                The number of concurrent jobs the build machine supports. The
+                build machine will enforce its own limits, but this allows hydra
+                to schedule better since there is no work-stealing between build
+                machines.
+              '';
+            };
+            speedFactor = mkOption {
+              type = types.int;
+              default = 1;
+              description = ''
+                The relative speed of this builder. This is an arbitrary integer
+                that indicates the speed of this builder, relative to other
+                builders. Higher is faster.
+              '';
+            };
+            mandatoryFeatures = mkOption {
+              type = types.listOf types.str;
+              default = [];
+              example = [ "big-parallel" ];
+              description = ''
+                A list of features mandatory for this builder. The builder will
+                be ignored for derivations that don't require all features in
+                this list. All mandatory features are automatically included in
+                <varname>supportedFeatures</varname>.
+              '';
+            };
+            supportedFeatures = mkOption {
+              type = types.listOf types.str;
+              default = [];
+              example = [ "kvm" "big-parallel" ];
+              description = ''
+                A list of features supported by this builder. The builder will
+                be ignored for derivations that require features not in this
+                list.
+              '';
+            };
+          };
+        }));
         default = [];
-        example = literalExample ''
-          [ { hostName = "voila.labs.cs.uu.nl";
-              sshUser = "nix";
-              sshKey = "/root/.ssh/id_buildfarm";
-              system = "powerpc-darwin";
-              maxJobs = 1;
-            }
-            { hostName = "linux64.example.org";
-              sshUser = "buildfarm";
-              sshKey = "/root/.ssh/id_buildfarm";
-              system = "x86_64-linux";
-              maxJobs = 2;
-              speedFactor = 2;
-              supportedFeatures = [ "kvm" ];
-              mandatoryFeatures = [ "perf" ];
-            }
-          ]
-        '';
         description = ''
-          This option lists the machines to be used if distributed
-          builds are enabled (see
-          <option>nix.distributedBuilds</option>).  Nix will perform
-          derivations on those machines via SSH by copying the inputs
-          to the Nix store on the remote machine, starting the build,
-          then copying the output back to the local Nix store.  Each
-          element of the list should be an attribute set containing
-          the machine's host name (<varname>hostname</varname>), the
-          user name to be used for the SSH connection
-          (<varname>sshUser</varname>), the Nix system type
-          (<varname>system</varname>, e.g.,
-          <literal>"i686-linux"</literal>), the maximum number of
-          jobs to be run in parallel on that machine
-          (<varname>maxJobs</varname>), the path to the SSH private
-          key to be used to connect (<varname>sshKey</varname>), a
-          list of supported features of the machine
-          (<varname>supportedFeatures</varname>) and a list of
-          mandatory features of the machine
-          (<varname>mandatoryFeatures</varname>). The SSH private key
-          should not have a passphrase, and the corresponding public
-          key should be added to
-          <filename>~<replaceable>sshUser</replaceable>/authorized_keys</filename>
-          on the remote machine.
+          This option lists the machines to be used if distributed builds are
+          enabled (see <option>nix.distributedBuilds</option>).
+          Nix will perform derivations on those machines via SSH by copying the
+          inputs to the Nix store on the remote machine, starting the build,
+          then copying the output back to the local Nix store.
         '';
       };
 
@@ -439,6 +500,13 @@ in
 
   config = {
 
+    assertions = [
+      {
+        assertion = config.nix.distributedBuilds || config.nix.buildMachines == [];
+        message = "You must set `nix.distributedBuilds = true` to use nix.buildMachines";
+      }
+    ];
+
     nix.binaryCachePublicKeys = [ "cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY=" ];
     nix.binaryCaches = [ "https://cache.nixos.org/" ];
 
@@ -461,14 +529,14 @@ in
       { enable = cfg.buildMachines != [];
         text =
           concatMapStrings (machine:
-            "${if machine ? sshUser then "${machine.sshUser}@" else ""}${machine.hostName} "
-            + machine.system or (concatStringsSep "," machine.systems)
-            + " ${machine.sshKey or "-"} ${toString machine.maxJobs or 1} "
-            + toString (machine.speedFactor or 1)
+            "${if machine.sshUser != null then "${machine.sshUser}@" else ""}${machine.hostName} "
+            + (if machine.system != null then machine.system else concatStringsSep "," machine.systems)
+            + " ${if machine.sshKey != null then machine.sshKey else "-"} ${toString machine.maxJobs} "
+            + toString (machine.speedFactor)
             + " "
-            + concatStringsSep "," (machine.mandatoryFeatures or [] ++ machine.supportedFeatures or [])
+            + concatStringsSep "," (machine.mandatoryFeatures ++ machine.supportedFeatures)
             + " "
-            + concatStringsSep "," machine.mandatoryFeatures or []
+            + concatStringsSep "," machine.mandatoryFeatures
             + "\n"
           ) cfg.buildMachines;
       };
diff --git a/nixos/modules/services/misc/pinnwand.nix b/nixos/modules/services/misc/pinnwand.nix
new file mode 100644
index 0000000000000..aa1ee5cfaa77d
--- /dev/null
+++ b/nixos/modules/services/misc/pinnwand.nix
@@ -0,0 +1,78 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.pinnwand;
+
+  format = pkgs.formats.toml {};
+  configFile = format.generate "pinnwand.toml" cfg.settings;
+in
+{
+  options.services.pinnwand = {
+    enable = mkEnableOption "Pinnwand";
+
+    port = mkOption {
+      type = types.port;
+      description = "The port to listen on.";
+      default = 8000;
+    };
+
+    settings = mkOption {
+      type = format.type;
+      description = ''
+        Your <filename>pinnwand.toml</filename> as a Nix attribute set. Look up
+        possible options in the <link xlink:href="https://github.com/supakeen/pinnwand/blob/master/pinnwand.toml-example">pinnwand.toml-example</link>.
+      '';
+      default = {
+        # https://github.com/supakeen/pinnwand/blob/master/pinnwand.toml-example
+        database_uri = "sqlite:///var/lib/pinnwand/pinnwand.db";
+        preferred_lexeres = [];
+        paste_size = 262144;
+        paste_help = ''
+          <p>Welcome to pinnwand, this site is a pastebin. It allows you to share code with others. If you write code in the text area below and press the paste button you will be given a link you can share with others so they can view your code as well.</p><p>People with the link can view your pasted code, only you can remove your paste and it expires automatically. Note that anyone could guess the URI to your paste so don't rely on it being private.</p>
+        '';
+        footer = ''
+          View <a href="//github.com/supakeen/pinnwand" target="_BLANK">source code</a>, the <a href="/removal">removal</a> or <a href="/expiry">expiry</a> stories, or read the <a href="/about">about</a> page.
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.pinnwand = {
+      description = "Pinnwannd HTTP Server";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+
+      unitConfig.Documentation = "https://pinnwand.readthedocs.io/en/latest/";
+      serviceConfig = {
+        ExecStart = "${pkgs.pinnwand}/bin/pinnwand --configuration-path ${configFile} http --port ${toString(cfg.port)}";
+        StateDirectory = "pinnwand";
+        StateDirectoryMode = "0700";
+
+        AmbientCapabilities = [];
+        CapabilityBoundingSet = "";
+        DevicePolicy = "closed";
+        DynamicUser = true;
+        LockPersonality = true;
+        MemoryDenyWriteExecute = true;
+        PrivateDevices = true;
+        PrivateUsers = true;
+        ProtectClock = true;
+        ProtectControlGroups = true;
+        ProtectKernelLogs = true;
+        ProtectHome = true;
+        ProtectHostname = true;
+        ProtectKernelModules = true;
+        ProtectKernelTunables = true;
+        RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" ];
+        RestrictNamespaces = true;
+        RestrictRealtime = true;
+        SystemCallArchitectures = "native";
+        SystemCallFilter = "@system-service";
+        UMask = "0077";
+      };
+    };
+  };
+}
diff --git a/nixos/modules/services/misc/siproxd.nix b/nixos/modules/services/misc/siproxd.nix
index ae7b27de8e706..0e87fc461d3fc 100644
--- a/nixos/modules/services/misc/siproxd.nix
+++ b/nixos/modules/services/misc/siproxd.nix
@@ -38,7 +38,7 @@ in
         type = types.bool;
         default = false;
         description = ''
-          Whether to enable the Siproxd SIP 
+          Whether to enable the Siproxd SIP
 	  proxy/masquerading daemon.
         '';
       };
@@ -111,7 +111,7 @@ in
         type = types.int;
         default = 300;
         description = ''
-          Timeout for an RTP stream. If for the specified 
+          Timeout for an RTP stream. If for the specified
           number of seconds no data is relayed on an active
           stream, it is considered dead and will be killed.
         '';
@@ -122,7 +122,7 @@ in
         default = 46;
         description = ''
           DSCP (differentiated services) value to be assigned
-          to RTP packets. Allows QOS aware routers to handle 
+          to RTP packets. Allows QOS aware routers to handle
           different types traffic with different priorities.
         '';
       };
@@ -132,7 +132,7 @@ in
         default = 0;
         description = ''
           DSCP (differentiated services) value to be assigned
-          to SIP packets. Allows QOS aware routers to handle 
+          to SIP packets. Allows QOS aware routers to handle
           different types traffic with different priorities.
         '';
       };
diff --git a/nixos/modules/services/misc/tzupdate.nix b/nixos/modules/services/misc/tzupdate.nix
index 570982ced29a7..eac1e1112a5ab 100644
--- a/nixos/modules/services/misc/tzupdate.nix
+++ b/nixos/modules/services/misc/tzupdate.nix
@@ -11,7 +11,7 @@ in {
       default = false;
       description = ''
         Enable the tzupdate timezone updating service. This provides
-        a one-shot service which can be activated with systemctl to 
+        a one-shot service which can be activated with systemctl to
         update the timezone.
       '';
     };
@@ -21,7 +21,7 @@ in {
     # We need to have imperative time zone management for this to work.
     # This will give users an error if they have set an explicit time
     # zone, which is better than silently overriding it.
-    time.timeZone = null; 
+    time.timeZone = null;
 
     # We provide a one-shot service which can be manually run. We could
     # provide a service that runs on startup, but it's tricky to get
diff --git a/nixos/modules/services/misc/zigbee2mqtt.nix b/nixos/modules/services/misc/zigbee2mqtt.nix
new file mode 100644
index 0000000000000..0957920f1a097
--- /dev/null
+++ b/nixos/modules/services/misc/zigbee2mqtt.nix
@@ -0,0 +1,98 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.zigbee2mqtt;
+
+  configJSON = pkgs.writeText "configuration.json"
+    (builtins.toJSON (recursiveUpdate defaultConfig cfg.config));
+  configFile = pkgs.runCommand "configuration.yaml" { preferLocalBuild = true; } ''
+    ${pkgs.remarshal}/bin/json2yaml -i ${configJSON} -o $out
+  '';
+
+  # the default config contains all required settings,
+  # so the service starts up without crashing.
+  defaultConfig = {
+    homeassistant = false;
+    permit_join = false;
+    mqtt = {
+      base_topic = "zigbee2mqtt";
+      server = "mqtt://localhost:1883";
+    };
+    serial.port = "/dev/ttyACM0";
+    # put device configuration into separate file because configuration.yaml
+    # is copied from the store on startup
+    devices = "devices.yaml";
+  };
+in
+{
+  meta.maintainers = with maintainers; [ sweber ];
+
+  options.services.zigbee2mqtt = {
+    enable = mkEnableOption "enable zigbee2mqtt service";
+
+    package = mkOption {
+      description = "Zigbee2mqtt package to use";
+      default = pkgs.zigbee2mqtt.override {
+        dataDir = cfg.dataDir;
+      };
+      defaultText = "pkgs.zigbee2mqtt";
+      type = types.package;
+    };
+
+    dataDir = mkOption {
+      description = "Zigbee2mqtt data directory";
+      default = "/var/lib/zigbee2mqtt";
+      type = types.path;
+    };
+
+    config = mkOption {
+      default = {};
+      type = with types; nullOr attrs;
+      example = literalExample ''
+        {
+          homeassistant = config.services.home-assistant.enable;
+          permit_join = true;
+          serial = {
+            port = "/dev/ttyACM1";
+          };
+        }
+      '';
+      description = ''
+        Your <filename>configuration.yaml</filename> as a Nix attribute set.
+      '';
+    };
+  };
+
+  config = mkIf (cfg.enable) {
+    systemd.services.zigbee2mqtt = {
+      description = "Zigbee2mqtt Service";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+      serviceConfig = {
+        ExecStart = "${cfg.package}/bin/zigbee2mqtt";
+        User = "zigbee2mqtt";
+        WorkingDirectory = cfg.dataDir;
+        Restart = "on-failure";
+        ProtectSystem = "strict";
+        ReadWritePaths = cfg.dataDir;
+        PrivateTmp = true;
+        RemoveIPC = true;
+      };
+      preStart = ''
+        cp --no-preserve=mode ${configFile} "${cfg.dataDir}/configuration.yaml"
+      '';
+    };
+
+    users.users.zigbee2mqtt = {
+      home = cfg.dataDir;
+      createHome = true;
+      group = "zigbee2mqtt";
+      extraGroups = [ "dialout" ];
+      uid = config.ids.uids.zigbee2mqtt;
+    };
+
+    users.groups.zigbee2mqtt.gid = config.ids.gids.zigbee2mqtt;
+  };
+}
diff --git a/nixos/modules/services/monitoring/cadvisor.nix b/nixos/modules/services/monitoring/cadvisor.nix
index 655a6934a266f..da051dbe4655b 100644
--- a/nixos/modules/services/monitoring/cadvisor.nix
+++ b/nixos/modules/services/monitoring/cadvisor.nix
@@ -90,7 +90,7 @@ in {
         default = [];
         description = ''
           Additional cadvisor options.
-          
+
           See <link xlink:href='https://github.com/google/cadvisor/blob/master/docs/runtime_options.md'/> for available options.
         '';
       };
diff --git a/nixos/modules/services/monitoring/dd-agent/dd-agent.nix b/nixos/modules/services/monitoring/dd-agent/dd-agent.nix
index e91717fb20547..a290dae8d4b99 100644
--- a/nixos/modules/services/monitoring/dd-agent/dd-agent.nix
+++ b/nixos/modules/services/monitoring/dd-agent/dd-agent.nix
@@ -97,11 +97,11 @@ let
         "dd-agent/conf.d/nginx.yaml".source = nginxConfig;
       }) //
       (optionalAttrs (cfg.mongoConfig != null)
-      { 
+      {
         "dd-agent/conf.d/mongo.yaml".source = mongoConfig;
       }) //
       (optionalAttrs (cfg.processConfig != null)
-      { 
+      {
         "dd-agent/conf.d/process.yaml".source = processConfig;
       }) //
       (optionalAttrs (cfg.jmxConfig != null)
diff --git a/nixos/modules/services/monitoring/do-agent.nix b/nixos/modules/services/monitoring/do-agent.nix
index 2d3fe2f797687..4dfb6236727b5 100644
--- a/nixos/modules/services/monitoring/do-agent.nix
+++ b/nixos/modules/services/monitoring/do-agent.nix
@@ -4,6 +4,7 @@ with lib;
 
 let
   cfg = config.services.do-agent;
+
 in
 {
   options.services.do-agent = {
@@ -11,23 +12,13 @@ in
   };
 
   config = mkIf cfg.enable {
-    environment.systemPackages = [ pkgs.do-agent ];
+    systemd.packages = [ pkgs.do-agent ];
 
     systemd.services.do-agent = {
-      description = "DigitalOcean Droplet Metrics Agent";
       wantedBy = [ "multi-user.target" ];
-      after = [ "network-online.target" ];
-      wants = [ "network-online.target" ];
       serviceConfig = {
-        ExecStart = "${pkgs.do-agent}/bin/do-agent --syslog";
-        Restart = "always";
-        OOMScoreAdjust = -900;
-        SyslogIdentifier = "DigitalOceanAgent";
-        PrivateTmp = "yes";
-        ProtectSystem = "full";
-        ProtectHome = "yes";
-        NoNewPrivileges = "yes";
-        DynamicUser = "yes";
+        ExecStart = [ "" "${pkgs.do-agent}/bin/do-agent --syslog" ];
+        DynamicUser = true;
       };
     };
   };
diff --git a/nixos/modules/services/monitoring/monit.nix b/nixos/modules/services/monitoring/monit.nix
index ca9352272174e..aa51b83912cec 100644
--- a/nixos/modules/services/monitoring/monit.nix
+++ b/nixos/modules/services/monitoring/monit.nix
@@ -4,19 +4,29 @@ with lib;
 
 let
   cfg = config.services.monit;
+  extraConfig = pkgs.writeText "monitConfig" cfg.extraConfig;
 in
 
 {
+  imports = [
+    (mkRenamedOptionModule [ "services" "monit" "config" ] ["services" "monit" "extraConfig" ])
+  ];
+
   options.services.monit = {
 
     enable = mkEnableOption "Monit";
 
-    config = mkOption {
+    configFiles = mkOption {
+      type = types.listOf types.path;
+      default = [];
+      description = "List of paths to be included in the monitrc file";
+    };
+
+    extraConfig = mkOption {
       type = types.lines;
       default = "";
-      description = "monitrc content";
+      description = "Additional monit config as string";
     };
-
   };
 
   config = mkIf cfg.enable {
@@ -24,7 +34,7 @@ in
     environment.systemPackages = [ pkgs.monit ];
 
     environment.etc.monitrc = {
-      text = cfg.config;
+      text = concatMapStringsSep "\n" (path: "include ${path}")  (cfg.configFiles ++ [extraConfig]);
       mode = "0400";
     };
 
diff --git a/nixos/modules/services/monitoring/netdata.nix b/nixos/modules/services/monitoring/netdata.nix
index a5233a46e3411..2e73e15d3a867 100644
--- a/nixos/modules/services/monitoring/netdata.nix
+++ b/nixos/modules/services/monitoring/netdata.nix
@@ -133,16 +133,6 @@ in {
         }
       ];
 
-    systemd.tmpfiles.rules = [
-      "d /var/cache/netdata 0755 ${cfg.user} ${cfg.group} -"
-      "Z /var/cache/netdata - ${cfg.user} ${cfg.group} -"
-      "d /var/log/netdata 0755 ${cfg.user} ${cfg.group} -"
-      "Z /var/log/netdata - ${cfg.user} ${cfg.group} -"
-      "d /var/lib/netdata 0755 ${cfg.user} ${cfg.group} -"
-      "Z /var/lib/netdata - ${cfg.user} ${cfg.group} -"
-      "d /etc/netdata 0755 ${cfg.user} ${cfg.group} -"
-      "Z /etc/netdata - ${cfg.user} ${cfg.group} -"
-    ];
     systemd.services.netdata = {
       description = "Real time performance monitoring";
       after = [ "network.target" ];
@@ -158,11 +148,40 @@ in {
         # User and group
         User = cfg.user;
         Group = cfg.group;
-        # Runtime directory and mode
-        RuntimeDirectory = "netdata";
-        RuntimeDirectoryMode = "0755";
         # Performance
         LimitNOFILE = "30000";
+        # Runtime directory and mode
+        RuntimeDirectory = "netdata";
+        RuntimeDirectoryMode = "0750";
+        # State directory and mode
+        StateDirectory = "netdata";
+        StateDirectoryMode = "0750";
+        # Cache directory and mode
+        CacheDirectory = "netdata";
+        CacheDirectoryMode = "0750";
+        # Logs directory and mode
+        LogsDirectory = "netdata";
+        LogsDirectoryMode = "0750";
+        # Configuration directory and mode
+        ConfigurationDirectory = "netdata";
+        ConfigurationDirectoryMode = "0755";
+        # Capabilities
+        CapabilityBoundingSet = [
+          "CAP_DAC_OVERRIDE"      # is required for freeipmi and slabinfo plugins
+          "CAP_DAC_READ_SEARCH"   # is required for apps plugin
+          "CAP_FOWNER"            # is required for freeipmi plugin
+          "CAP_SETPCAP"           # is required for apps, perf and slabinfo plugins
+          "CAP_SYS_ADMIN"         # is required for perf plugin
+          "CAP_SYS_PTRACE"        # is required for apps plugin
+          "CAP_SYS_RESOURCE"      # is required for ebpf plugin
+          "CAP_NET_RAW"           # is required for fping app
+        ];
+        # Sandboxing
+        ProtectSystem = "full";
+        ProtectHome = "read-only";
+        PrivateTmp = true;
+        ProtectControlGroups = true;
+        PrivateMounts = true;
       };
     };
 
diff --git a/nixos/modules/services/monitoring/prometheus/exporters.nix b/nixos/modules/services/monitoring/prometheus/exporters.nix
index c4aedd7e23b20..59748efe0ded8 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters.nix
@@ -34,6 +34,7 @@ let
     "mail"
     "mikrotik"
     "minio"
+    "modemmanager"
     "nextcloud"
     "nginx"
     "node"
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/modemmanager.nix b/nixos/modules/services/monitoring/prometheus/exporters/modemmanager.nix
new file mode 100644
index 0000000000000..86ea98b94e4c2
--- /dev/null
+++ b/nixos/modules/services/monitoring/prometheus/exporters/modemmanager.nix
@@ -0,0 +1,33 @@
+{ config, lib, pkgs, options }:
+
+with lib;
+
+let
+  cfg = config.services.prometheus.exporters.modemmanager;
+in
+{
+  port = 9539;
+  extraOpts = {
+    refreshRate = mkOption {
+      type = types.str;
+      default = "5s";
+      description = ''
+        How frequently ModemManager will refresh the extended signal quality
+        information for each modem. The duration should be specified in seconds
+        ("5s"), minutes ("1m"), or hours ("1h").
+      '';
+    };
+  };
+  serviceOpts = {
+    serviceConfig = {
+      # Required in order to authenticate with ModemManager via D-Bus.
+      SupplementaryGroups = "networkmanager";
+      ExecStart = ''
+        ${pkgs.prometheus-modemmanager-exporter}/bin/modemmanager_exporter \
+          -addr ${cfg.listenAddress}:${toString cfg.port} \
+          -rate ${cfg.refreshRate} \
+          ${concatStringsSep " \\\n  " cfg.extraFlags}
+      '';
+    };
+  };
+}
diff --git a/nixos/modules/services/monitoring/smartd.nix b/nixos/modules/services/monitoring/smartd.nix
index c345ec48a018e..c72b4abfcdce3 100644
--- a/nixos/modules/services/monitoring/smartd.nix
+++ b/nixos/modules/services/monitoring/smartd.nix
@@ -18,9 +18,9 @@ let
     ${optionalString nm.enable ''
       {
       ${pkgs.coreutils}/bin/cat << EOF
-      From: smartd on ${host} <root>
+      From: smartd on ${host} <${nm.sender}>
       To: undisclosed-recipients:;
-      Subject: SMART error on $SMARTD_DEVICESTRING: $SMARTD_FAILTYPE
+      Subject: $SMARTD_SUBJECT
 
       $SMARTD_FULLMESSAGE
       EOF
@@ -129,6 +129,16 @@ in
             description = "Whenever to send e-mail notifications.";
           };
 
+          sender = mkOption {
+            default = "root";
+            example = "example@domain.tld";
+            type = types.str;
+            description = ''
+              Sender of the notification messages.
+              Acts as the value of <literal>email</literal> in the emails' <literal>From: ... </literal> field.
+            '';
+          };
+
           recipient = mkOption {
             default = "root";
             type = types.str;
@@ -229,11 +239,7 @@ in
 
     systemd.services.smartd = {
       description = "S.M.A.R.T. Daemon";
-
       wantedBy = [ "multi-user.target" ];
-
-      path = [ pkgs.nettools ]; # for hostname and dnsdomanname calls in smartd
-
       serviceConfig.ExecStart = "${pkgs.smartmontools}/sbin/smartd ${lib.concatStringsSep " " cfg.extraOptions} --no-fork --configfile=${smartdConf}";
     };
 
diff --git a/nixos/modules/services/monitoring/teamviewer.nix b/nixos/modules/services/monitoring/teamviewer.nix
index dd98ecab828d7..8d781d82d0864 100644
--- a/nixos/modules/services/monitoring/teamviewer.nix
+++ b/nixos/modules/services/monitoring/teamviewer.nix
@@ -15,7 +15,7 @@ in
   options = {
 
     services.teamviewer.enable = mkEnableOption "TeamViewer daemon";
-      
+
   };
 
   ###### implementation
diff --git a/nixos/modules/services/monitoring/zabbix-agent.nix b/nixos/modules/services/monitoring/zabbix-agent.nix
index b3383ed628b29..73eed7aa66af3 100644
--- a/nixos/modules/services/monitoring/zabbix-agent.nix
+++ b/nixos/modules/services/monitoring/zabbix-agent.nix
@@ -3,8 +3,9 @@
 let
   cfg = config.services.zabbixAgent;
 
-  inherit (lib) mkDefault mkEnableOption mkIf mkOption;
+  inherit (lib) mkDefault mkEnableOption mkIf mkMerge mkOption;
   inherit (lib) attrValues concatMapStringsSep literalExample optionalString types;
+  inherit (lib.generators) toKeyValue;
 
   user = "zabbix-agent";
   group = "zabbix-agent";
@@ -14,19 +15,15 @@ let
     paths = attrValues cfg.modules;
   };
 
-  configFile = pkgs.writeText "zabbix_agent.conf" ''
-    LogType = console
-    Server = ${cfg.server}
-    ListenIP = ${cfg.listen.ip}
-    ListenPort = ${toString cfg.listen.port}
-    ${optionalString (cfg.modules != {}) "LoadModulePath = ${moduleEnv}/lib"}
-    ${concatMapStringsSep "\n" (name: "LoadModule = ${name}") (builtins.attrNames cfg.modules)}
-    ${cfg.extraConfig}
-  '';
+  configFile = pkgs.writeText "zabbix_agent.conf" (toKeyValue { listsAsDuplicateKeys = true; } cfg.settings);
 
 in
 
 {
+  imports = [
+    (lib.mkRemovedOptionModule [ "services" "zabbixAgent" "extraConfig" ] "Use services.zabbixAgent.settings instead.")
+  ];
+
   # interface
 
   options = {
@@ -105,15 +102,18 @@ in
         '';
       };
 
-      # TODO: for bonus points migrate this to https://github.com/NixOS/rfcs/pull/42
-      extraConfig = mkOption {
-        default = "";
-        type = types.lines;
+      settings = mkOption {
+        type = with types; attrsOf (oneOf [ int str (listOf str) ]);
+        default = {};
         description = ''
-          Configuration that is injected verbatim into the configuration file. Refer to
+          Zabbix Agent configuration. Refer to
           <link xlink:href="https://www.zabbix.com/documentation/current/manual/appendix/config/zabbix_agentd"/>
           for details on supported values.
         '';
+        example = {
+          Hostname = "example.org";
+          DebugLevel = 4;
+        };
       };
 
     };
@@ -124,6 +124,17 @@ in
 
   config = mkIf cfg.enable {
 
+    services.zabbixAgent.settings = mkMerge [
+      {
+        LogType = "console";
+        Server = cfg.server;
+        ListenIP = cfg.listen.ip;
+        ListenPort = cfg.listen.port;
+        LoadModule = builtins.attrNames cfg.modules;
+      }
+      (mkIf (cfg.modules != {}) { LoadModulePath = "${moduleEnv}/lib"; })
+    ];
+
     networking.firewall = mkIf cfg.openFirewall {
       allowedTCPPorts = [ cfg.listen.port ];
     };
diff --git a/nixos/modules/services/monitoring/zabbix-proxy.nix b/nixos/modules/services/monitoring/zabbix-proxy.nix
index 9d214469c3b32..2c8b8b92cb38b 100644
--- a/nixos/modules/services/monitoring/zabbix-proxy.nix
+++ b/nixos/modules/services/monitoring/zabbix-proxy.nix
@@ -5,8 +5,9 @@ let
   pgsql = config.services.postgresql;
   mysql = config.services.mysql;
 
-  inherit (lib) mkDefault mkEnableOption mkIf mkOption;
-  inherit (lib) attrValues concatMapStringsSep literalExample optional optionalAttrs optionalString types;
+  inherit (lib) mkAfter mkDefault mkEnableOption mkIf mkMerge mkOption;
+  inherit (lib) attrValues concatMapStringsSep getName literalExample optional optionalAttrs optionalString types;
+  inherit (lib.generators) toKeyValue;
 
   user = "zabbix";
   group = "zabbix";
@@ -19,24 +20,7 @@ let
     paths = attrValues cfg.modules;
   };
 
-  configFile = pkgs.writeText "zabbix_proxy.conf" ''
-    LogType = console
-    ListenIP = ${cfg.listen.ip}
-    ListenPort = ${toString cfg.listen.port}
-    Server = ${cfg.server}
-    # TODO: set to cfg.database.socket if database type is pgsql?
-    DBHost = ${optionalString (cfg.database.createLocally != true) cfg.database.host}
-    ${optionalString (cfg.database.createLocally != true) "DBPort = ${cfg.database.port}"}
-    DBName = ${cfg.database.name}
-    DBUser = ${cfg.database.user}
-    ${optionalString (cfg.database.passwordFile != null) "Include ${passwordFile}"}
-    ${optionalString (mysqlLocal && cfg.database.socket != null) "DBSocket = ${cfg.database.socket}"}
-    SocketDir = ${runtimeDir}
-    FpingLocation = /run/wrappers/bin/fping
-    ${optionalString (cfg.modules != {}) "LoadModulePath = ${moduleEnv}/lib"}
-    ${concatMapStringsSep "\n" (name: "LoadModule = ${name}") (builtins.attrNames cfg.modules)}
-    ${cfg.extraConfig}
-  '';
+  configFile = pkgs.writeText "zabbix_proxy.conf" (toKeyValue { listsAsDuplicateKeys = true; } cfg.settings);
 
   mysqlLocal = cfg.database.createLocally && cfg.database.type == "mysql";
   pgsqlLocal = cfg.database.createLocally && cfg.database.type == "pgsql";
@@ -44,6 +28,10 @@ let
 in
 
 {
+  imports = [
+    (lib.mkRemovedOptionModule [ "services" "zabbixProxy" "extraConfig" ] "Use services.zabbixProxy.settings instead.")
+  ];
+
   # interface
 
   options = {
@@ -182,15 +170,19 @@ in
         '';
       };
 
-      # TODO: for bonus points migrate this to https://github.com/NixOS/rfcs/pull/42
-      extraConfig = mkOption {
-        default = "";
-        type = types.lines;
+      settings = mkOption {
+        type = with types; attrsOf (oneOf [ int str (listOf str) ]);
+        default = {};
         description = ''
-          Configuration that is injected verbatim into the configuration file. Refer to
+          Zabbix Proxy configuration. Refer to
           <link xlink:href="https://www.zabbix.com/documentation/current/manual/appendix/config/zabbix_proxy"/>
           for details on supported values.
         '';
+        example = {
+          CacheSize = "1G";
+          SSHKeyLocation = "/var/lib/zabbix/.ssh";
+          StartPingers = 32;
+        };
       };
 
     };
@@ -213,6 +205,26 @@ in
       }
     ];
 
+    services.zabbixProxy.settings = mkMerge [
+      {
+        LogType = "console";
+        ListenIP = cfg.listen.ip;
+        ListenPort = cfg.listen.port;
+        Server = cfg.server;
+        # TODO: set to cfg.database.socket if database type is pgsql?
+        DBHost = optionalString (cfg.database.createLocally != true) cfg.database.host;
+        DBName = cfg.database.name;
+        DBUser = cfg.database.user;
+        SocketDir = runtimeDir;
+        FpingLocation = "/run/wrappers/bin/fping";
+        LoadModule = builtins.attrNames cfg.modules;
+      }
+      (mkIf (cfg.database.createLocally != true) { DBPort = cfg.database.port; })
+      (mkIf (cfg.database.passwordFile != null) { Include = [ "${passwordFile}" ]; })
+      (mkIf (mysqlLocal && cfg.database.socket != null) { DBSocket = cfg.database.socket; })
+      (mkIf (cfg.modules != {}) { LoadModulePath = "${moduleEnv}/lib"; })
+    ];
+
     networking.firewall = mkIf cfg.openFirewall {
       allowedTCPPorts = [ cfg.listen.port ];
     };
@@ -220,14 +232,15 @@ in
     services.mysql = optionalAttrs mysqlLocal {
       enable = true;
       package = mkDefault pkgs.mariadb;
-      ensureDatabases = [ cfg.database.name ];
-      ensureUsers = [
-        { name = cfg.database.user;
-          ensurePermissions = { "${cfg.database.name}.*" = "ALL PRIVILEGES"; };
-        }
-      ];
     };
 
+    systemd.services.mysql.postStart = mkAfter (optionalString mysqlLocal ''
+      ( echo "CREATE DATABASE IF NOT EXISTS \`${cfg.database.name}\` CHARACTER SET utf8 COLLATE utf8_bin;"
+        echo "CREATE USER IF NOT EXISTS '${cfg.database.user}'@'localhost' IDENTIFIED WITH ${if (getName config.services.mysql.package == getName pkgs.mariadb) then "unix_socket" else "auth_socket"};"
+        echo "GRANT ALL PRIVILEGES ON \`${cfg.database.name}\`.* TO '${cfg.database.user}'@'localhost';"
+      ) | ${config.services.mysql.package}/bin/mysql -N
+    '');
+
     services.postgresql = optionalAttrs pgsqlLocal {
       enable = true;
       ensureDatabases = [ cfg.database.name ];
diff --git a/nixos/modules/services/monitoring/zabbix-server.nix b/nixos/modules/services/monitoring/zabbix-server.nix
index b4e4378ce1e76..c8658634ecb62 100644
--- a/nixos/modules/services/monitoring/zabbix-server.nix
+++ b/nixos/modules/services/monitoring/zabbix-server.nix
@@ -5,8 +5,9 @@ let
   pgsql = config.services.postgresql;
   mysql = config.services.mysql;
 
-  inherit (lib) mkDefault mkEnableOption mkIf mkOption;
-  inherit (lib) attrValues concatMapStringsSep literalExample optional optionalAttrs optionalString types;
+  inherit (lib) mkAfter mkDefault mkEnableOption mkIf mkMerge mkOption;
+  inherit (lib) attrValues concatMapStringsSep getName literalExample optional optionalAttrs optionalString types;
+  inherit (lib.generators) toKeyValue;
 
   user = "zabbix";
   group = "zabbix";
@@ -19,24 +20,7 @@ let
     paths = attrValues cfg.modules;
   };
 
-  configFile = pkgs.writeText "zabbix_server.conf" ''
-    LogType = console
-    ListenIP = ${cfg.listen.ip}
-    ListenPort = ${toString cfg.listen.port}
-    # TODO: set to cfg.database.socket if database type is pgsql?
-    DBHost = ${optionalString (cfg.database.createLocally != true) cfg.database.host}
-    ${optionalString (cfg.database.createLocally != true) "DBPort = ${cfg.database.port}"}
-    DBName = ${cfg.database.name}
-    DBUser = ${cfg.database.user}
-    ${optionalString (cfg.database.passwordFile != null) "Include ${passwordFile}"}
-    ${optionalString (mysqlLocal && cfg.database.socket != null) "DBSocket = ${cfg.database.socket}"}
-    PidFile = ${runtimeDir}/zabbix_server.pid
-    SocketDir = ${runtimeDir}
-    FpingLocation = /run/wrappers/bin/fping
-    ${optionalString (cfg.modules != {}) "LoadModulePath = ${moduleEnv}/lib"}
-    ${concatMapStringsSep "\n" (name: "LoadModule = ${name}") (builtins.attrNames cfg.modules)}
-    ${cfg.extraConfig}
-  '';
+  configFile = pkgs.writeText "zabbix_server.conf" (toKeyValue { listsAsDuplicateKeys = true; } cfg.settings);
 
   mysqlLocal = cfg.database.createLocally && cfg.database.type == "mysql";
   pgsqlLocal = cfg.database.createLocally && cfg.database.type == "pgsql";
@@ -47,6 +31,7 @@ in
   imports = [
     (lib.mkRenamedOptionModule [ "services" "zabbixServer" "dbServer" ] [ "services" "zabbixServer" "database" "host" ])
     (lib.mkRemovedOptionModule [ "services" "zabbixServer" "dbPassword" ] "Use services.zabbixServer.database.passwordFile instead.")
+    (lib.mkRemovedOptionModule [ "services" "zabbixServer" "extraConfig" ] "Use services.zabbixServer.settings instead.")
   ];
 
   # interface
@@ -176,15 +161,19 @@ in
         '';
       };
 
-      # TODO: for bonus points migrate this to https://github.com/NixOS/rfcs/pull/42
-      extraConfig = mkOption {
-        default = "";
-        type = types.lines;
+      settings = mkOption {
+        type = with types; attrsOf (oneOf [ int str (listOf str) ]);
+        default = {};
         description = ''
-          Configuration that is injected verbatim into the configuration file. Refer to
+          Zabbix Server configuration. Refer to
           <link xlink:href="https://www.zabbix.com/documentation/current/manual/appendix/config/zabbix_server"/>
           for details on supported values.
         '';
+        example = {
+          CacheSize = "1G";
+          SSHKeyLocation = "/var/lib/zabbix/.ssh";
+          StartPingers = 32;
+        };
       };
 
     };
@@ -204,6 +193,26 @@ in
       }
     ];
 
+    services.zabbixServer.settings = mkMerge [
+      {
+        LogType = "console";
+        ListenIP = cfg.listen.ip;
+        ListenPort = cfg.listen.port;
+        # TODO: set to cfg.database.socket if database type is pgsql?
+        DBHost = optionalString (cfg.database.createLocally != true) cfg.database.host;
+        DBName = cfg.database.name;
+        DBUser = cfg.database.user;
+        PidFile = "${runtimeDir}/zabbix_server.pid";
+        SocketDir = runtimeDir;
+        FpingLocation = "/run/wrappers/bin/fping";
+        LoadModule = builtins.attrNames cfg.modules;
+      }
+      (mkIf (cfg.database.createLocally != true) { DBPort = cfg.database.port; })
+      (mkIf (cfg.database.passwordFile != null) { Include = [ "${passwordFile}" ]; })
+      (mkIf (mysqlLocal && cfg.database.socket != null) { DBSocket = cfg.database.socket; })
+      (mkIf (cfg.modules != {}) { LoadModulePath = "${moduleEnv}/lib"; })
+    ];
+
     networking.firewall = mkIf cfg.openFirewall {
       allowedTCPPorts = [ cfg.listen.port ];
     };
@@ -211,14 +220,15 @@ in
     services.mysql = optionalAttrs mysqlLocal {
       enable = true;
       package = mkDefault pkgs.mariadb;
-      ensureDatabases = [ cfg.database.name ];
-      ensureUsers = [
-        { name = cfg.database.user;
-          ensurePermissions = { "${cfg.database.name}.*" = "ALL PRIVILEGES"; };
-        }
-      ];
     };
 
+    systemd.services.mysql.postStart = mkAfter (optionalString mysqlLocal ''
+      ( echo "CREATE DATABASE IF NOT EXISTS \`${cfg.database.name}\` CHARACTER SET utf8 COLLATE utf8_bin;"
+        echo "CREATE USER IF NOT EXISTS '${cfg.database.user}'@'localhost' IDENTIFIED WITH ${if (getName config.services.mysql.package == getName pkgs.mariadb) then "unix_socket" else "auth_socket"};"
+        echo "GRANT ALL PRIVILEGES ON \`${cfg.database.name}\`.* TO '${cfg.database.user}'@'localhost';"
+      ) | ${config.services.mysql.package}/bin/mysql -N
+    '');
+
     services.postgresql = optionalAttrs pgsqlLocal {
       enable = true;
       ensureDatabases = [ cfg.database.name ];
diff --git a/nixos/modules/services/network-filesystems/ipfs.nix b/nixos/modules/services/network-filesystems/ipfs.nix
index 7d18410ff0a45..f298f831fa7b2 100644
--- a/nixos/modules/services/network-filesystems/ipfs.nix
+++ b/nixos/modules/services/network-filesystems/ipfs.nix
@@ -25,6 +25,15 @@ let
       then "/${lib.concatStringsSep "/" (lib.tail addr)}"
     else null; # not valid for listen stream, skip
 
+  multiaddrToListenDatagram = addrRaw: let
+      addr = splitMulitaddr addrRaw;
+      s = builtins.elemAt addr;
+    in if s 0 == "ip4" && s 2 == "udp"
+      then "${s 1}:${s 3}"
+    else if s 0 == "ip6" && s 2 == "udp"
+      then "[${s 1}]:${s 3}"
+    else null; # not valid for listen datagram, skip
+
 in {
 
   ###### interface
@@ -96,6 +105,8 @@ in {
         default = [
           "/ip4/0.0.0.0/tcp/4001"
           "/ip6/::/tcp/4001"
+          "/ip4/0.0.0.0/udp/4001/quic"
+          "/ip6/::/udp/4001/quic"
         ];
         description = "Where IPFS listens for incoming p2p connections";
       };
@@ -266,9 +277,14 @@ in {
 
     systemd.sockets.ipfs-gateway = {
       wantedBy = [ "sockets.target" ];
-      socketConfig.ListenStream = let
-          fromCfg = multiaddrToListenStream cfg.gatewayAddress;
-        in [ "" ] ++ lib.optional (fromCfg != null) fromCfg;
+      socketConfig = {
+        ListenStream = let
+            fromCfg = multiaddrToListenStream cfg.gatewayAddress;
+          in [ "" ] ++ lib.optional (fromCfg != null) fromCfg;
+        ListenDatagram = let
+            fromCfg = multiaddrToListenDatagram cfg.gatewayAddress;
+          in [ "" ] ++ lib.optional (fromCfg != null) fromCfg;
+      };
     };
 
     systemd.sockets.ipfs-api = {
diff --git a/nixos/modules/services/networking/3proxy.nix b/nixos/modules/services/networking/3proxy.nix
index ae8a4958ca965..37a48657c1cc9 100644
--- a/nixos/modules/services/networking/3proxy.nix
+++ b/nixos/modules/services/networking/3proxy.nix
@@ -124,7 +124,7 @@ in {
                   <literal>"iponly"</literal>: specifies no authentication. ACLs authorization is used.
                 </para></listitem>
                 <listitem><para>
-                  <literal>"strong"</literal>: authentication by username/password. If user is not registered his access is denied regardless of ACLs.
+                  <literal>"strong"</literal>: authentication by username/password. If user is not registered their access is denied regardless of ACLs.
                 </para></listitem>
               </itemizedlist>
 
diff --git a/nixos/modules/services/networking/bitcoind.nix b/nixos/modules/services/networking/bitcoind.nix
index 4e00a88654747..bc9aa53f49aa7 100644
--- a/nixos/modules/services/networking/bitcoind.nix
+++ b/nixos/modules/services/networking/bitcoind.nix
@@ -3,31 +3,8 @@
 with lib;
 
 let
-  cfg = config.services.bitcoind;
-  pidFile = "${cfg.dataDir}/bitcoind.pid";
-  configFile = pkgs.writeText "bitcoin.conf" ''
-    ${optionalString cfg.testnet "testnet=1"}
-    ${optionalString (cfg.dbCache != null) "dbcache=${toString cfg.dbCache}"}
-    ${optionalString (cfg.prune != null) "prune=${toString cfg.prune}"}
-
-    # Connection options
-    ${optionalString (cfg.port != null) "port=${toString cfg.port}"}
-
-    # RPC server options
-    ${optionalString (cfg.rpc.port != null) "rpcport=${toString cfg.rpc.port}"}
-    ${concatMapStringsSep  "\n"
-      (rpcUser: "rpcauth=${rpcUser.name}:${rpcUser.passwordHMAC}")
-      (attrValues cfg.rpc.users)
-    }
 
-    # Extra config options (from bitcoind nixos service)
-    ${cfg.extraConfig}
-  '';
-  cmdlineOptions = escapeShellArgs [
-    "-conf=${cfg.configFile}"
-    "-datadir=${cfg.dataDir}"
-    "-pid=${pidFile}"
-  ];
+  eachBitcoind = config.services.bitcoind;
 
   rpcUserOpts = { name, ... }: {
     options = {
@@ -39,11 +16,14 @@ let
         '';
       };
       passwordHMAC = mkOption {
-        type = with types; uniq (strMatching "[0-9a-f]+\\$[0-9a-f]{64}");
+        type = types.uniq (types.strMatching "[0-9a-f]+\\$[0-9a-f]{64}");
         example = "f7efda5c189b999524f151318c0c86$d5b51b3beffbc02b724e5d095828e0bc8b2456e9ac8757ae3211a5d9b16a22ae";
         description = ''
           Password HMAC-SHA-256 for JSON-RPC connections. Must be a string of the
           format &lt;SALT-HEX&gt;$&lt;HMAC-HEX&gt;.
+
+          Tool (Python script) for HMAC generation is available here:
+          <link xlink:href="https://github.com/bitcoin/bitcoin/blob/master/share/rpcauth/rpcauth.py"/>
         '';
       };
     };
@@ -51,10 +31,10 @@ let
       name = mkDefault name;
     };
   };
-in {
-  options = {
 
-    services.bitcoind = {
+  bitcoindOpts = { config, lib, name, ...}: {
+    options = {
+
       enable = mkEnableOption "Bitcoin daemon";
 
       package = mkOption {
@@ -63,12 +43,14 @@ in {
         defaultText = "pkgs.bitcoind";
         description = "The package providing bitcoin binaries.";
       };
+
       configFile = mkOption {
-        type = types.path;
-        default = configFile;
-        example = "/etc/bitcoind.conf";
+        type = types.nullOr types.path;
+        default = null;
+        example = "/var/lib/${name}/bitcoin.conf";
         description = "The configuration file path to supply bitcoind.";
       };
+
       extraConfig = mkOption {
         type = types.lines;
         default = "";
@@ -79,20 +61,22 @@ in {
         '';
         description = "Additional configurations to be appended to <filename>bitcoin.conf</filename>.";
       };
+
       dataDir = mkOption {
         type = types.path;
-        default = "/var/lib/bitcoind";
+        default = "/var/lib/bitcoind-${name}";
         description = "The data directory for bitcoind.";
       };
 
       user = mkOption {
         type = types.str;
-        default = "bitcoin";
+        default = "bitcoind-${name}";
         description = "The user as which to run bitcoind.";
       };
+
       group = mkOption {
         type = types.str;
-        default = cfg.user;
+        default = config.user;
         description = "The group as which to run bitcoind.";
       };
 
@@ -110,29 +94,36 @@ in {
               bob.passwordHMAC = "b2dd077cb54591a2f3139e69a897ac$4e71f08d48b4347cf8eff3815c0e25ae2e9a4340474079f55705f40574f4ec99";
             }
           '';
-          type = with types; loaOf (submodule rpcUserOpts);
-          description = ''
-            RPC user information for JSON-RPC connnections.
-          '';
+          type = types.attrsOf (types.submodule rpcUserOpts);
+          description = "RPC user information for JSON-RPC connnections.";
         };
       };
 
+      pidFile = mkOption {
+        type = types.path;
+        default = "${config.dataDir}/bitcoind.pid";
+        description = "Location of bitcoind pid file.";
+      };
+
       testnet = mkOption {
         type = types.bool;
         default = false;
-        description = "Whether to use the test chain.";
+        description = "Whether to use the testnet instead of mainnet.";
       };
+
       port = mkOption {
         type = types.nullOr types.port;
         default = null;
         description = "Override the default port on which to listen for connections.";
       };
+
       dbCache = mkOption {
         type = types.nullOr (types.ints.between 4 16384);
         default = null;
         example = 4000;
-        description = "Override the default database cache size in megabytes.";
+        description = "Override the default database cache size in MiB.";
       };
+
       prune = mkOption {
         type = types.nullOr (types.coercedTo
           (types.enum [ "disable" "manual" ])
@@ -149,45 +140,122 @@ in {
           and -rescan. Warning: Reverting this setting requires re-downloading
           the entire blockchain. ("disable" = disable pruning blocks, "manual"
           = allow manual pruning via RPC, >=550 = automatically prune block files
-          to stay under the specified target size in MiB)
+          to stay under the specified target size in MiB).
+        '';
+      };
+
+      extraCmdlineOptions = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        description = ''
+          Extra command line options to pass to bitcoind.
+          Run bitcoind --help to list all available options.
         '';
       };
     };
   };
+in
+{
 
-  config = mkIf cfg.enable {
-    environment.systemPackages = [ cfg.package ];
-    systemd.tmpfiles.rules = [
-      "d '${cfg.dataDir}' 0770 '${cfg.user}' '${cfg.group}' - -"
-      "L '${cfg.dataDir}/bitcoin.conf' - - - - '${cfg.configFile}'"
-    ];
-    systemd.services.bitcoind = {
-      description = "Bitcoin daemon";
-      after = [ "network.target" ];
-      wantedBy = [ "multi-user.target" ];
-      serviceConfig = {
-        User = cfg.user;
-        Group = cfg.group;
-        ExecStart = "${cfg.package}/bin/bitcoind ${cmdlineOptions}";
-        Restart = "on-failure";
-
-        # Hardening measures
-        PrivateTmp = "true";
-        ProtectSystem = "full";
-        NoNewPrivileges = "true";
-        PrivateDevices = "true";
-        MemoryDenyWriteExecute = "true";
-      };
+  options = {
+    services.bitcoind = mkOption {
+      type = types.attrsOf (types.submodule bitcoindOpts);
+      default = {};
+      description = "Specification of one or more bitcoind instances.";
     };
-    users.users.${cfg.user} = {
+  };
+
+  config = mkIf (eachBitcoind != {}) {
+
+    assertions = flatten (mapAttrsToList (bitcoindName: cfg: [
+    {
+      assertion = (cfg.prune != null) -> (builtins.elem cfg.prune [ "disable" "manual" 0 1 ] || (builtins.isInt cfg.prune && cfg.prune >= 550));
+      message = ''
+        If set, services.bitcoind.${bitcoindName}.prune has to be "disable", "manual", 0 , 1 or >= 550.
+      '';
+    }
+    {
+      assertion = (cfg.rpc.users != {}) -> (cfg.configFile == null);
+      message = ''
+        You cannot set both services.bitcoind.${bitcoindName}.rpc.users and services.bitcoind.${bitcoindName}.configFile
+        as they are exclusive. RPC user setting would have no effect if custom configFile would be used.
+      '';
+    }
+    ]) eachBitcoind);
+
+    environment.systemPackages = flatten (mapAttrsToList (bitcoindName: cfg: [
+      cfg.package
+    ]) eachBitcoind);
+
+    systemd.services = mapAttrs' (bitcoindName: cfg: (
+      nameValuePair "bitcoind-${bitcoindName}" (
+      let
+        configFile = pkgs.writeText "bitcoin.conf" ''
+          # If Testnet is enabled, we need to add [test] section
+          # otherwise, some options (e.g.: custom RPC port) will not work
+          ${optionalString cfg.testnet "[test]"}
+          # RPC users
+          ${concatMapStringsSep  "\n"
+            (rpcUser: "rpcauth=${rpcUser.name}:${rpcUser.passwordHMAC}")
+            (attrValues cfg.rpc.users)
+          }
+          # Extra config options (from bitcoind nixos service)
+          ${cfg.extraConfig}
+        '';
+      in {
+        description = "Bitcoin daemon";
+        after = [ "network.target" ];
+        wantedBy = [ "multi-user.target" ];
+        serviceConfig = {
+          User = cfg.user;
+          Group = cfg.group;
+          ExecStart = ''
+            ${cfg.package}/bin/bitcoind \
+            ${if (cfg.configFile != null) then
+              "-conf=${cfg.configFile}"
+            else
+              "-conf=${configFile}"
+            } \
+            -datadir=${cfg.dataDir} \
+            -pid=${cfg.pidFile} \
+            ${optionalString cfg.testnet "-testnet"}\
+            ${optionalString (cfg.port != null) "-port=${toString cfg.port}"}\
+            ${optionalString (cfg.prune != null) "-prune=${toString cfg.prune}"}\
+            ${optionalString (cfg.dbCache != null) "-dbcache=${toString cfg.dbCache}"}\
+            ${optionalString (cfg.rpc.port != null) "-rpcport=${toString cfg.rpc.port}"}\
+            ${toString cfg.extraCmdlineOptions}
+          '';
+          Restart = "on-failure";
+
+          # Hardening measures
+          PrivateTmp = "true";
+          ProtectSystem = "full";
+          NoNewPrivileges = "true";
+          PrivateDevices = "true";
+          MemoryDenyWriteExecute = "true";
+        };
+      }
+    ))) eachBitcoind;
+
+    systemd.tmpfiles.rules = flatten (mapAttrsToList (bitcoindName: cfg: [
+      "d '${cfg.dataDir}' 0770 '${cfg.user}' '${cfg.group}' - -"
+    ]) eachBitcoind);
+
+    users.users = mapAttrs' (bitcoindName: cfg: (
+      nameValuePair "bitcoind-${bitcoindName}" {
       name = cfg.user;
       group = cfg.group;
       description = "Bitcoin daemon user";
       home = cfg.dataDir;
       isSystemUser = true;
-    };
-    users.groups.${cfg.group} = {
-      name = cfg.group;
-    };
+    })) eachBitcoind;
+
+    users.groups = mapAttrs' (bitcoindName: cfg: (
+      nameValuePair "${cfg.group}" { }
+    )) eachBitcoind;
+
   };
+
+  meta.maintainers = with maintainers; [ _1000101 ];
+
 }
diff --git a/nixos/modules/services/networking/blockbook-frontend.nix b/nixos/modules/services/networking/blockbook-frontend.nix
new file mode 100644
index 0000000000000..dde24522756af
--- /dev/null
+++ b/nixos/modules/services/networking/blockbook-frontend.nix
@@ -0,0 +1,275 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  eachBlockbook = config.services.blockbook-frontend;
+
+  blockbookOpts = { config, lib, name, ...}: {
+
+    options = {
+
+      enable = mkEnableOption "blockbook-frontend application.";
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.blockbook;
+        description = "Which blockbook package to use.";
+      };
+
+      user = mkOption {
+        type = types.str;
+        default = "blockbook-frontend-${name}";
+        description = "The user as which to run blockbook-frontend-${name}.";
+      };
+
+      group = mkOption {
+        type = types.str;
+        default = "${config.user}";
+        description = "The group as which to run blockbook-frontend-${name}.";
+      };
+
+      certFile = mkOption {
+        type = types.nullOr types.path;
+        default = null;
+        example = "/etc/secrets/blockbook-frontend-${name}/certFile";
+        description = ''
+          To enable SSL, specify path to the name of certificate files without extension.
+          Expecting <filename>certFile.crt</filename> and <filename>certFile.key</filename>.
+        '';
+      };
+
+      configFile = mkOption {
+        type = with types; nullOr path;
+        default = null;
+        example = "${config.dataDir}/config.json";
+        description = "Location of the blockbook configuration file.";
+      };
+
+      coinName = mkOption {
+        type = types.str;
+        default = "Bitcoin";
+        example = "Bitcoin";
+        description = ''
+          See <link xlink:href="https://github.com/trezor/blockbook/blob/master/bchain/coins/blockchain.go#L61"/>
+          for current of coins supported in master (Note: may differ from release).
+        '';
+      };
+
+      cssDir = mkOption {
+        type = types.path;
+        default = "${config.package}/share/css/";
+        example = "${config.dataDir}/static/css/";
+        description = ''
+          Location of the dir with <filename>main.css</filename> CSS file.
+          By default, the one shipped with the package is used.
+        '';
+      };
+
+      dataDir = mkOption {
+        type = types.path;
+        default = "/var/lib/blockbook-frontend-${name}";
+        description = "Location of blockbook-frontend-${name} data directory.";
+      };
+
+      debug = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Debug mode, return more verbose errors, reload templates on each request.";
+      };
+
+      internal = mkOption {
+        type = types.nullOr types.str;
+        default = ":9030";
+        example = ":9030";
+        description = "Internal http server binding <literal>[address]:port</literal>.";
+      };
+
+      messageQueueBinding = mkOption {
+        type = types.str;
+        default = "tcp://127.0.0.1:38330";
+        example = "tcp://127.0.0.1:38330";
+        description = "Message Queue Binding <literal>address:port</literal>.";
+      };
+
+      public = mkOption {
+        type = types.nullOr types.str;
+        default = ":9130";
+        example = ":9130";
+        description = "Public http server binding <literal>[address]:port</literal>.";
+      };
+
+      rpc = {
+        url = mkOption {
+          type = types.str;
+          default = "http://127.0.0.1";
+          description = "URL for JSON-RPC connections.";
+        };
+
+        port = mkOption {
+          type = types.port;
+          default = 8030;
+          description = "Port for JSON-RPC connections.";
+        };
+
+        user = mkOption {
+          type = types.str;
+          default = "rpc";
+          example = "rpc";
+          description = "Username for JSON-RPC connections.";
+        };
+
+        password = mkOption {
+          type = types.str;
+          default = "rpc";
+          example = "rpc";
+          description = ''
+            RPC password for JSON-RPC connections.
+            Warning: this is stored in cleartext in the Nix store!!!
+            Use <literal>configFile</literal> or <literal>passwordFile</literal> if needed.
+          '';
+        };
+
+        passwordFile = mkOption {
+          type = types.nullOr types.path;
+          default = null;
+          description = ''
+            File containing password of the RPC user.
+            Note: This options is ignored when <literal>configFile</literal> is used.
+          '';
+        };
+      };
+
+      sync = mkOption {
+        type = types.bool;
+        default = true;
+        description = "Synchronizes until tip, if together with zeromq, keeps index synchronized.";
+      };
+
+      templateDir = mkOption {
+        type = types.path;
+        default = "${config.package}/share/templates/";
+        example = "${config.dataDir}/templates/static/";
+        description = "Location of the HTML templates. By default, ones shipped with the package are used.";
+      };
+
+      extraConfig = mkOption {
+        type = types.attrs;
+        default = {};
+        example = literalExample '' {
+          alternative_estimate_fee = "whatthefee-disabled";
+          alternative_estimate_fee_params = "{\"url\": \"https://whatthefee.io/data.json\", \"periodSeconds\": 60}";
+          fiat_rates = "coingecko";
+          fiat_rates_params = "{\"url\": \"https://api.coingecko.com/api/v3\", \"coin\": \"bitcoin\", \"periodSeconds\": 60}";
+          coin_shortcut = "BTC";
+          coin_label = "Bitcoin";
+          xpub_magic = 76067358;
+          xpub_magic_segwit_p2sh = 77429938;
+          xpub_magic_segwit_native = 78792518;
+        }'';
+        description = ''
+          Additional configurations to be appended to <filename>coin.conf</filename>.
+          Overrides any already defined configuration options.
+          See <link xlink:href="https://github.com/trezor/blockbook/tree/master/configs/coins"/>
+          for current configuration options supported in master (Note: may differ from release).
+        '';
+      };
+
+      extraCmdLineOptions = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        example = [ "-workers=1" "-dbcache=0" "-logtosderr" ];
+        description = ''
+          Extra command line options to pass to Blockbook.
+          Run blockbook --help to list all available options.
+        '';
+      };
+    };
+  };
+in
+{
+  # interface
+
+  options = {
+    services.blockbook-frontend = mkOption {
+      type = types.attrsOf (types.submodule blockbookOpts);
+      default = {};
+      description = "Specification of one or more blockbook-frontend instances.";
+    };
+  };
+
+  # implementation
+
+  config = mkIf (eachBlockbook != {}) {
+
+    systemd.services = mapAttrs' (blockbookName: cfg: (
+      nameValuePair "blockbook-frontend-${blockbookName}" (
+        let
+          configFile = if cfg.configFile != null then cfg.configFile else
+            pkgs.writeText "config.conf" (builtins.toJSON ( {
+                coin_name = "${cfg.coinName}";
+                rpc_user = "${cfg.rpc.user}";
+                rpc_pass = "${cfg.rpc.password}";
+                rpc_url = "${cfg.rpc.url}:${toString cfg.rpc.port}";
+                message_queue_binding = "${cfg.messageQueueBinding}";
+              } // cfg.extraConfig)
+            );
+        in {
+          description = "blockbook-frontend-${blockbookName} daemon";
+          after = [ "network.target" ];
+          wantedBy = [ "multi-user.target" ];
+          preStart = ''
+            ln -sf ${cfg.templateDir} ${cfg.dataDir}/static/
+            ln -sf ${cfg.cssDir} ${cfg.dataDir}/static/
+            ${optionalString (cfg.rpc.passwordFile != null && cfg.configFile == null) ''
+              CONFIGTMP=$(mktemp)
+              ${pkgs.jq}/bin/jq ".rpc_pass = \"$(cat ${cfg.rpc.passwordFile})\"" ${configFile} > $CONFIGTMP
+              mv $CONFIGTMP ${cfg.dataDir}/${blockbookName}-config.json
+            ''}
+          '';
+          serviceConfig = {
+            User = cfg.user;
+            Group = cfg.group;
+            ExecStart = ''
+               ${cfg.package}/bin/blockbook \
+               ${if (cfg.rpc.passwordFile != null && cfg.configFile == null) then
+               "-blockchaincfg=${cfg.dataDir}/${blockbookName}-config.json"
+               else
+               "-blockchaincfg=${configFile}"
+               } \
+               -datadir=${cfg.dataDir} \
+               ${optionalString (cfg.sync != false) "-sync"} \
+               ${optionalString (cfg.certFile != null) "-certfile=${toString cfg.certFile}"} \
+               ${optionalString (cfg.debug != false) "-debug"} \
+               ${optionalString (cfg.internal != null) "-internal=${toString cfg.internal}"} \
+               ${optionalString (cfg.public != null) "-public=${toString cfg.public}"} \
+               ${toString cfg.extraCmdLineOptions}
+            '';
+            Restart = "on-failure";
+            WorkingDirectory = cfg.dataDir;
+            LimitNOFILE = 65536;
+          };
+        }
+    ) )) eachBlockbook;
+
+    systemd.tmpfiles.rules = flatten (mapAttrsToList (blockbookName: cfg: [
+      "d ${cfg.dataDir} 0750 ${cfg.user} ${cfg.group} - -"
+      "d ${cfg.dataDir}/static 0750 ${cfg.user} ${cfg.group} - -"
+    ]) eachBlockbook);
+
+    users.users = mapAttrs' (blockbookName: cfg: (
+      nameValuePair "blockbook-frontend-${blockbookName}" {
+      name = cfg.user;
+      group = cfg.group;
+      home = cfg.dataDir;
+      isSystemUser = true;
+    })) eachBlockbook;
+
+    users.groups = mapAttrs' (instanceName: cfg: (
+      nameValuePair "${cfg.group}" { })) eachBlockbook;
+  };
+
+  meta.maintainers = with maintainers; [ _1000101 ];
+
+}
diff --git a/nixos/modules/services/networking/corerad.nix b/nixos/modules/services/networking/corerad.nix
index 5d73c0a0d7790..d90a5923bc62e 100644
--- a/nixos/modules/services/networking/corerad.nix
+++ b/nixos/modules/services/networking/corerad.nix
@@ -77,8 +77,11 @@ in {
         AmbientCapabilities = "CAP_NET_ADMIN CAP_NET_RAW";
         NoNewPrivileges = true;
         DynamicUser = true;
+        Type = "notify";
+        NotifyAccess = "main";
         ExecStart = "${getBin cfg.package}/bin/corerad -c=${cfg.configFile}";
         Restart = "on-failure";
+        RestartKillSignal = "SIGHUP";
       };
     };
   };
diff --git a/nixos/modules/services/networking/dhcpd.nix b/nixos/modules/services/networking/dhcpd.nix
index 67f7d81188700..8966deac76cba 100644
--- a/nixos/modules/services/networking/dhcpd.nix
+++ b/nixos/modules/services/networking/dhcpd.nix
@@ -11,7 +11,7 @@ let
     ''
       default-lease-time 600;
       max-lease-time 7200;
-      authoritative;
+      ${optionalString (!cfg.authoritative) "not "}authoritative;
       ddns-update-style interim;
       log-facility local1; # see dhcpd.nix
 
@@ -176,6 +176,16 @@ let
       '';
     };
 
+    authoritative = mkOption {
+      type = types.bool;
+      default = true;
+      description = ''
+        Whether the DHCP server shall send DHCPNAK messages to misconfigured
+        clients. If this is not done, clients may be unable to get a correct
+        IP address after changing subnets until their old lease has expired.
+      '';
+    };
+
   };
 
 in
diff --git a/nixos/modules/services/networking/gateone.nix b/nixos/modules/services/networking/gateone.nix
index 4456a95402edd..56f2ba21a125b 100644
--- a/nixos/modules/services/networking/gateone.nix
+++ b/nixos/modules/services/networking/gateone.nix
@@ -56,4 +56,4 @@ config = mkIf cfg.enable {
   };
 };
 }
-  
+
diff --git a/nixos/modules/services/networking/jicofo.nix b/nixos/modules/services/networking/jicofo.nix
new file mode 100644
index 0000000000000..160a5fea91a0e
--- /dev/null
+++ b/nixos/modules/services/networking/jicofo.nix
@@ -0,0 +1,152 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.jicofo;
+in
+{
+  options.services.jicofo = with types; {
+    enable = mkEnableOption "Jitsi Conference Focus - component of Jitsi Meet";
+
+    xmppHost = mkOption {
+      type = str;
+      example = "localhost";
+      description = ''
+        Hostname of the XMPP server to connect to.
+      '';
+    };
+
+    xmppDomain = mkOption {
+      type = nullOr str;
+      example = "meet.example.org";
+      description = ''
+        Domain name of the XMMP server to which to connect as a component.
+
+        If null, <option>xmppHost</option> is used.
+      '';
+    };
+
+    componentPasswordFile = mkOption {
+      type = str;
+      example = "/run/keys/jicofo-component";
+      description = ''
+        Path to file containing component secret.
+      '';
+    };
+
+    userName = mkOption {
+      type = str;
+      default = "focus";
+      description = ''
+        User part of the JID for XMPP user connection.
+      '';
+    };
+
+    userDomain = mkOption {
+      type = str;
+      example = "auth.meet.example.org";
+      description = ''
+        Domain part of the JID for XMPP user connection.
+      '';
+    };
+
+    userPasswordFile = mkOption {
+      type = str;
+      example = "/run/keys/jicofo-user";
+      description = ''
+        Path to file containing password for XMPP user connection.
+      '';
+    };
+
+    bridgeMuc = mkOption {
+      type = str;
+      example = "jvbbrewery@internal.meet.example.org";
+      description = ''
+        JID of the internal MUC used to communicate with Videobridges.
+      '';
+    };
+
+    config = mkOption {
+      type = attrsOf str;
+      default = { };
+      example = literalExample ''
+        {
+          "org.jitsi.jicofo.auth.URL" = "XMPP:jitsi-meet.example.com";
+        }
+      '';
+      description = ''
+        Contents of the <filename>sip-communicator.properties</filename> configuration file for jicofo.
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+    services.jicofo.config = mapAttrs (_: v: mkDefault v) {
+      "org.jitsi.jicofo.BRIDGE_MUC" = cfg.bridgeMuc;
+    };
+
+    users.groups.jitsi-meet = {};
+
+    systemd.services.jicofo = let
+      jicofoProps = {
+        "-Dnet.java.sip.communicator.SC_HOME_DIR_LOCATION" = "/etc/jitsi";
+        "-Dnet.java.sip.communicator.SC_HOME_DIR_NAME" = "jicofo";
+        "-Djava.util.logging.config.file" = "/etc/jitsi/jicofo/logging.properties";
+      };
+    in
+    {
+      description = "JItsi COnference FOcus";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+
+      restartTriggers = [
+        config.environment.etc."jitsi/jicofo/sip-communicator.properties".source
+      ];
+      environment.JAVA_SYS_PROPS = concatStringsSep " " (mapAttrsToList (k: v: "${k}=${toString v}") jicofoProps);
+
+      script = ''
+        ${pkgs.jicofo}/bin/jicofo \
+          --host=${cfg.xmppHost} \
+          --domain=${if cfg.xmppDomain == null then cfg.xmppHost else cfg.xmppDomain} \
+          --secret=$(cat ${cfg.componentPasswordFile}) \
+          --user_name=${cfg.userName} \
+          --user_domain=${cfg.userDomain} \
+          --user_password=$(cat ${cfg.userPasswordFile})
+      '';
+
+      serviceConfig = {
+        Type = "exec";
+
+        DynamicUser = true;
+        User = "jicofo";
+        Group = "jitsi-meet";
+
+        CapabilityBoundingSet = "";
+        NoNewPrivileges = true;
+        ProtectSystem = "strict";
+        ProtectHome = true;
+        PrivateTmp = true;
+        PrivateDevices = true;
+        ProtectHostname = true;
+        ProtectKernelTunables = true;
+        ProtectKernelModules = true;
+        ProtectControlGroups = true;
+        RestrictAddressFamilies = [ "AF_INET" "AF_INET6" "AF_UNIX" ];
+        RestrictNamespaces = true;
+        LockPersonality = true;
+        RestrictRealtime = true;
+        RestrictSUIDSGID = true;
+      };
+    };
+
+    environment.etc."jitsi/jicofo/sip-communicator.properties".source =
+      pkgs.writeText "sip-communicator.properties" (
+        generators.toKeyValue {} cfg.config
+      );
+    environment.etc."jitsi/jicofo/logging.properties".source =
+      mkDefault "${pkgs.jicofo}/etc/jitsi/jicofo/logging.properties-journal";
+  };
+
+  meta.maintainers = lib.teams.jitsi.members;
+}
diff --git a/nixos/modules/services/networking/jitsi-videobridge.nix b/nixos/modules/services/networking/jitsi-videobridge.nix
new file mode 100644
index 0000000000000..5482e997a4018
--- /dev/null
+++ b/nixos/modules/services/networking/jitsi-videobridge.nix
@@ -0,0 +1,276 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.jitsi-videobridge;
+  attrsToArgs = a: concatStringsSep " " (mapAttrsToList (k: v: "${k}=${toString v}") a);
+
+  # HOCON is a JSON superset that videobridge2 uses for configuration.
+  # It can substitute environment variables which we use for passwords here.
+  # https://github.com/lightbend/config/blob/master/README.md
+  #
+  # Substitution for environment variable FOO is represented as attribute set
+  # { __hocon_envvar = "FOO"; }
+  toHOCON = x: if isAttrs x && x ? __hocon_envvar then ("\${" + x.__hocon_envvar + "}")
+    else if isAttrs x then "{${ concatStringsSep "," (mapAttrsToList (k: v: ''"${k}":${toHOCON v}'') x) }}"
+    else if isList x then "[${ concatMapStringsSep "," toHOCON x }]"
+    else builtins.toJSON x;
+
+  # We're passing passwords in environment variables that have names generated
+  # from an attribute name, which may not be a valid bash identifier.
+  toVarName = s: "XMPP_PASSWORD_" + stringAsChars (c: if builtins.match "[A-Za-z0-9]" c != null then c else "_") s;
+
+  defaultJvbConfig = {
+    videobridge = {
+      ice = {
+        tcp = {
+          enabled = true;
+          port = 4443;
+        };
+        udp.port = 10000;
+      };
+      stats = {
+        enabled = true;
+        transports = [ { type = "muc"; } ];
+      };
+      apis.xmpp-client.configs = flip mapAttrs cfg.xmppConfigs (name: xmppConfig: {
+        hostname = xmppConfig.hostName;
+        domain = xmppConfig.domain;
+        username = xmppConfig.userName;
+        password = { __hocon_envvar = toVarName name; };
+        muc_jids = xmppConfig.mucJids;
+        muc_nickname = xmppConfig.mucNickname;
+        disable_certificate_verification = xmppConfig.disableCertificateVerification;
+      });
+    };
+  };
+
+  # Allow overriding leaves of the default config despite types.attrs not doing any merging.
+  jvbConfig = recursiveUpdate defaultJvbConfig cfg.config;
+in
+{
+  options.services.jitsi-videobridge = with types; {
+    enable = mkEnableOption "Jitsi Videobridge, a WebRTC compatible video router";
+
+    config = mkOption {
+      type = attrs;
+      default = { };
+      example = literalExample ''
+        {
+          videobridge = {
+            ice.udp.port = 5000;
+            websockets = {
+              enabled = true;
+              server-id = "jvb1";
+            };
+          };
+        }
+      '';
+      description = ''
+        Videobridge configuration.
+
+        See <link xlink:href="https://github.com/jitsi/jitsi-videobridge/blob/master/src/main/resources/reference.conf" />
+        for default configuration with comments.
+      '';
+    };
+
+    xmppConfigs = mkOption {
+      description = ''
+        XMPP servers to connect to.
+
+        See <link xlink:href="https://github.com/jitsi/jitsi-videobridge/blob/master/doc/muc.md" /> for more information.
+      '';
+      default = { };
+      example = literalExample ''
+        {
+          "localhost" = {
+            hostName = "localhost";
+            userName = "jvb";
+            domain = "auth.xmpp.example.org";
+            passwordFile = "/var/lib/jitsi-meet/videobridge-secret";
+            mucJids = "jvbbrewery@internal.xmpp.example.org";
+          };
+        }
+      '';
+      type = attrsOf (submodule ({ name, ... }: {
+        options = {
+          hostName = mkOption {
+            type = str;
+            example = "xmpp.example.org";
+            description = ''
+              Hostname of the XMPP server to connect to. Name of the attribute set is used by default.
+            '';
+          };
+          domain = mkOption {
+            type = nullOr str;
+            default = null;
+            example = "auth.xmpp.example.org";
+            description = ''
+              Domain part of JID of the XMPP user, if it is different from hostName.
+            '';
+          };
+          userName = mkOption {
+            type = str;
+            default = "jvb";
+            description = ''
+              User part of the JID.
+            '';
+          };
+          passwordFile = mkOption {
+            type = str;
+            example = "/run/keys/jitsi-videobridge-xmpp1";
+            description = ''
+              File containing the password for the user.
+            '';
+          };
+          mucJids = mkOption {
+            type = str;
+            example = "jvbbrewery@internal.xmpp.example.org";
+            description = ''
+              JID of the MUC to join. JiCoFo needs to be configured to join the same MUC.
+            '';
+          };
+          mucNickname = mkOption {
+            # Upstream DEBs use UUID, let's use hostname instead.
+            type = str;
+            description = ''
+              Videobridges use the same XMPP account and need to be distinguished by the
+              nickname (aka resource part of the JID). By default, system hostname is used.
+            '';
+          };
+          disableCertificateVerification = mkOption {
+            type = bool;
+            default = false;
+            description = ''
+              Whether to skip validation of the server's certificate.
+            '';
+          };
+        };
+        config = {
+          hostName = mkDefault name;
+          mucNickname = mkDefault (builtins.replaceStrings [ "." ] [ "-" ] (
+            config.networking.hostName + optionalString (config.networking.domain != null) ".${config.networking.domain}"
+          ));
+        };
+      }));
+    };
+
+    nat = {
+      localAddress = mkOption {
+        type = nullOr str;
+        default = null;
+        example = "192.168.1.42";
+        description = ''
+          Local address when running behind NAT.
+        '';
+      };
+
+      publicAddress = mkOption {
+        type = nullOr str;
+        default = null;
+        example = "1.2.3.4";
+        description = ''
+          Public address when running behind NAT.
+        '';
+      };
+    };
+
+    extraProperties = mkOption {
+      type = attrsOf str;
+      default = { };
+      description = ''
+        Additional Java properties passed to jitsi-videobridge.
+      '';
+    };
+
+    openFirewall = mkOption {
+      type = bool;
+      default = false;
+      description = ''
+        Whether to open ports in the firewall for the videobridge.
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+    users.groups.jitsi-meet = {};
+
+    services.jitsi-videobridge.extraProperties = optionalAttrs (cfg.nat.localAddress != null) {
+      "org.ice4j.ice.harvest.NAT_HARVESTER_LOCAL_ADDRESS" = cfg.nat.localAddress;
+      "org.ice4j.ice.harvest.NAT_HARVESTER_PUBLIC_ADDRESS" = cfg.nat.publicAddress;
+    };
+
+    systemd.services.jitsi-videobridge2 = let
+      jvbProps = {
+        "-Dnet.java.sip.communicator.SC_HOME_DIR_LOCATION" = "/etc/jitsi";
+        "-Dnet.java.sip.communicator.SC_HOME_DIR_NAME" = "videobridge";
+        "-Djava.util.logging.config.file" = "/etc/jitsi/videobridge/logging.properties";
+        "-Dconfig.file" = pkgs.writeText "jvb.conf" (toHOCON jvbConfig);
+      } // (mapAttrs' (k: v: nameValuePair "-D${k}" v) cfg.extraProperties);
+    in
+    {
+      aliases = [ "jitsi-videobridge.service" ];
+      description = "Jitsi Videobridge";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+
+      environment.JAVA_SYS_PROPS = attrsToArgs jvbProps;
+
+      script = (concatStrings (mapAttrsToList (name: xmppConfig:
+        "export ${toVarName name}=$(cat ${xmppConfig.passwordFile})\n"
+      ) cfg.xmppConfigs))
+      + ''
+        ${pkgs.jitsi-videobridge}/bin/jitsi-videobridge --apis=none
+      '';
+
+      serviceConfig = {
+        Type = "exec";
+
+        DynamicUser = true;
+        User = "jitsi-videobridge";
+        Group = "jitsi-meet";
+
+        CapabilityBoundingSet = "";
+        NoNewPrivileges = true;
+        ProtectSystem = "strict";
+        ProtectHome = true;
+        PrivateTmp = true;
+        PrivateDevices = true;
+        ProtectHostname = true;
+        ProtectKernelTunables = true;
+        ProtectKernelModules = true;
+        ProtectControlGroups = true;
+        RestrictAddressFamilies = [ "AF_INET" "AF_INET6" "AF_UNIX" ];
+        RestrictNamespaces = true;
+        LockPersonality = true;
+        RestrictRealtime = true;
+        RestrictSUIDSGID = true;
+
+        TasksMax = 65000;
+        LimitNPROC = 65000;
+        LimitNOFILE = 65000;
+      };
+    };
+
+    environment.etc."jitsi/videobridge/logging.properties".source =
+      mkDefault "${pkgs.jitsi-videobridge}/etc/jitsi/videobridge/logging.properties-journal";
+
+    # (from videobridge2 .deb)
+    # this sets the max, so that we can bump the JVB UDP single port buffer size.
+    boot.kernel.sysctl."net.core.rmem_max" = mkDefault 10485760;
+    boot.kernel.sysctl."net.core.netdev_max_backlog" = mkDefault 100000;
+
+    networking.firewall.allowedTCPPorts = mkIf cfg.openFirewall
+      [ jvbConfig.videobridge.ice.tcp.port ];
+    networking.firewall.allowedUDPPorts = mkIf cfg.openFirewall
+      [ jvbConfig.videobridge.ice.udp.port ];
+
+    assertions = [{
+      message = "publicAddress must be set if and only if localAddress is set";
+      assertion = (cfg.nat.publicAddress == null) == (cfg.nat.localAddress == null);
+    }];
+  };
+
+  meta.maintainers = lib.teams.jitsi.members;
+}
diff --git a/nixos/modules/services/networking/kresd.nix b/nixos/modules/services/networking/kresd.nix
index c5a84eebd46f4..ccb34163d5f36 100644
--- a/nixos/modules/services/networking/kresd.nix
+++ b/nixos/modules/services/networking/kresd.nix
@@ -129,14 +129,17 @@ in {
     systemd.services."kresd@".serviceConfig = {
       ExecStart = "${package}/bin/kresd --noninteractive "
         + "-c ${package}/lib/knot-resolver/distro-preconfig.lua -c ${configFile}";
-      # Ensure correct ownership in case UID or GID changes.
+      # Ensure /run/knot-resolver exists
+      RuntimeDirectory = "knot-resolver";
+      RuntimeDirectoryMode = "0770";
+      # Ensure /var/lib/knot-resolver exists
+      StateDirectory = "knot-resolver";
+      StateDirectoryMode = "0770";
+      # Ensure /var/cache/knot-resolver exists
       CacheDirectory = "knot-resolver";
-      CacheDirectoryMode = "0750";
+      CacheDirectoryMode = "0770";
     };
 
-    environment.etc."tmpfiles.d/knot-resolver.conf".source =
-      "${package}/lib/tmpfiles.d/knot-resolver.conf";
-
     # Try cleaning up the previously default location of cache file.
     # Note that /var/cache/* should always be safe to remove.
     # TODO: remove later, probably between 20.09 and 21.03
diff --git a/nixos/modules/services/networking/mstpd.nix b/nixos/modules/services/networking/mstpd.nix
index 5d1fc4a65427d..bd71010ce549c 100644
--- a/nixos/modules/services/networking/mstpd.nix
+++ b/nixos/modules/services/networking/mstpd.nix
@@ -5,7 +5,7 @@ in
 with lib;
 {
   options.services.mstpd = {
-    
+
     enable = mkOption {
       default = false;
       type = types.bool;
diff --git a/nixos/modules/services/networking/ncdns.nix b/nixos/modules/services/networking/ncdns.nix
new file mode 100644
index 0000000000000..c1832ad175205
--- /dev/null
+++ b/nixos/modules/services/networking/ncdns.nix
@@ -0,0 +1,278 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfgs = config.services;
+  cfg  = cfgs.ncdns;
+
+  dataDir  = "/var/lib/ncdns";
+  username = "ncdns";
+
+  valueType = with types; oneOf [ int str bool path ]
+    // { description = "setting type (integer, string, bool or path)"; };
+
+  configType = with types; attrsOf (nullOr (either valueType configType))
+    // { description = ''
+          ncdns.conf configuration type. The format consists of an
+          attribute set of settings. Each setting can be either `null`,
+          a value or an attribute set. The allowed values are integers,
+          strings, booleans or paths.
+         '';
+       };
+
+  configFile = pkgs.runCommand "ncdns.conf"
+    { json = builtins.toJSON cfg.settings;
+      passAsFile = [ "json" ];
+    }
+    "${pkgs.remarshal}/bin/json2toml < $jsonPath > $out";
+
+  defaultFiles = {
+    public  = "${dataDir}/bit.key";
+    private = "${dataDir}/bit.private";
+    zonePublic  = "${dataDir}/bit-zone.key";
+    zonePrivate = "${dataDir}/bit-zone.private";
+  };
+
+  # if all keys are the default value
+  needsKeygen = all id (flip mapAttrsToList cfg.dnssec.keys
+    (n: v: v == getAttr n defaultFiles));
+
+  mkDefaultAttrs = mapAttrs (n: v: mkDefault v);
+
+in
+
+{
+
+  ###### interface
+
+  options = {
+
+    services.ncdns = {
+
+      enable = mkEnableOption ''
+        ncdns, a Go daemon to bridge Namecoin to DNS.
+        To resolve .bit domains set <literal>services.namecoind.enable = true;</literal>
+        and an RPC username/password
+      '';
+
+      address = mkOption {
+        type = types.str;
+        default = "127.0.0.1";
+        description = ''
+          The IP address the ncdns resolver will bind to.  Leave this unchanged
+          if you do not wish to directly expose the resolver.
+        '';
+      };
+
+      port = mkOption {
+        type = types.port;
+        default = 5333;
+        description = ''
+          The port the ncdns resolver will bind to.
+        '';
+      };
+
+      identity.hostname = mkOption {
+        type = types.str;
+        default = config.networking.hostName;
+        example = "example.com";
+        description = ''
+          The hostname of this ncdns instance, which defaults to the machine
+          hostname. If specified, ncdns lists the hostname as an NS record at
+          the zone apex:
+          <programlisting>
+          bit. IN NS ns1.example.com.
+          </programlisting>
+          If unset ncdns will generate an internal psuedo-hostname under the
+          zone, which will resolve to the value of
+          <option>services.ncdns.identity.address</option>.
+          If you are only using ncdns locally you can ignore this.
+        '';
+      };
+
+      identity.hostmaster = mkOption {
+        type = types.str;
+        default = "";
+        example = "root@example.com";
+        description = ''
+          An email address for the SOA record at the bit zone.
+          If you are only using ncdns locally you can ignore this.
+        '';
+      };
+
+      identity.address = mkOption {
+        type = types.str;
+        default = "127.127.127.127";
+        description = ''
+          The IP address the hostname specified in
+          <option>services.ncdns.identity.hostname</option> should resolve to.
+          If you are only using ncdns locally you can ignore this.
+        '';
+      };
+
+      dnssec.enable = mkEnableOption ''
+        DNSSEC support in ncdns. This will generate KSK and ZSK keypairs
+        (unless provided via the options
+        <option>services.ncdns.dnssec.publicKey</option>,
+        <option>services.ncdns.dnssec.privateKey</option> etc.) and add a trust
+        anchor to recursive resolvers
+      '';
+
+      dnssec.keys.public = mkOption {
+        type = types.path;
+        default = defaultFiles.public;
+        description = ''
+          Path to the file containing the KSK public key.
+          The key can be generated using the <literal>dnssec-keygen</literal>
+          command, provided by the package <package>bind</package> as follows:
+          <programlisting>
+          $ dnssec-keygen -a RSASHA256 -3 -b 2048 -f KSK bit
+          </programlisting>
+        '';
+      };
+
+      dnssec.keys.private = mkOption {
+        type = types.path;
+        default = defaultFiles.private;
+        description = ''
+          Path to the file containing the KSK private key.
+        '';
+      };
+
+      dnssec.keys.zonePublic = mkOption {
+        type = types.path;
+        default = defaultFiles.zonePublic;
+        description = ''
+          Path to the file containing the ZSK public key.
+          The key can be generated using the <literal>dnssec-keygen</literal>
+          command, provided by the package <package>bind</package> as follows:
+          <programlisting>
+          $ dnssec-keygen -a RSASHA256 -3 -b 2048 bit
+          </programlisting>
+        '';
+      };
+
+      dnssec.keys.zonePrivate = mkOption {
+        type = types.path;
+        default = defaultFiles.zonePrivate;
+        description = ''
+          Path to the file containing the ZSK private key.
+        '';
+      };
+
+      settings = mkOption {
+        type = configType;
+        default = { };
+        example = literalExample ''
+          { # enable webserver
+            ncdns.httplistenaddr = ":8202";
+
+            # synchronize TLS certs
+            certstore.nss = true;
+            # note: all paths are relative to the config file
+            certstore.nsscertdir =  "../../var/lib/ncdns";
+            certstore.nssdbdir = "../../home/alice/.pki/nssdb";
+          }
+        '';
+        description = ''
+          ncdns settings. Use this option to configure ncds
+          settings not exposed in a NixOS option or to bypass one.
+          See the example ncdns.conf file at <link xlink:href="
+          https://git.io/JfX7g"/> for the available options.
+        '';
+      };
+
+    };
+
+    services.pdns-recursor.resolveNamecoin = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Resolve <literal>.bit</literal> top-level domains using ncdns and namecoin.
+      '';
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    services.pdns-recursor = mkIf cfgs.pdns-recursor.resolveNamecoin {
+      forwardZonesRecurse.bit = "127.0.0.1:${toString cfg.port}";
+      luaConfig =
+        if cfg.dnssec.enable
+          then ''readTrustAnchorsFromFile("${cfg.dnssec.keys.public}")''
+          else ''addNTA("bit", "namecoin DNSSEC disabled")'';
+    };
+
+    # Avoid pdns-recursor not finding the DNSSEC keys
+    systemd.services.pdns-recursor = mkIf cfgs.pdns-recursor.resolveNamecoin {
+      after = [ "ncdns.service" ];
+      wants = [ "ncdns.service" ];
+    };
+
+    services.ncdns.settings = mkDefaultAttrs {
+      ncdns =
+        { # Namecoin RPC
+          namecoinrpcaddress =
+            "${cfgs.namecoind.rpc.address}:${toString cfgs.namecoind.rpc.port}";
+          namecoinrpcusername = cfgs.namecoind.rpc.user;
+          namecoinrpcpassword = cfgs.namecoind.rpc.password;
+
+          # Identity
+          selfname = cfg.identity.hostname;
+          hostmaster = cfg.identity.hostmaster;
+          selfip = cfg.identity.address;
+
+          # Other
+          bind = "${cfg.address}:${toString cfg.port}";
+        }
+        // optionalAttrs cfg.dnssec.enable
+        { # DNSSEC
+          publickey  = "../.." + cfg.dnssec.keys.public;
+          privatekey = "../.." + cfg.dnssec.keys.private;
+          zonepublickey  = "../.." + cfg.dnssec.keys.zonePublic;
+          zoneprivatekey = "../.." + cfg.dnssec.keys.zonePrivate;
+        };
+
+        # Daemon
+        service.daemon = true;
+        xlog.journal = true;
+    };
+
+    users.users.ncdns =
+      { description = "ncdns daemon user"; };
+
+    systemd.services.ncdns = {
+      description = "ncdns daemon";
+      after    = [ "namecoind.service" ];
+      wantedBy = [ "multi-user.target" ];
+
+      serviceConfig = {
+        User = "ncdns";
+        StateDirectory = "ncdns";
+        Restart = "on-failure";
+        ExecStart = "${pkgs.ncdns}/bin/ncdns -conf=${configFile}";
+      };
+
+      preStart = optionalString (cfg.dnssec.enable && needsKeygen) ''
+        cd ${dataDir}
+        if [ ! -e bit.key ]; then
+          ${pkgs.bind}/bin/dnssec-keygen -a RSASHA256 -3 -b 2048 bit
+          mv Kbit.*.key bit-zone.key
+          mv Kbit.*.private bit-zone.private
+          ${pkgs.bind}/bin/dnssec-keygen -a RSASHA256 -3 -b 2048 -f KSK bit
+          mv Kbit.*.key bit.key
+          mv Kbit.*.private bit.private
+        fi
+      '';
+    };
+
+  };
+
+  meta.maintainers = with lib.maintainers; [ rnhmjoj ];
+
+}
diff --git a/nixos/modules/services/networking/nextdns.nix b/nixos/modules/services/networking/nextdns.nix
new file mode 100644
index 0000000000000..a633bff62ec7e
--- /dev/null
+++ b/nixos/modules/services/networking/nextdns.nix
@@ -0,0 +1,44 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.nextdns;
+in {
+  options = {
+    services.nextdns = {
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Whether to enable the NextDNS DNS/53 to DoH Proxy service.";
+      };
+      arguments = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        example = [ "-config" "10.0.3.0/24=abcdef" ];
+        description = "Additional arguments to be passed to nextdns run.";
+      };
+    };
+  };
+
+  # https://github.com/nextdns/nextdns/blob/628ea509eaaccd27adb66337db03e5b56f6f38a8/host/service/systemd/service.go
+  config = mkIf cfg.enable {
+    systemd.services.nextdns = {
+      description = "NextDNS DNS/53 to DoH Proxy";
+      environment = {
+        SERVICE_RUN_MODE = "1";
+      };
+      serviceConfig = {
+        StartLimitInterval = 5;
+        StartLimitBurst = 10;
+        ExecStart = "${pkgs.nextdns}/bin/nextdns run ${escapeShellArgs config.services.nextdns.arguments}";
+        RestartSec = 120;
+        LimitMEMLOCK = "infinity";
+      };
+      after = [ "network.target" ];
+      before = [ "nss-lookup.target" ];
+      wants = [ "nss-lookup.target" ];
+      wantedBy = [ "multi-user.target" ];
+    };
+  };
+}
diff --git a/nixos/modules/services/networking/nghttpx/default.nix b/nixos/modules/services/networking/nghttpx/default.nix
index 881a2670f5db0..b8a0a24e3aad1 100644
--- a/nixos/modules/services/networking/nghttpx/default.nix
+++ b/nixos/modules/services/networking/nghttpx/default.nix
@@ -60,7 +60,7 @@ let
       # NB: nghttpx doesn't accept "tls", you must omit "no-tls" for
       # the default behavior of turning on TLS.
       params1 = lib.remove "tls" params0;
-          
+
       sections          = [ host] ++ params1;
       formattedSections = lib.concatStringsSep ";" sections;
     in
@@ -90,7 +90,7 @@ in
 { imports = [
     ./nghttpx-options.nix
   ];
-  
+
   config = lib.mkIf cfg.enable {
 
     users.groups.nghttpx = { };
@@ -98,7 +98,7 @@ in
       group = config.users.groups.nghttpx.name;
       isSystemUser = true;
     };
-      
+
 
     systemd.services = {
       nghttpx = {
diff --git a/nixos/modules/services/networking/nsd.nix b/nixos/modules/services/networking/nsd.nix
index 6e3eed0c55700..3ecbd06ee416b 100644
--- a/nixos/modules/services/networking/nsd.nix
+++ b/nixos/modules/services/networking/nsd.nix
@@ -11,8 +11,6 @@ let
 
   # build nsd with the options needed for the given config
   nsdPkg = pkgs.nsd.override {
-    configFile = "${configFile}/nsd.conf";
-
     bind8Stats = cfg.bind8Stats;
     ipv6 = cfg.ipv6;
     ratelimit = cfg.ratelimit.enable;
@@ -897,7 +895,10 @@ in
               + "want, please enable 'services.nsd.rootServer'.";
     };
 
-    environment.systemPackages = [ nsdPkg ];
+    environment = {
+      systemPackages = [ nsdPkg ];
+      etc."nsd/nsd.conf".source = "${configFile}/nsd.conf";
+    };
 
     users.groups.${username}.gid = config.ids.gids.nsd;
 
diff --git a/nixos/modules/services/networking/onedrive.nix b/nixos/modules/services/networking/onedrive.nix
new file mode 100644
index 0000000000000..210d2217b27fc
--- /dev/null
+++ b/nixos/modules/services/networking/onedrive.nix
@@ -0,0 +1,72 @@
+{ config, lib, pkgs, ... }:
+let
+  cfg = config.services.onedrive;
+
+  onedriveLauncher =  pkgs.writeShellScriptBin
+    "onedrive-launcher"
+    ''
+      # XDG_CONFIG_HOME is not recognized in the environment here.
+      if [ -f $HOME/.config/onedrive-launcher ]
+      then
+        # Hopefully using underscore boundary helps locate variables
+        for _onedrive_config_dirname_ in $(cat $HOME/.config/onedrive-launcher | grep -v '[ \t]*#' )
+        do
+          systemctl --user start onedrive@$_onedrive_config_dirname_
+        done
+      else
+        systemctl --user start onedrive@onedrive
+      fi
+    ''
+  ;
+
+in {
+  ### Documentation
+  # meta.doc = ./onedrive.xml;
+
+  ### Interface
+
+  options.services.onedrive = {
+    enable = lib.mkOption {
+      type = lib.types.bool;
+      default = false;
+      description = "Enable OneDrive service";
+    };
+
+     package = lib.mkOption {
+       type = lib.types.package;
+       default = pkgs.onedrive;
+       defaultText = "pkgs.onedrive";
+       example = lib.literalExample "pkgs.onedrive";
+       description = ''
+         OneDrive package to use.
+       '';
+     };
+  };
+### Implementation
+
+  config = lib.mkIf cfg.enable {
+    environment.systemPackages = [ cfg.package ];
+
+    systemd.user.services."onedrive@" = {
+      description = "Onedrive sync service";
+
+      serviceConfig = {
+        Type = "simple";
+        ExecStart = ''
+          ${cfg.package}/bin/onedrive --monitor --verbose --confdir=%h/.config/%i
+        '';
+        Restart="on-failure";
+        RestartSec=3;
+        RestartPreventExitStatus=3;
+      };
+    };
+
+    systemd.user.services.onedrive-launcher = {
+      wantedBy = [ "default.target" ];
+      serviceConfig = {
+        Type = "oneshot";
+        ExecStart = "${onedriveLauncher}/bin/onedrive-launcher";
+      };
+    };
+  };
+}
diff --git a/nixos/modules/services/networking/onedrive.xml b/nixos/modules/services/networking/onedrive.xml
new file mode 100644
index 0000000000000..5a9dcf01aeee9
--- /dev/null
+++ b/nixos/modules/services/networking/onedrive.xml
@@ -0,0 +1,34 @@
+<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="onedrive">
+ <title>Microsoft OneDrive</title>
+ <para>
+  Microsoft Onedrive is a popular cloud file-hosting service, used by 85% of Fortune 500 companies. NixOS uses a popular OneDrive client for Linux maintained by github user abraunegg. The Linux client is excellent and allows customization of which files or paths to download, not much unlike the default Windows OneDrive client by Microsoft itself. The client allows syncing with multiple onedrive accounts at the same time, of any type- OneDrive personal, OneDrive business, Office365 and Sharepoint libraries, without any additional charge.
+ </para>
+ <para>
+  For more information, guides and documentation, see <link xlink:href="https://abraunegg.github.io/"/>.
+ </para>
+ <para>
+  To enable OneDrive support, add the following to your <filename>configuration.nix</filename>:
+<programlisting>
+<xref linkend="opt-services.onedrive.enable"/> = true;
+</programlisting>
+  This installs the <literal>onedrive</literal> package and a service <literal>onedriveLauncher</literal> which will instantiate a <literal>onedrive</literal> service for all your OneDrive accounts. Follow the steps in documentation of the onedrive client to setup your accounts. To use the service with multiple accounts, create a file named <filename>onedrive-launcher</filename> in <filename>~/.config</filename> and add the filename of the config directory, relative to <filename>~/.config</filename>. For example, if you have two OneDrive accounts with configs in <filename>~/.config/onedrive_bob_work</filename> and <filename>~/.config/onedrive_bob_personal</filename>, add the following lines:
+<programlisting>
+onedrive_bob_work
+# Not in use:
+# onedrive_bob_office365
+onedrive_bob_personal
+</programlisting>
+  No such file needs to be created if you are using only a single OneDrive account with config in the default location <filename>~/.config/onedrive</filename>, in the absence of <filename>~/.config/onedrive-launcher</filename>, only a single service is instantiated, with default config path.
+</para>
+
+  <para>
+  If you wish to use a custom OneDrive package, say from another channel, add the following line:
+<programlisting>
+<xref linkend="opt-services.onedrive.package"/> = pkgs.unstable.onedrive;
+</programlisting>
+ </para>
+</chapter>
diff --git a/nixos/modules/services/networking/prosody.nix b/nixos/modules/services/networking/prosody.nix
index cdd341c9fb62e..e53d7093be864 100644
--- a/nixos/modules/services/networking/prosody.nix
+++ b/nixos/modules/services/networking/prosody.nix
@@ -772,7 +772,7 @@ in
       };
 
       disco_items = {
-      ${ lib.concatStringsSep "\n" (builtins.map (x: ''{ "${x.url}", "${x.description}"};'') discoItems)} 
+      ${ lib.concatStringsSep "\n" (builtins.map (x: ''{ "${x.url}", "${x.description}"};'') discoItems)}
       };
 
       allow_registration = ${toLua cfg.allowRegistration}
diff --git a/nixos/modules/services/networking/radicale.nix b/nixos/modules/services/networking/radicale.nix
index 30bf22586f86f..5af035fd59e05 100644
--- a/nixos/modules/services/networking/radicale.nix
+++ b/nixos/modules/services/networking/radicale.nix
@@ -8,8 +8,10 @@ let
 
   confFile = pkgs.writeText "radicale.conf" cfg.config;
 
-  # This enables us to default to version 2 while still not breaking configurations of people with version 1
-  defaultPackage = if versionAtLeast config.system.stateVersion "17.09" then {
+  defaultPackage = if versionAtLeast config.system.stateVersion "20.09" then {
+    pkg = pkgs.radicale3;
+    text = "pkgs.radicale3";
+  } else if versionAtLeast config.system.stateVersion "17.09" then {
     pkg = pkgs.radicale2;
     text = "pkgs.radicale2";
   } else {
@@ -35,8 +37,9 @@ in
       defaultText = defaultPackage.text;
       description = ''
         Radicale package to use. This defaults to version 1.x if
-        <literal>system.stateVersion &lt; 17.09</literal> and version 2.x
-        otherwise.
+        <literal>system.stateVersion &lt; 17.09</literal>, version 2.x if
+        <literal>17.09 ≤ system.stateVersion &lt; 20.09</literal>, and
+        version 3.x otherwise.
       '';
     };
 
diff --git a/nixos/modules/services/networking/resilio.nix b/nixos/modules/services/networking/resilio.nix
index 266318296d7ad..6193d7340fc4a 100644
--- a/nixos/modules/services/networking/resilio.nix
+++ b/nixos/modules/services/networking/resilio.nix
@@ -30,12 +30,12 @@ let
     download_limit = cfg.downloadLimit;
     upload_limit = cfg.uploadLimit;
     lan_encrypt_data = cfg.encryptLAN;
-  } // optionalAttrs cfg.enableWebUI {
+  } // optionalAttrs (cfg.directoryRoot != "") { directory_root = cfg.directoryRoot; }
+    // optionalAttrs cfg.enableWebUI {
     webui = { listen = "${cfg.httpListenAddr}:${toString cfg.httpListenPort}"; } //
       (optionalAttrs (cfg.httpLogin != "") { login = cfg.httpLogin; }) //
       (optionalAttrs (cfg.httpPass != "") { password = cfg.httpPass; }) //
-      (optionalAttrs (cfg.apiKey != "") { api_key = cfg.apiKey; }) //
-      (optionalAttrs (cfg.directoryRoot != "") { directory_root = cfg.directoryRoot; });
+      (optionalAttrs (cfg.apiKey != "") { api_key = cfg.apiKey; });
   } // optionalAttrs (sharedFoldersRecord != []) {
     shared_folders = sharedFoldersRecord;
   }));
diff --git a/nixos/modules/services/networking/skydns.nix b/nixos/modules/services/networking/skydns.nix
index e79d6de926449..ea466de932750 100644
--- a/nixos/modules/services/networking/skydns.nix
+++ b/nixos/modules/services/networking/skydns.nix
@@ -64,7 +64,7 @@ in {
     extraConfig = mkOption {
       default = {};
       type = types.attrsOf types.str;
-      description = "Skydns attribute set of extra config options passed as environemnt variables.";
+      description = "Skydns attribute set of extra config options passed as environment variables.";
     };
   };
 
diff --git a/nixos/modules/services/networking/sslh.nix b/nixos/modules/services/networking/sslh.nix
index c4fa370a5fef3..0921febba668a 100644
--- a/nixos/modules/services/networking/sslh.nix
+++ b/nixos/modules/services/networking/sslh.nix
@@ -15,7 +15,11 @@ let
 
     listen:
     (
-      { host: "${cfg.listenAddress}"; port: "${toString cfg.port}"; }
+      ${
+        concatMapStringsSep ",\n"
+        (addr: ''{ host: "${addr}"; port: "${toString cfg.port}"; }'')
+        cfg.listenAddresses
+      }
     );
 
     ${cfg.appendConfig}
@@ -33,6 +37,10 @@ let
   '';
 in
 {
+  imports = [
+    (mkRenamedOptionModule [ "services" "sslh" "listenAddress" ] [ "services" "sslh" "listenAddresses" ])
+  ];
+
   options = {
     services.sslh = {
       enable = mkEnableOption "sslh";
@@ -55,10 +63,10 @@ in
         description = "Will the services behind sslh (Apache, sshd and so on) see the external IP and ports as if the external world connected directly to them";
       };
 
-      listenAddress = mkOption {
-        type = types.str;
-        default = "0.0.0.0";
-        description = "Listening address or hostname.";
+      listenAddresses = mkOption {
+        type = types.coercedTo types.str singleton (types.listOf types.str);
+        default = [ "0.0.0.0" "[::]" ];
+        description = "Listening addresses or hostnames.";
       };
 
       port = mkOption {
diff --git a/nixos/modules/services/networking/supplicant.nix b/nixos/modules/services/networking/supplicant.nix
index b5b9989ce1863..20704be9b36fe 100644
--- a/nixos/modules/services/networking/supplicant.nix
+++ b/nixos/modules/services/networking/supplicant.nix
@@ -76,9 +76,9 @@ in
     networking.supplicant = mkOption {
       type = with types; attrsOf (submodule {
         options = {
-  
+
           configFile = {
-  
+
             path = mkOption {
               type = types.nullOr types.path;
               default = null;
@@ -89,7 +89,7 @@ in
                 precedence over options defined in <literal>configFile</literal>.
               '';
             };
-  
+
             writable = mkOption {
               type = types.bool;
               default = false;
@@ -98,9 +98,9 @@ in
                 <literal>wpa_supplicant</literal>.
               '';
             };
-  
+
           };
-  
+
           extraConf = mkOption {
             type = types.lines;
             default = "";
@@ -126,7 +126,7 @@ in
               use the <literal>configFile</literal> instead.
             '';
           };
-  
+
           extraCmdArgs = mkOption {
             type = types.str;
             default = "";
@@ -134,21 +134,21 @@ in
             description =
               "Command line arguments to add when executing <literal>wpa_supplicant</literal>.";
           };
-  
+
           driver = mkOption {
             type = types.nullOr types.str;
             default = "nl80211,wext";
             description = "Force a specific wpa_supplicant driver.";
           };
-  
+
           bridge = mkOption {
             type = types.str;
             default = "";
             description = "Name of the bridge interface that wpa_supplicant should listen at.";
           };
-  
+
           userControlled = {
-  
+
             enable = mkOption {
               type = types.bool;
               default = false;
@@ -159,20 +159,20 @@ in
                 access points.
               '';
             };
-  
+
             socketDir = mkOption {
               type = types.str;
               default = "/run/wpa_supplicant";
               description = "Directory of sockets for controlling wpa_supplicant.";
             };
-  
+
             group = mkOption {
               type = types.str;
               default = "wheel";
               example = "network";
               description = "Members of this group can control wpa_supplicant.";
             };
-  
+
           };
         };
       });
diff --git a/nixos/modules/services/networking/tinc.nix b/nixos/modules/services/networking/tinc.nix
index e98aafc209372..725bd9bf9403b 100644
--- a/nixos/modules/services/networking/tinc.nix
+++ b/nixos/modules/services/networking/tinc.nix
@@ -48,6 +48,14 @@ in
               '';
             };
 
+            rsaPrivateKeyFile = mkOption {
+              default = null;
+              type = types.nullOr types.path;
+              description = ''
+                Path of the private RSA keyfile.
+              '';
+            };
+
             debugLevel = mkOption {
               default = 0;
               type = types.addCheck types.int (l: l >= 0 && l <= 5);
@@ -139,6 +147,7 @@ in
               Name = ${if data.name == null then "$HOST" else data.name}
               DeviceType = ${data.interfaceType}
               ${optionalString (data.ed25519PrivateKeyFile != null) "Ed25519PrivateKeyFile = ${data.ed25519PrivateKeyFile}"}
+              ${optionalString (data.rsaPrivateKeyFile != null) "PrivateKeyFile = ${data.rsaPrivateKeyFile}"}
               ${optionalString (data.listenAddress != null) "ListenAddress = ${data.listenAddress}"}
               ${optionalString (data.bindToAddress != null) "BindToAddress = ${data.bindToAddress}"}
               Interface = tinc.${network}
@@ -170,12 +179,15 @@ in
           # Determine how we should generate our keys
           if type tinc >/dev/null 2>&1; then
             # Tinc 1.1+ uses the tinc helper application for key generation
-          ${if data.ed25519PrivateKeyFile != null then "  # Keyfile managed by nix" else ''
+          ${if data.ed25519PrivateKeyFile != null then "  # ed25519 Keyfile managed by nix" else ''
             # Prefer ED25519 keys (only in 1.1+)
             [ -f "/etc/tinc/${network}/ed25519_key.priv" ] || tinc -n ${network} generate-ed25519-keys
           ''}
-            # Otherwise use RSA keys
+          ${if data.rsaPrivateKeyFile != null then "  # RSA Keyfile managed by nix" else ''
             [ -f "/etc/tinc/${network}/rsa_key.priv" ] || tinc -n ${network} generate-rsa-keys 4096
+          ''}
+            # In case there isn't anything to do
+            true
           else
             # Tinc 1.0 uses the tincd application
             [ -f "/etc/tinc/${network}/rsa_key.priv" ] || tincd -n ${network} -K 4096
diff --git a/nixos/modules/services/networking/trickster.nix b/nixos/modules/services/networking/trickster.nix
index 8760dd5a93827..49c945adb80f3 100644
--- a/nixos/modules/services/networking/trickster.nix
+++ b/nixos/modules/services/networking/trickster.nix
@@ -106,7 +106,8 @@ in
         Restart = "always";
       };
     };
+  };
 
-  };  
-}
+  meta.maintainers = with maintainers; [ _1000101 ];
 
+}
diff --git a/nixos/modules/services/networking/unifi.nix b/nixos/modules/services/networking/unifi.nix
index 4bdfa8143dce0..62bcf7a149726 100644
--- a/nixos/modules/services/networking/unifi.nix
+++ b/nixos/modules/services/networking/unifi.nix
@@ -162,6 +162,8 @@ in
       unitConfig.RequiresMountsFor = stateDir;
       # This a HACK to fix missing dependencies of dynamic libs extracted from jars
       environment.LD_LIBRARY_PATH = with pkgs.stdenv; "${cc.cc.lib}/lib";
+      # Make sure package upgrades trigger a service restart
+      restartTriggers = [ cfg.unifiPackage cfg.mongodbPackage ];
 
       serviceConfig = {
         Type = "simple";
diff --git a/nixos/modules/services/networking/websockify.nix b/nixos/modules/services/networking/websockify.nix
index d9177df65bd62..27cb47be12f7e 100644
--- a/nixos/modules/services/networking/websockify.nix
+++ b/nixos/modules/services/networking/websockify.nix
@@ -5,12 +5,12 @@ with lib;
 let cfg = config.services.networking.websockify; in {
   options = {
     services.networking.websockify = {
-      enable = mkOption {  
+      enable = mkOption {
         description = "Whether to enable websockify to forward websocket connections to TCP connections.";
 
-        default = false;   
+        default = false;
 
-        type = types.bool; 
+        type = types.bool;
       };
 
       sslCert = mkOption {
diff --git a/nixos/modules/services/networking/wg-quick.nix b/nixos/modules/services/networking/wg-quick.nix
index ff1bdeed9f481..02fe40a22a102 100644
--- a/nixos/modules/services/networking/wg-quick.nix
+++ b/nixos/modules/services/networking/wg-quick.nix
@@ -29,7 +29,7 @@ let
         type = with types; nullOr str;
         default = null;
         description = ''
-          Base64 private key generated by wg genkey.
+          Base64 private key generated by <command>wg genkey</command>.
 
           Warning: Consider using privateKeyFile instead if you do not
           want to store the key in the world-readable Nix store.
@@ -41,7 +41,7 @@ let
         type = with types; nullOr str;
         default = null;
         description = ''
-          Private key file as generated by wg genkey.
+          Private key file as generated by <command>wg genkey</command>.
         '';
       };
 
@@ -106,9 +106,9 @@ let
         description = ''
           The kernel routing table to add this interface's
           associated routes to. Setting this is useful for e.g. policy routing
-          ("ip rule") or virtual routing and forwarding ("ip vrf"). Both numeric
-          table IDs and table names (/etc/rt_tables) can be used. Defaults to
-          "main".
+          ("ip rule") or virtual routing and forwarding ("ip vrf"). Both
+          numeric table IDs and table names (/etc/rt_tables) can be used.
+          Defaults to "main".
         '';
       };
 
@@ -139,7 +139,7 @@ let
       publicKey = mkOption {
         example = "xTIBA5rboUvnH4htodjb6e697QjLERt1NAB4mZqp8Dg=";
         type = types.str;
-        description = "The base64 public key the peer.";
+        description = "The base64 public key to the peer.";
       };
 
       presharedKey = mkOption {
@@ -147,8 +147,8 @@ let
         example = "rVXs/Ni9tu3oDBLS4hOyAUAa1qTWVA3loR8eL20os3I=";
         type = with types; nullOr str;
         description = ''
-          Base64 preshared key generated by wg genpsk. Optional,
-          and may be omitted. This option adds an additional layer of
+          Base64 preshared key generated by <command>wg genpsk</command>.
+          Optional, and may be omitted. This option adds an additional layer of
           symmetric-key cryptography to be mixed into the already existing
           public-key cryptography, for post-quantum resistance.
 
@@ -162,8 +162,8 @@ let
         example = "/private/wireguard_psk";
         type = with types; nullOr str;
         description = ''
-          File pointing to preshared key as generated by wg pensk. Optional,
-          and may be omitted. This option adds an additional layer of
+          File pointing to preshared key as generated by <command>wg genpsk</command>.
+          Optional, and may be omitted. This option adds an additional layer of
           symmetric-key cryptography to be mixed into the already existing
           public-key cryptography, for post-quantum resistance.
         '';
diff --git a/nixos/modules/services/networking/wireguard.nix b/nixos/modules/services/networking/wireguard.nix
index e8f83f6dd8bf8..e07020349cf4f 100644
--- a/nixos/modules/services/networking/wireguard.nix
+++ b/nixos/modules/services/networking/wireguard.nix
@@ -91,11 +91,13 @@ let
       table = mkOption {
         default = "main";
         type = types.str;
-        description = ''The kernel routing table to add this interface's
-        associated routes to. Setting this is useful for e.g. policy routing
-        ("ip rule") or virtual routing and forwarding ("ip vrf"). Both numeric
-        table IDs and table names (/etc/rt_tables) can be used. Defaults to
-        "main".'';
+        description = ''
+          The kernel routing table to add this interface's
+          associated routes to. Setting this is useful for e.g. policy routing
+          ("ip rule") or virtual routing and forwarding ("ip vrf"). Both
+          numeric table IDs and table names (/etc/rt_tables) can be used.
+          Defaults to "main".
+        '';
       };
 
       peers = mkOption {
@@ -174,7 +176,7 @@ let
         example = "/private/wireguard_psk";
         type = with types; nullOr str;
         description = ''
-          File pointing to preshared key as generated by <command>wg pensk</command>.
+          File pointing to preshared key as generated by <command>wg genpsk</command>.
           Optional, and may be omitted. This option adds an additional layer of
           symmetric-key cryptography to be mixed into the already existing
           public-key cryptography, for post-quantum resistance.
@@ -217,7 +219,6 @@ let
 
   };
 
-
   generatePathUnit = name: values:
     assert (values.privateKey == null);
     assert (values.privateKeyFile != null);
diff --git a/nixos/modules/services/networking/wpa_supplicant.nix b/nixos/modules/services/networking/wpa_supplicant.nix
index a7dea95056a0e..08a17d20ed7fd 100644
--- a/nixos/modules/services/networking/wpa_supplicant.nix
+++ b/nixos/modules/services/networking/wpa_supplicant.nix
@@ -4,7 +4,7 @@ with lib;
 
 let
   cfg = config.networking.wireless;
-  configFile = if cfg.networks != {} then pkgs.writeText "wpa_supplicant.conf" ''
+  configFile = if cfg.networks != {} || cfg.extraConfig != "" || cfg.userControlled.enable then pkgs.writeText "wpa_supplicant.conf" ''
     ${optionalString cfg.userControlled.enable ''
       ctrl_interface=DIR=/run/wpa_supplicant GROUP=${cfg.userControlled.group}
       update_config=1''}
diff --git a/nixos/modules/services/networking/xandikos.nix b/nixos/modules/services/networking/xandikos.nix
index 87c029156b9e7..3c40bb956f57e 100644
--- a/nixos/modules/services/networking/xandikos.nix
+++ b/nixos/modules/services/networking/xandikos.nix
@@ -90,7 +90,7 @@ in
   config = mkIf cfg.enable (
     mkMerge [
       {
-        meta.maintainers = [ lib.maintainers."0x4A6F" ];
+        meta.maintainers = with lib.maintainers; [ _0x4A6F ];
 
         systemd.services.xandikos = {
           description = "A Simple Calendar and Contact Server";
@@ -122,7 +122,7 @@ in
             ExecStart = ''
               ${cfg.package}/bin/xandikos \
                 --directory /var/lib/xandikos \
-                --listen_address ${cfg.address} \
+                --listen-address ${cfg.address} \
                 --port ${toString cfg.port} \
                 --route-prefix ${cfg.routePrefix} \
                 ${lib.concatStringsSep " " cfg.extraOptions}
diff --git a/nixos/modules/services/networking/yggdrasil.nix b/nixos/modules/services/networking/yggdrasil.nix
index 0fe9a200a1b60..a71c635c9f6ef 100644
--- a/nixos/modules/services/networking/yggdrasil.nix
+++ b/nixos/modules/services/networking/yggdrasil.nix
@@ -195,5 +195,8 @@ in {
     # Make yggdrasilctl available on the command line.
     environment.systemPackages = [ cfg.package ];
   });
-  meta.maintainers = with lib.maintainers; [ gazally ehmry ];
+  meta = {
+    doc = ./yggdrasil.xml;
+    maintainers = with lib.maintainers; [ gazally ehmry ];
+  };
 }
diff --git a/nixos/modules/services/networking/yggdrasil.xml b/nixos/modules/services/networking/yggdrasil.xml
new file mode 100644
index 0000000000000..c012cd4a92949
--- /dev/null
+++ b/nixos/modules/services/networking/yggdrasil.xml
@@ -0,0 +1,157 @@
+<?xml version="1.0"?>
+<chapter xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:xi="http://www.w3.org/2001/XInclude" version="5.0" xml:id="module-services-networking-yggdrasil">
+  <title>Yggdrasil</title>
+  <para>
+    <emphasis>Source:</emphasis>
+    <filename>modules/services/networking/yggdrasil/default.nix</filename>
+  </para>
+  <para>
+    <emphasis>Upstream documentation:</emphasis>
+    <link xlink:href="https://yggdrasil-network.github.io/"/>
+  </para>
+  <para>
+Yggdrasil is an early-stage implementation of a fully end-to-end encrypted,
+self-arranging IPv6 network.
+</para>
+  <section xml:id="module-services-networking-yggdrasil-configuration">
+    <title>Configuration</title>
+    <section xml:id="module-services-networking-yggdrasil-configuration-simple">
+      <title>Simple ephemeral node</title>
+      <para>
+An annotated example of a simple configuration:
+<programlisting>
+{
+  services.yggdrasil = {
+    enable = true;
+    persistentKeys = false;
+      # The NixOS module will generate new keys and a new IPv6 address each time
+      # it is started if persistentKeys is not enabled.
+
+    config = {
+      Peers = [
+        # Yggdrasil will automatically connect and "peer" with other nodes it
+        # discovers via link-local multicast annoucements. Unless this is the
+        # case (it probably isn't) a node needs peers within the existing
+        # network that it can tunnel to.
+        "tcp://1.2.3.4:1024"
+        "tcp://1.2.3.5:1024"
+        # Public peers can be found at
+        # https://github.com/yggdrasil-network/public-peers
+      ];
+    };
+  };
+}
+</programlisting>
+   </para>
+    </section>
+    <section xml:id="module-services-networking-yggdrasil-configuration-prefix">
+      <title>Persistent node with prefix</title>
+      <para>
+A node with a fixed address that announces a prefix:
+<programlisting>
+let
+  address = "210:5217:69c0:9afc:1b95:b9f:8718:c3d2";
+  prefix = "310:5217:69c0:9afc";
+  # taken from the output of "yggdrasilctl getself".
+in {
+
+  services.yggdrasil = {
+    enable = true;
+    persistentKeys = true; # Maintain a fixed public key and IPv6 address.
+    config = {
+      Peers = [ "tcp://1.2.3.4:1024" "tcp://1.2.3.5:1024" ];
+      NodeInfo = {
+        # This information is visible to the network.
+        name = config.networking.hostName;
+        location = "The North Pole";
+      };
+    };
+  };
+
+  boot.kernel.sysctl."net.ipv6.conf.all.forwarding" = 1;
+    # Forward traffic under the prefix.
+
+  networking.interfaces.${eth0}.ipv6.addresses = [{
+    # Set a 300::/8 address on the local physical device.
+    address = prefix + "::1";
+    prefixLength = 64;
+  }];
+
+  services.radvd = {
+    # Annouce the 300::/8 prefix to eth0.
+    enable = true;
+    config = ''
+      interface eth0
+      {
+        AdvSendAdvert on;
+        AdvDefaultLifetime 0;
+        prefix ${prefix}::/64 {
+          AdvOnLink on;
+          AdvAutonomous on;
+        };
+        route 200::/8 {};
+      };
+    '';
+  };
+}
+</programlisting>
+  </para>
+    </section>
+    <section xml:id="module-services-networking-yggdrasil-configuration-container">
+      <title>Yggdrasil attached Container</title>
+      <para>
+A NixOS container attached to the Yggdrasil network via a node running on the
+host:
+        <programlisting>
+let
+  yggPrefix64 = "310:5217:69c0:9afc";
+    # Again, taken from the output of "yggdrasilctl getself".
+in
+{
+  boot.kernel.sysctl."net.ipv6.conf.all.forwarding" = 1;
+  # Enable IPv6 forwarding.
+
+  networking = {
+    bridges.br0.interfaces = [ ];
+    # A bridge only to containers&#x2026;
+
+    interfaces.br0 = {
+      # &#x2026; configured with a prefix address.
+      ipv6.addresses = [{
+        address = "${yggPrefix64}::1";
+        prefixLength = 64;
+      }];
+    };
+  };
+
+  containers.foo = {
+    autoStart = true;
+    privateNetwork = true;
+    hostBridge = "br0";
+    # Attach the container to the bridge only.
+    config = { config, pkgs, ... }: {
+      networking.interfaces.eth0.ipv6 = {
+        addresses = [{
+          # Configure a prefix address.
+          address = "${yggPrefix64}::2";
+          prefixLength = 64;
+        }];
+        routes = [{
+          # Configure the prefix route.
+          address = "200::";
+          prefixLength = 7;
+          via = "${yggPrefix64}::1";
+        }];
+      };
+
+      services.httpd.enable = true;
+      networking.firewall.allowedTCPPorts = [ 80 ];
+    };
+  };
+
+}
+</programlisting>
+      </para>
+    </section>
+  </section>
+</chapter>
diff --git a/nixos/modules/services/scheduling/chronos.nix b/nixos/modules/services/scheduling/chronos.nix
deleted file mode 100644
index 9a8ed4c09ac1c..0000000000000
--- a/nixos/modules/services/scheduling/chronos.nix
+++ /dev/null
@@ -1,54 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-with lib;
-
-let
-  cfg = config.services.chronos;
-
-in {
-
-  ###### interface
-
-  options.services.chronos = {
-    enable = mkOption {
-      description = "Whether to enable graphite web frontend.";
-      default = false;
-      type = types.bool;
-    };
-
-    httpPort = mkOption {
-      description = "Chronos listening port";
-      default = 4400;
-      type = types.int;
-    };
-
-    master = mkOption {
-      description = "Chronos mesos master zookeeper address";
-      default = "zk://${head cfg.zookeeperHosts}/mesos";
-      type = types.str;
-    };
-
-    zookeeperHosts = mkOption {
-      description = "Chronos mesos zookepper addresses";
-      default = [ "localhost:2181" ];
-      type = types.listOf types.str;
-    };
-  };
-
-  ###### implementation
-
-  config = mkIf cfg.enable {
-    systemd.services.chronos = {
-      description = "Chronos Service";
-      wantedBy = [ "multi-user.target" ];
-      after = [ "network.target" "zookeeper.service" ];
-
-      serviceConfig = {
-        ExecStart = "${pkgs.chronos}/bin/chronos --master ${cfg.master} --zk_hosts ${concatStringsSep "," cfg.zookeeperHosts} --http_port ${toString cfg.httpPort}";
-        User = "chronos";
-      };
-    };
-
-    users.users.chronos.uid = config.ids.uids.chronos;
-  };
-}
diff --git a/nixos/modules/services/scheduling/marathon.nix b/nixos/modules/services/scheduling/marathon.nix
deleted file mode 100644
index 2e0d20c64b23a..0000000000000
--- a/nixos/modules/services/scheduling/marathon.nix
+++ /dev/null
@@ -1,98 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-with lib;
-
-let
-
-  cfg = config.services.marathon;
-
-in {
-
-  ###### interface
-
-  options.services.marathon = {
-    enable = mkOption {
-      type = types.bool;
-      default = false;
-      description = ''
-	Whether to enable the marathon mesos framework.
-      '';
-    };
-
-    master = mkOption {
-      type = types.str;
-      default = "zk://${concatStringsSep "," cfg.zookeeperHosts}/mesos";
-      example = "zk://1.2.3.4:2181,2.3.4.5:2181,3.4.5.6:2181/mesos";
-      description = ''
-	Mesos master address. See <link xlink:href="https://mesosphere.github.io/marathon/docs/"/> for details.
-      '';
-    };
-
-    zookeeperHosts = mkOption {
-      type = types.listOf types.str;
-      default = [ "localhost:2181" ];
-      example = [ "1.2.3.4:2181" "2.3.4.5:2181" "3.4.5.6:2181" ];
-      description = ''
-	ZooKeeper hosts' addresses.
-      '';
-    };
-
-    user = mkOption {
-      type = types.str;
-      default = "marathon";
-      example = "root";
-      description = ''
-	The user that the Marathon framework will be launched as. If the user doesn't exist it will be created.
-	If you want to run apps that require root access or you want to launch apps using arbitrary users, that
-	is using the `--mesos_user` flag then you need to change this to `root`.
-      '';
-    };
-
-    httpPort = mkOption {
-      type = types.int;
-      default = 8080;
-      description = ''
-	Marathon listening port for HTTP connections.
-      '';
-    };
-
-    extraCmdLineOptions = mkOption {
-      type = types.listOf types.str;
-      default = [ ];
-      example = [ "--https_port=8443" "--zk_timeout=10000" "--marathon_store_timeout=2000" ];
-      description = ''
-	Extra command line options to pass to Marathon.
-	See <link xlink:href="https://mesosphere.github.io/marathon/docs/command-line-flags.html"/> for all possible flags.
-      '';
-    };
-
-    environment = mkOption {
-      default = { };
-      type = types.attrs;
-      example = { JAVA_OPTS = "-Xmx512m"; MESOSPHERE_HTTP_CREDENTIALS = "username:password"; };
-      description = ''
-	Environment variables passed to Marathon.
-      '';
-    };
-  };
-
-  ###### implementation
-
-  config = mkIf cfg.enable {
-    systemd.services.marathon = {
-      description = "Marathon Service";
-      environment = cfg.environment;
-      wantedBy = [ "multi-user.target" ];
-      after = [ "network.target" "zookeeper.service" "mesos-master.service" "mesos-slave.service" ];
-
-      serviceConfig = {
-        ExecStart = "${pkgs.marathon}/bin/marathon --master ${cfg.master} --zk zk://${concatStringsSep "," cfg.zookeeperHosts}/marathon --http_port ${toString cfg.httpPort} ${concatStringsSep " " cfg.extraCmdLineOptions}";
-        User = cfg.user;
-        Restart = "always";
-        RestartSec = "2";
-      };
-    };
-
-    users.users.${cfg.user}.isSystemUser = true;
-  };
-}
diff --git a/nixos/modules/services/security/haveged.nix b/nixos/modules/services/security/haveged.nix
index eca5291888100..22ece1883446a 100644
--- a/nixos/modules/services/security/haveged.nix
+++ b/nixos/modules/services/security/haveged.nix
@@ -21,11 +21,11 @@ in
         type = types.bool;
         default = false;
         description = ''
-          Whether to enable to haveged entropy daemon, which refills 
+          Whether to enable to haveged entropy daemon, which refills
           /dev/random when low.
         '';
       };
-      
+
       refill_threshold = mkOption {
         type = types.int;
         default = 1024;
@@ -34,16 +34,16 @@ in
           haveged should refill the entropy pool.
         '';
       };
-      
+
     };
-    
+
   };
-  
-  
+
+
   ###### implementation
-  
+
   config = mkIf cfg.enable {
-  
+
     systemd.services.haveged =
       { description = "Entropy Harvesting Daemon";
         unitConfig.Documentation = "man:haveged(8)";
@@ -63,5 +63,5 @@ in
       };
 
   };
-  
+
 }
diff --git a/nixos/modules/services/security/nginx-sso.nix b/nixos/modules/services/security/nginx-sso.nix
index d792f90abe64b..50d250fc4d761 100644
--- a/nixos/modules/services/security/nginx-sso.nix
+++ b/nixos/modules/services/security/nginx-sso.nix
@@ -4,12 +4,21 @@ with lib;
 
 let
   cfg = config.services.nginx.sso;
-  pkg = getBin pkgs.nginx-sso;
+  pkg = getBin cfg.package;
   configYml = pkgs.writeText "nginx-sso.yml" (builtins.toJSON cfg.configuration);
 in {
   options.services.nginx.sso = {
     enable = mkEnableOption "nginx-sso service";
 
+    package = mkOption {
+      type = types.package;
+      default = pkgs.nginx-sso;
+      defaultText = "pkgs.nginx-sso";
+      description = ''
+        The nginx-sso package that should be used.
+      '';
+    };
+
     configuration = mkOption {
       type = types.attrsOf types.unspecified;
       default = {};
diff --git a/nixos/modules/services/security/oauth2_proxy.nix b/nixos/modules/services/security/oauth2_proxy.nix
index d5c5437329ead..2f9e94bd77bac 100644
--- a/nixos/modules/services/security/oauth2_proxy.nix
+++ b/nixos/modules/services/security/oauth2_proxy.nix
@@ -99,7 +99,7 @@ in
 
     ##############################################
     # PROVIDER configuration
-    # Taken from: https://github.com/pusher/oauth2_proxy/blob/master/providers/providers.go
+    # Taken from: https://github.com/oauth2-proxy/oauth2-proxy/blob/master/providers/providers.go
     provider = mkOption {
       type = types.enum [
         "google"
@@ -346,7 +346,9 @@ in
         type = types.nullOr types.str;
         default = null;
         description = ''
-          An optional cookie domain to force cookies to.
+          Optional cookie domains to force cookies to (ie: `.yourcompany.com`).
+          The longest domain matching the request's host will be used (or the shortest
+          cookie domain if there is no match).
         '';
         example = ".yourcompany.com";
       };
@@ -537,7 +539,7 @@ in
     extraConfig = mkOption {
       default = {};
       description = ''
-        Extra config to pass to oauth2_proxy.
+        Extra config to pass to oauth2-proxy.
       '';
     };
 
@@ -545,7 +547,7 @@ in
       type = types.nullOr types.path;
       default = null;
       description = ''
-        oauth2_proxy allows passing sensitive configuration via environment variables.
+        oauth2-proxy allows passing sensitive configuration via environment variables.
         Make a file that contains lines like
         OAUTH2_PROXY_CLIENT_SECRET=asdfasdfasdf.apps.googleuserscontent.com
         and specify the path here.
@@ -577,7 +579,7 @@ in
       serviceConfig = {
         User = "oauth2_proxy";
         Restart = "always";
-        ExecStart = "${cfg.package}/bin/oauth2_proxy ${configString}";
+        ExecStart = "${cfg.package}/bin/oauth2-proxy ${configString}";
         EnvironmentFile = mkIf (cfg.keyFile != null) cfg.keyFile;
       };
     };
diff --git a/nixos/modules/services/security/privacyidea.nix b/nixos/modules/services/security/privacyidea.nix
index d6abfd0e27180..c2988858e56c3 100644
--- a/nixos/modules/services/security/privacyidea.nix
+++ b/nixos/modules/services/security/privacyidea.nix
@@ -234,7 +234,6 @@ in
           ExecStop = "${pkgs.coreutils}/bin/kill -INT $MAINPID";
           NotifyAccess = "main";
           KillSignal = "SIGQUIT";
-          StandardError = "syslog";
         };
       };
 
diff --git a/nixos/modules/services/security/tor.nix b/nixos/modules/services/security/tor.nix
index 18c105b2f5765..b33e905c67dea 100644
--- a/nixos/modules/services/security/tor.nix
+++ b/nixos/modules/services/security/tor.nix
@@ -159,7 +159,7 @@ in
           type = types.bool;
           default = false;
           description = ''
-            Wheter to enable Tor control socket. Control socket is created
+            Whether to enable Tor control socket. Control socket is created
             in <literal>${torRunDirectory}/control</literal>
           '';
         };
diff --git a/nixos/modules/services/security/usbguard.nix b/nixos/modules/services/security/usbguard.nix
index f4118eb87fc71..16a90da52314e 100644
--- a/nixos/modules/services/security/usbguard.nix
+++ b/nixos/modules/services/security/usbguard.nix
@@ -1,37 +1,39 @@
-{config, lib, pkgs, ... }:
+{ config, lib, pkgs, ... }:
 
 with lib;
-
 let
-
   cfg = config.services.usbguard;
 
   # valid policy options
   policy = (types.enum [ "allow" "block" "reject" "keep" "apply-policy" ]);
 
+  defaultRuleFile = "/var/lib/usbguard/rules.conf";
+
   # decide what file to use for rules
-  ruleFile = if cfg.rules != null then pkgs.writeText "usbguard-rules" cfg.rules else cfg.ruleFile;
+  ruleFile = if cfg.rules != null then pkgs.writeText "usbguard-rules" cfg.rules else defaultRuleFile;
 
   daemonConf = ''
-      # generated by nixos/modules/services/security/usbguard.nix
-      RuleFile=${ruleFile}
-      ImplicitPolicyTarget=${cfg.implictPolicyTarget}
-      PresentDevicePolicy=${cfg.presentDevicePolicy}
-      PresentControllerPolicy=${cfg.presentControllerPolicy}
-      InsertedDevicePolicy=${cfg.insertedDevicePolicy}
-      RestoreControllerDeviceState=${if cfg.restoreControllerDeviceState then "true" else "false"}
-      # this does not seem useful for endusers to change
-      DeviceManagerBackend=uevent
-      IPCAllowedUsers=${concatStringsSep " " cfg.IPCAllowedUsers}
-      IPCAllowedGroups=${concatStringsSep " " cfg.IPCAllowedGroups}
-      IPCAccessControlFiles=${cfg.IPCAccessControlFiles}
-      DeviceRulesWithPort=${if cfg.deviceRulesWithPort then "true" else "false"}
-      AuditFilePath=${cfg.auditFilePath}
-    '';
-
-    daemonConfFile = pkgs.writeText "usbguard-daemon-conf" daemonConf;
-
-in {
+    # generated by nixos/modules/services/security/usbguard.nix
+    RuleFile=${ruleFile}
+    ImplicitPolicyTarget=${cfg.implictPolicyTarget}
+    PresentDevicePolicy=${cfg.presentDevicePolicy}
+    PresentControllerPolicy=${cfg.presentControllerPolicy}
+    InsertedDevicePolicy=${cfg.insertedDevicePolicy}
+    RestoreControllerDeviceState=${if cfg.restoreControllerDeviceState then "true" else "false"}
+    # this does not seem useful for endusers to change
+    DeviceManagerBackend=uevent
+    IPCAllowedUsers=${concatStringsSep " " cfg.IPCAllowedUsers}
+    IPCAllowedGroups=${concatStringsSep " " cfg.IPCAllowedGroups}
+    IPCAccessControlFiles=/var/lib/usbguard/IPCAccessControl.d/
+    DeviceRulesWithPort=${if cfg.deviceRulesWithPort then "true" else "false"}
+    # HACK: that way audit logs still land in the journal
+    AuditFilePath=/dev/null
+  '';
+
+  daemonConfFile = pkgs.writeText "usbguard-daemon-conf" daemonConf;
+
+in
+{
 
   ###### interface
 
@@ -49,22 +51,6 @@ in {
         '';
       };
 
-      ruleFile = mkOption {
-        type = types.path;
-        default = "/var/lib/usbguard/rules.conf";
-        description = ''
-          The USBGuard daemon will use this file to load the policy rule set
-          from it and to write new rules received via the IPC interface.
-
-          Running the command <literal>usbguard generate-policy</literal> as
-          root will generate a config for your currently plugged in devices.
-          For a in depth guide consult the official documentation.
-
-          Setting the <literal>rules</literal> option will ignore the
-          <literal>ruleFile</literal> option.
-        '';
-      };
-
       rules = mkOption {
         type = types.nullOr types.lines;
         default = null;
@@ -72,16 +58,20 @@ in {
           allow with-interface equals { 08:*:* }
         '';
         description = ''
-          The USBGuard daemon will load this policy rule set. Modifying it via
-          the IPC interface won't work if you use this option, since the
-          contents of this option will be written into the nix-store it will be
-          read-only.
+          The USBGuard daemon will load this as the policy rule set.
+          As these rules are NixOS managed they are immutable and can't
+          be changed by the IPC interface.
+
+          If you do not set this option, the USBGuard daemon will load
+          it's policy rule set from <literal>${defaultRuleFile}</literal>.
+          This file can be changed manually or via the IPC interface.
 
-          You can still use <literal> usbguard generate-policy</literal> to
-          generate rules, but you would have to insert them here.
+          Running <literal>usbguard generate-policy</literal> as root will
+          generate a config for your currently plugged in devices.
 
-          Setting the <literal>rules</literal> option will ignore the
-          <literal>ruleFile</literal> option.
+          For more details see <citerefentry>
+          <refentrytitle>usbguard-rules.conf</refentrytitle>
+          <manvolnum>5</manvolnum></citerefentry>.
         '';
       };
 
@@ -155,17 +145,6 @@ in {
         '';
       };
 
-      IPCAccessControlFiles = mkOption {
-        type = types.path;
-        default = "/var/lib/usbguard/IPCAccessControl.d/";
-        description = ''
-          The files at this location will be interpreted by the daemon as IPC
-          access control definition files. See the IPC ACCESS CONTROL section
-          in <citerefentry><refentrytitle>usbguard-daemon.conf</refentrytitle>
-          <manvolnum>5</manvolnum></citerefentry> for more details.
-        '';
-      };
-
       deviceRulesWithPort = mkOption {
         type = types.bool;
         default = false;
@@ -173,14 +152,6 @@ in {
           Generate device specific rules including the "via-port" attribute.
         '';
       };
-
-      auditFilePath = mkOption {
-        type = types.path;
-        default = "/var/log/usbguard/usbguard-audit.log";
-        description = ''
-          USBGuard audit events log file path.
-        '';
-      };
     };
   };
 
@@ -197,17 +168,19 @@ in {
       wantedBy = [ "basic.target" ];
       wants = [ "systemd-udevd.service" ];
 
-      # make sure an empty rule file and required directories exist
-      preStart = ''
-        mkdir -p $(dirname "${cfg.ruleFile}") $(dirname "${cfg.auditFilePath}") "${cfg.IPCAccessControlFiles}" \
-          && ([ -f "${cfg.ruleFile}" ] || touch ${cfg.ruleFile})
-      '';
+      # make sure an empty rule file exists
+      preStart = ''[ -f "${ruleFile}" ] || touch ${ruleFile}'';
 
       serviceConfig = {
         Type = "simple";
         ExecStart = ''${cfg.package}/bin/usbguard-daemon -P -k -c ${daemonConfFile}'';
         Restart = "on-failure";
 
+        StateDirectory = [
+          "usbguard"
+          "usbguard/IPCAccessControl.d"
+        ];
+
         AmbientCapabilities = "";
         CapabilityBoundingSet = "CAP_CHOWN CAP_FOWNER";
         DeviceAllow = "/dev/null rw";
@@ -223,8 +196,8 @@ in {
         ProtectKernelModules = true;
         ProtectSystem = true;
         ReadOnlyPaths = "-/";
-        ReadWritePaths = "-/dev/shm -${dirOf cfg.auditFilePath} -/tmp -${dirOf cfg.ruleFile}";
-        RestrictAddressFamilies = "AF_UNIX AF_NETLINK";
+        ReadWritePaths = "-/dev/shm -/tmp";
+        RestrictAddressFamilies = [ "AF_UNIX" "AF_NETLINK" ];
         RestrictNamespaces = true;
         RestrictRealtime = true;
         SystemCallArchitectures = "native";
@@ -233,4 +206,9 @@ in {
       };
     };
   };
+  imports = [
+    (mkRemovedOptionModule [ "services" "usbguard" "ruleFile" ] "The usbguard module now uses ${defaultRuleFile} as ruleFile. Alternatively, use services.usbguard.rules to configure rules.")
+    (mkRemovedOptionModule [ "services" "usbguard" "IPCAccessControlFiles" ] "The usbguard module now hardcodes IPCAccessControlFiles to /var/lib/usbguard/IPCAccessControl.d.")
+    (mkRemovedOptionModule [ "services" "usbguard" "auditFilePath" ] "Removed usbguard module audit log files. Audit logs can be found in the systemd journal.")
+  ];
 }
diff --git a/nixos/modules/services/security/yubikey-agent.nix b/nixos/modules/services/security/yubikey-agent.nix
new file mode 100644
index 0000000000000..2972c64a36413
--- /dev/null
+++ b/nixos/modules/services/security/yubikey-agent.nix
@@ -0,0 +1,60 @@
+# Global configuration for yubikey-agent.
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.yubikey-agent;
+
+  # reuse the pinentryFlavor option from the gnupg module
+  pinentryFlavor = config.programs.gnupg.agent.pinentryFlavor;
+in
+{
+  ###### interface
+
+  meta.maintainers = with maintainers; [ philandstuff rawkode ];
+
+  options = {
+
+    services.yubikey-agent = {
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to start yubikey-agent when you log in.  Also sets
+          SSH_AUTH_SOCK to point at yubikey-agent.
+
+          Note that yubikey-agent will use whatever pinentry is
+          specified in programs.gnupg.agent.pinentryFlavor.
+        '';
+      };
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.yubikey-agent;
+        defaultText = "pkgs.yubikey-agent";
+        description = ''
+          The package used for the yubikey-agent daemon.
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    environment.systemPackages = [ cfg.package ];
+    systemd.packages = [ cfg.package ];
+
+    # This overrides the systemd user unit shipped with the
+    # yubikey-agent package
+    systemd.user.services.yubikey-agent = mkIf (pinentryFlavor != null) {
+      path = [ pkgs.pinentry.${pinentryFlavor} ];
+    };
+
+    environment.extraInit = ''
+      if [ -z "$SSH_AUTH_SOCK" -a -n "$XDG_RUNTIME_DIR" ]; then
+        export SSH_AUTH_SOCK="$XDG_RUNTIME_DIR/yubikey-agent/yubikey-agent.sock"
+      fi
+    '';
+  };
+}
diff --git a/nixos/modules/services/system/earlyoom.nix b/nixos/modules/services/system/earlyoom.nix
index c6a001d30eeb3..e29bdbe264cc8 100644
--- a/nixos/modules/services/system/earlyoom.nix
+++ b/nixos/modules/services/system/earlyoom.nix
@@ -106,7 +106,6 @@ in
       path = optional ecfg.enableNotifications pkgs.dbus;
       serviceConfig = {
         StandardOutput = "null";
-        StandardError = "syslog";
         ExecStart = ''
           ${pkgs.earlyoom}/bin/earlyoom \
           -m ${toString ecfg.freeMemThreshold} \
diff --git a/nixos/modules/services/torrent/transmission.nix b/nixos/modules/services/torrent/transmission.nix
index 1bfcf2de82f84..014a22bb5a8d6 100644
--- a/nixos/modules/services/torrent/transmission.nix
+++ b/nixos/modules/services/torrent/transmission.nix
@@ -1,52 +1,54 @@
-{ config, lib, pkgs, ... }:
+{ config, lib, pkgs, options, ... }:
 
 with lib;
 
 let
   cfg = config.services.transmission;
+  inherit (config.environment) etc;
   apparmor = config.security.apparmor.enable;
-
-  homeDir = cfg.home;
-  downloadDirPermissions = cfg.downloadDirPermissions;
-  downloadDir = "${homeDir}/Downloads";
-  incompleteDir = "${homeDir}/.incomplete";
-
-  settingsDir = "${homeDir}/config";
-  settingsFile = pkgs.writeText "settings.json" (builtins.toJSON fullSettings);
-
-  # for users in group "transmission" to have access to torrents
-  fullSettings = { umask = 2; download-dir = downloadDir; incomplete-dir = incompleteDir; } // cfg.settings;
-
-  preStart = pkgs.writeScript "transmission-pre-start" ''
-    #!${pkgs.runtimeShell}
-    set -ex
-    cp -f ${settingsFile} ${settingsDir}/settings.json
-  '';
+  rootDir = "/run/transmission";
+  homeDir = "/var/lib/transmission";
+  settingsDir = ".config/transmission-daemon";
+  downloadsDir = "Downloads";
+  incompleteDir = ".incomplete";
+  watchDir = "watchdir";
+  # TODO: switch to configGen.json once RFC0042 is implemented
+  settingsFile = pkgs.writeText "settings.json" (builtins.toJSON cfg.settings);
 in
 {
   options = {
     services.transmission = {
-      enable = mkOption {
-        type = types.bool;
-        default = false;
-        description = ''
-          Whether or not to enable the headless Transmission BitTorrent daemon.
+      enable = mkEnableOption ''the headless Transmission BitTorrent daemon.
 
-          Transmission daemon can be controlled via the RPC interface using
-          transmission-remote or the WebUI (http://localhost:9091/ by default).
+        Transmission daemon can be controlled via the RPC interface using
+        transmission-remote, the WebUI (http://127.0.0.1:9091/ by default),
+        or other clients like stig or tremc.
 
-          Torrents are downloaded to ${downloadDir} by default and are
-          accessible to users in the "transmission" group.
-        '';
-      };
+        Torrents are downloaded to ${homeDir}/${downloadsDir} by default and are
+        accessible to users in the "transmission" group'';
 
-      settings = mkOption {
+      settings = mkOption rec {
+        # TODO: switch to types.config.json as prescribed by RFC0042 once it's implemented
         type = types.attrs;
+        apply = recursiveUpdate default;
         default =
           {
-            download-dir = downloadDir;
-            incomplete-dir = incompleteDir;
+            download-dir = "${cfg.home}/${downloadsDir}";
+            incomplete-dir = "${cfg.home}/${incompleteDir}";
             incomplete-dir-enabled = true;
+            watch-dir = "${cfg.home}/${watchDir}";
+            watch-dir-enabled = false;
+            message-level = 1;
+            peer-port = 51413;
+            peer-port-random-high = 65535;
+            peer-port-random-low = 49152;
+            peer-port-random-on-start = false;
+            rpc-bind-address = "127.0.0.1";
+            rpc-port = 9091;
+            script-torrent-done-enabled = false;
+            script-torrent-done-filename = "";
+            umask = 2; # 0o002 in decimal as expected by Transmission
+            utp-enabled = true;
           };
         example =
           {
@@ -56,11 +58,12 @@ in
             rpc-whitelist = "127.0.0.1,192.168.*.*";
           };
         description = ''
-          Attribute set whos fields overwrites fields in settings.json (each
-          time the service starts). String values must be quoted, integer and
+          Attribute set whose fields overwrites fields in
+          <literal>.config/transmission-daemon/settings.json</literal>
+          (each time the service starts). String values must be quoted, integer and
           boolean values must not.
 
-          See https://github.com/transmission/transmission/wiki/Editing-Configuration-Files
+          See <link xlink:href="https://github.com/transmission/transmission/wiki/Editing-Configuration-Files">Transmission's Wiki</link>
           for documentation.
         '';
       };
@@ -70,22 +73,32 @@ in
         default = "770";
         example = "775";
         description = ''
-          The permissions to set for download-dir and incomplete-dir.
-          They will be applied on every service start.
+          The permissions set by <literal>systemd.activationScripts.transmission-daemon</literal>
+          on the directories <link linkend="opt-services.transmission.settings">settings.download-dir</link>
+          and <link linkend="opt-services.transmission.settings">settings.incomplete-dir</link>.
+          Note that you may also want to change
+          <link linkend="opt-services.transmission.settings">settings.umask</link>.
         '';
       };
 
       port = mkOption {
-        type = types.int;
-        default = 9091;
-        description = "TCP port number to run the RPC/web interface.";
+        type = types.port;
+        description = ''
+          TCP port number to run the RPC/web interface.
+
+          If instead you want to change the peer port,
+          use <link linkend="opt-services.transmission.settings">settings.peer-port</link>
+          or <link linkend="opt-services.transmission.settings">settings.peer-port-random-on-start</link>.
+        '';
       };
 
       home = mkOption {
         type = types.path;
-        default = "/var/lib/transmission";
+        default = homeDir;
         description = ''
-          The directory where transmission will create files.
+          The directory where Transmission will create <literal>${settingsDir}</literal>.
+          as well as <literal>${downloadsDir}/</literal> unless <link linkend="opt-services.transmission.settings">settings.download-dir</link> is changed,
+          and <literal>${incompleteDir}/</literal> unless <link linkend="opt-services.transmission.settings">settings.incomplete-dir</link> is changed.
         '';
       };
 
@@ -100,32 +113,179 @@ in
         default = "transmission";
         description = "Group account under which Transmission runs.";
       };
+
+      credentialsFile = mkOption {
+        type = types.path;
+        description = ''
+          Path to a JSON file to be merged with the settings.
+          Useful to merge a file which is better kept out of the Nix store
+          because it contains sensible data like <link linkend="opt-services.transmission.settings">settings.rpc-password</link>.
+        '';
+        default = "/dev/null";
+        example = "/var/lib/secrets/transmission/settings.json";
+      };
+
+      openFirewall = mkEnableOption "opening of the peer port(s) in the firewall";
+
+      performanceNetParameters = mkEnableOption ''tweaking of kernel parameters
+        to open many more connections at the same time.
+
+        Note that you may also want to increase
+        <link linkend="opt-services.transmission.settings">settings.peer-limit-global</link>.
+        And be aware that these settings are quite aggressive
+        and might not suite your regular desktop use.
+        For instance, SSH sessions may time out more easily'';
     };
   };
 
   config = mkIf cfg.enable {
-    systemd.tmpfiles.rules = [
-      "d '${homeDir}' 0770 '${cfg.user}' '${cfg.group}' - -"
-      "d '${settingsDir}' 0700 '${cfg.user}' '${cfg.group}' - -"
-      "d '${fullSettings.download-dir}' '${downloadDirPermissions}' '${cfg.user}' '${cfg.group}' - -"
-      "d '${fullSettings.incomplete-dir}' '${downloadDirPermissions}' '${cfg.user}' '${cfg.group}' - -"
+    # Note that using systemd.tmpfiles would not work here
+    # because it would fail when creating a directory
+    # with a different owner than its parent directory, by saying:
+    # Detected unsafe path transition /home/foo → /home/foo/Downloads during canonicalization of /home/foo/Downloads
+    # when /home/foo is not owned by cfg.user.
+    # Note also that using an ExecStartPre= wouldn't work either
+    # because BindPaths= needs these directories before.
+    system.activationScripts.transmission-daemon = ''
+      install -d -m 700 '${cfg.home}/${settingsDir}'
+      chown -R '${cfg.user}:${cfg.group}' ${cfg.home}/${settingsDir}
+      install -d -m '${cfg.downloadDirPermissions}' -o '${cfg.user}' -g '${cfg.group}' '${cfg.settings.download-dir}'
+      '' + optionalString cfg.settings.incomplete-dir-enabled ''
+      install -d -m '${cfg.downloadDirPermissions}' -o '${cfg.user}' -g '${cfg.group}' '${cfg.settings.incomplete-dir}'
+      '';
+
+    assertions = [
+      { assertion = builtins.match "^/.*" cfg.home != null;
+        message = "`services.transmission.home' must be an absolute path.";
+      }
+      { assertion = types.path.check cfg.settings.download-dir;
+        message = "`services.transmission.settings.download-dir' must be an absolute path.";
+      }
+      { assertion = types.path.check cfg.settings.incomplete-dir;
+        message = "`services.transmission.settings.incomplete-dir' must be an absolute path.";
+      }
+      { assertion = types.path.check cfg.settings.watch-dir;
+        message = "`services.transmission.settings.watch-dir' must be an absolute path.";
+      }
+      { assertion = cfg.settings.script-torrent-done-filename == "" || types.path.check cfg.settings.script-torrent-done-filename;
+        message = "`services.transmission.settings.script-torrent-done-filename' must be an absolute path.";
+      }
+      { assertion = types.port.check cfg.settings.rpc-port;
+        message = "${toString cfg.settings.rpc-port} is not a valid port number for `services.transmission.settings.rpc-port`.";
+      }
+      # In case both port and settings.rpc-port are explicitely defined: they must be the same.
+      { assertion = !options.services.transmission.port.isDefined || cfg.port == cfg.settings.rpc-port;
+        message = "`services.transmission.port' is not equal to `services.transmission.settings.rpc-port'";
+      }
     ];
 
+    services.transmission.settings =
+      optionalAttrs options.services.transmission.port.isDefined { rpc-port = cfg.port; };
+
     systemd.services.transmission = {
       description = "Transmission BitTorrent Service";
       after = [ "network.target" ] ++ optional apparmor "apparmor.service";
-      requires = mkIf apparmor [ "apparmor.service" ];
+      requires = optional apparmor "apparmor.service";
       wantedBy = [ "multi-user.target" ];
+      environment.CURL_CA_BUNDLE = etc."ssl/certs/ca-certificates.crt".source;
 
-      # 1) Only the "transmission" user and group have access to torrents.
-      # 2) Optionally update/force specific fields into the configuration file.
-      serviceConfig.ExecStartPre = preStart;
-      serviceConfig.ExecStart = "${pkgs.transmission}/bin/transmission-daemon -f --port ${toString config.services.transmission.port} --config-dir ${settingsDir}";
-      serviceConfig.ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
-      serviceConfig.User = cfg.user;
-      serviceConfig.Group = cfg.group;
-      # NOTE: transmission has an internal umask that also must be set (in settings.json)
-      serviceConfig.UMask = "0002";
+      serviceConfig = {
+        # Use "+" because credentialsFile may not be accessible to User= or Group=.
+        ExecStartPre = [("+" + pkgs.writeShellScript "transmission-prestart" ''
+          set -eu${lib.optionalString (cfg.settings.message-level >= 3) "x"}
+          ${pkgs.jq}/bin/jq --slurp add ${settingsFile} '${cfg.credentialsFile}' |
+          install -D -m 600 -o '${cfg.user}' -g '${cfg.group}' /dev/stdin \
+           '${cfg.home}/${settingsDir}/settings.json'
+        '')];
+        ExecStart="${pkgs.transmission}/bin/transmission-daemon -f";
+        ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+        User = cfg.user;
+        Group = cfg.group;
+        # Create rootDir in the host's mount namespace.
+        RuntimeDirectory = [(baseNameOf rootDir)];
+        RuntimeDirectoryMode = "755";
+        # Avoid mounting rootDir in the own rootDir of ExecStart='s mount namespace.
+        InaccessiblePaths = ["-+${rootDir}"];
+        # This is for BindPaths= and BindReadOnlyPaths=
+        # to allow traversal of directories they create in RootDirectory=.
+        UMask = "0066";
+        # Using RootDirectory= makes it possible
+        # to use the same paths download-dir/incomplete-dir
+        # (which appear in user's interfaces) without requiring cfg.user
+        # to have access to their parent directories,
+        # by using BindPaths=/BindReadOnlyPaths=.
+        # Note that TemporaryFileSystem= could have been used instead
+        # but not without adding some BindPaths=/BindReadOnlyPaths=
+        # that would only be needed for ExecStartPre=,
+        # because RootDirectoryStartOnly=true would not help.
+        RootDirectory = rootDir;
+        RootDirectoryStartOnly = true;
+        MountAPIVFS = true;
+        BindPaths =
+          [ "${cfg.home}/${settingsDir}"
+            cfg.settings.download-dir
+          ] ++
+          optional cfg.settings.incomplete-dir-enabled
+            cfg.settings.incomplete-dir
+          ++
+          optional cfg.settings.watch-dir-enabled
+            cfg.settings.watch-dir
+          ;
+        BindReadOnlyPaths = [
+          # No confinement done of /nix/store here like in systemd-confinement.nix,
+          # an AppArmor profile is provided to get a confinement based upon paths and rights.
+          builtins.storeDir
+          "/etc"
+          ] ++
+          optional (cfg.settings.script-torrent-done-enabled &&
+                    cfg.settings.script-torrent-done-filename != "")
+            cfg.settings.script-torrent-done-filename;
+        # The following options are only for optimizing:
+        # systemd-analyze security transmission
+        AmbientCapabilities = "";
+        CapabilityBoundingSet = "";
+        # ProtectClock= adds DeviceAllow=char-rtc r
+        DeviceAllow = "";
+        LockPersonality = true;
+        MemoryDenyWriteExecute = true;
+        NoNewPrivileges = true;
+        PrivateDevices = true;
+        PrivateMounts = true;
+        PrivateNetwork = mkDefault false;
+        PrivateTmp = true;
+        PrivateUsers = true;
+        ProtectClock = true;
+        ProtectControlGroups = true;
+        # ProtectHome=true would not allow BindPaths= to work accross /home,
+        # and ProtectHome=tmpfs would break statfs(),
+        # preventing transmission-daemon to report the available free space.
+        # However, RootDirectory= is used, so this is not a security concern
+        # since there would be nothing in /home but any BindPaths= wanted by the user.
+        ProtectHome = "read-only";
+        ProtectHostname = true;
+        ProtectKernelLogs = true;
+        ProtectKernelModules = true;
+        ProtectKernelTunables = true;
+        ProtectSystem = "strict";
+        RemoveIPC = true;
+        # AF_UNIX may become usable one day:
+        # https://github.com/transmission/transmission/issues/441
+        RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" ];
+        RestrictNamespaces = true;
+        RestrictRealtime = true;
+        RestrictSUIDSGID = true;
+        SystemCallFilter = [
+          "@system-service"
+          # Groups in @system-service which do not contain a syscall
+          # listed by perf stat -e 'syscalls:sys_enter_*' transmission-daemon -f
+          # in tests, and seem likely not necessary for transmission-daemon.
+          "~@aio" "~@chown" "~@keyring" "~@memlock" "~@resources" "~@setuid" "~@timer"
+          # In the @privileged group, but reached when querying infos through RPC (eg. with stig).
+          "quotactl"
+        ];
+        SystemCallArchitectures = "native";
+        SystemCallErrorNumber = "EPERM";
+      };
     };
 
     # It's useful to have transmission in path, e.g. for remote control
@@ -133,70 +293,159 @@ in
 
     users.users = optionalAttrs (cfg.user == "transmission") ({
       transmission = {
-        name = "transmission";
         group = cfg.group;
         uid = config.ids.uids.transmission;
         description = "Transmission BitTorrent user";
-        home = homeDir;
-        createHome = true;
+        home = cfg.home;
       };
     });
 
     users.groups = optionalAttrs (cfg.group == "transmission") ({
       transmission = {
-        name = "transmission";
         gid = config.ids.gids.transmission;
       };
     });
 
-    # AppArmor profile
+    networking.firewall = mkIf cfg.openFirewall (
+      if cfg.settings.peer-port-random-on-start
+      then
+        { allowedTCPPortRanges =
+            [ { from = cfg.settings.peer-port-random-low;
+                to   = cfg.settings.peer-port-random-high;
+              }
+            ];
+          allowedUDPPortRanges =
+            [ { from = cfg.settings.peer-port-random-low;
+                to   = cfg.settings.peer-port-random-high;
+              }
+            ];
+        }
+      else
+        { allowedTCPPorts = [ cfg.settings.peer-port ];
+          allowedUDPPorts = [ cfg.settings.peer-port ];
+        }
+    );
+
+    boot.kernel.sysctl = mkMerge [
+      # Transmission uses a single UDP socket in order to implement multiple uTP sockets,
+      # and thus expects large kernel buffers for the UDP socket,
+      # https://trac.transmissionbt.com/browser/trunk/libtransmission/tr-udp.c?rev=11956.
+      # at least up to the values hardcoded here:
+      (mkIf cfg.settings.utp-enabled {
+        "net.core.rmem_max" = mkDefault "4194304"; # 4MB
+        "net.core.wmem_max" = mkDefault "1048576"; # 1MB
+      })
+      (mkIf cfg.performanceNetParameters {
+        # Increase the number of available source (local) TCP and UDP ports to 49151.
+        # Usual default is 32768 60999, ie. 28231 ports.
+        # Find out your current usage with: ss -s
+        "net.ipv4.ip_local_port_range" = "16384 65535";
+        # Timeout faster generic TCP states.
+        # Usual default is 600.
+        # Find out your current usage with: watch -n 1 netstat -nptuo
+        "net.netfilter.nf_conntrack_generic_timeout" = 60;
+        # Timeout faster established but inactive connections.
+        # Usual default is 432000.
+        "net.netfilter.nf_conntrack_tcp_timeout_established" = 600;
+        # Clear immediately TCP states after timeout.
+        # Usual default is 120.
+        "net.netfilter.nf_conntrack_tcp_timeout_time_wait" = 1;
+        # Increase the number of trackable connections.
+        # Usual default is 262144.
+        # Find out your current usage with: conntrack -C
+        "net.netfilter.nf_conntrack_max" = 1048576;
+      })
+    ];
+
     security.apparmor.profiles = mkIf apparmor [
       (pkgs.writeText "apparmor-transmission-daemon" ''
-        #include <tunables/global>
+        include <tunables/global>
 
         ${pkgs.transmission}/bin/transmission-daemon {
-          #include <abstractions/base>
-          #include <abstractions/nameservice>
-
-          ${getLib pkgs.glibc}/lib/*.so                    mr,
-          ${getLib pkgs.libevent}/lib/libevent*.so*        mr,
-          ${getLib pkgs.curl}/lib/libcurl*.so*             mr,
-          ${getLib pkgs.openssl}/lib/libssl*.so*           mr,
-          ${getLib pkgs.openssl}/lib/libcrypto*.so*        mr,
-          ${getLib pkgs.zlib}/lib/libz*.so*                mr,
-          ${getLib pkgs.libssh2}/lib/libssh2*.so*          mr,
-          ${getLib pkgs.systemd}/lib/libsystemd*.so*       mr,
-          ${getLib pkgs.xz}/lib/liblzma*.so*               mr,
-          ${getLib pkgs.libgcrypt}/lib/libgcrypt*.so*      mr,
-          ${getLib pkgs.libgpgerror}/lib/libgpg-error*.so* mr,
-          ${getLib pkgs.nghttp2}/lib/libnghttp2*.so*       mr,
-          ${getLib pkgs.c-ares}/lib/libcares*.so*          mr,
-          ${getLib pkgs.libcap}/lib/libcap*.so*            mr,
-          ${getLib pkgs.attr}/lib/libattr*.so*             mr,
-          ${getLib pkgs.lz4}/lib/liblz4*.so*               mr,
-          ${getLib pkgs.libkrb5}/lib/lib*.so*              mr,
-          ${getLib pkgs.keyutils}/lib/libkeyutils*.so*     mr,
-          ${getLib pkgs.utillinuxMinimal.out}/lib/libblkid.so.* mr,
-          ${getLib pkgs.utillinuxMinimal.out}/lib/libmount.so.* mr,
-          ${getLib pkgs.utillinuxMinimal.out}/lib/libuuid.so.* mr,
-          ${getLib pkgs.gcc.cc.lib}/lib/libstdc++.so.* mr,
-          ${getLib pkgs.gcc.cc.lib}/lib/libgcc_s.so.* mr,
-
-          @{PROC}/sys/kernel/random/uuid   r,
-          @{PROC}/sys/vm/overcommit_memory r,
-
-          ${pkgs.openssl.out}/etc/**                     r,
-          ${pkgs.transmission}/share/transmission/** r,
-
-          owner ${settingsDir}/** rw,
-
-          ${fullSettings.download-dir}/** rw,
-          ${optionalString fullSettings.incomplete-dir-enabled ''
-            ${fullSettings.incomplete-dir}/** rw,
+          include <abstractions/base>
+          include <abstractions/nameservice>
+
+          # NOTE: https://github.com/NixOS/nixpkgs/pull/93457
+          # will remove the need for these by fixing <abstractions/base>
+          r ${etc."hosts".source},
+          r /etc/ld-nix.so.preload,
+          ${lib.optionalString (builtins.hasAttr "ld-nix.so.preload" etc) ''
+            r ${etc."ld-nix.so.preload".source},
+            ${concatMapStrings (p: optionalString (p != "") ("mr ${p},\n"))
+              (splitString "\n" config.environment.etc."ld-nix.so.preload".text)}
           ''}
+          r ${etc."ssl/certs/ca-certificates.crt".source},
+          r ${pkgs.tzdata}/share/zoneinfo/**,
+          r ${pkgs.stdenv.cc.libc}/share/i18n/**,
+          r ${pkgs.stdenv.cc.libc}/share/locale/**,
+
+          mr ${getLib pkgs.stdenv.cc.cc}/lib/*.so*,
+          mr ${getLib pkgs.stdenv.cc.libc}/lib/*.so*,
+          mr ${getLib pkgs.attr}/lib/libattr*.so*,
+          mr ${getLib pkgs.c-ares}/lib/libcares*.so*,
+          mr ${getLib pkgs.curl}/lib/libcurl*.so*,
+          mr ${getLib pkgs.keyutils}/lib/libkeyutils*.so*,
+          mr ${getLib pkgs.libcap}/lib/libcap*.so*,
+          mr ${getLib pkgs.libevent}/lib/libevent*.so*,
+          mr ${getLib pkgs.libgcrypt}/lib/libgcrypt*.so*,
+          mr ${getLib pkgs.libgpgerror}/lib/libgpg-error*.so*,
+          mr ${getLib pkgs.libkrb5}/lib/lib*.so*,
+          mr ${getLib pkgs.libssh2}/lib/libssh2*.so*,
+          mr ${getLib pkgs.lz4}/lib/liblz4*.so*,
+          mr ${getLib pkgs.nghttp2}/lib/libnghttp2*.so*,
+          mr ${getLib pkgs.openssl}/lib/libcrypto*.so*,
+          mr ${getLib pkgs.openssl}/lib/libssl*.so*,
+          mr ${getLib pkgs.systemd}/lib/libsystemd*.so*,
+          mr ${getLib pkgs.utillinuxMinimal.out}/lib/libblkid.so*,
+          mr ${getLib pkgs.utillinuxMinimal.out}/lib/libmount.so*,
+          mr ${getLib pkgs.utillinuxMinimal.out}/lib/libuuid.so*,
+          mr ${getLib pkgs.xz}/lib/liblzma*.so*,
+          mr ${getLib pkgs.zlib}/lib/libz*.so*,
+
+          r @{PROC}/sys/kernel/random/uuid,
+          r @{PROC}/sys/vm/overcommit_memory,
+          # @{pid} is not a kernel variable yet but a regexp
+          #r @{PROC}/@{pid}/environ,
+          r @{PROC}/@{pid}/mounts,
+          rwk /tmp/tr_session_id_*,
+
+          r ${pkgs.openssl.out}/etc/**,
+          r ${config.systemd.services.transmission.environment.CURL_CA_BUNDLE},
+          r ${pkgs.transmission}/share/transmission/**,
+
+          owner rw ${cfg.home}/${settingsDir}/**,
+          rw ${cfg.settings.download-dir}/**,
+          ${optionalString cfg.settings.incomplete-dir-enabled ''
+            rw ${cfg.settings.incomplete-dir}/**,
+          ''}
+          ${optionalString cfg.settings.watch-dir-enabled ''
+            rw ${cfg.settings.watch-dir}/**,
+          ''}
+          profile dirs {
+            rw ${cfg.settings.download-dir}/**,
+            ${optionalString cfg.settings.incomplete-dir-enabled ''
+              rw ${cfg.settings.incomplete-dir}/**,
+            ''}
+            ${optionalString cfg.settings.watch-dir-enabled ''
+              rw ${cfg.settings.watch-dir}/**,
+            ''}
+          }
+
+          ${optionalString (cfg.settings.script-torrent-done-enabled &&
+                            cfg.settings.script-torrent-done-filename != "") ''
+            # Stack transmission_directories profile on top of
+            # any existing profile for script-torrent-done-filename
+            # FIXME: to be tested as I'm not sure it works well with NoNewPrivileges=
+            # https://gitlab.com/apparmor/apparmor/-/wikis/AppArmorStacking#seccomp-and-no_new_privs
+            px ${cfg.settings.script-torrent-done-filename} -> &@{dirs},
+          ''}
+
+          # FIXME: enable customizing using https://github.com/NixOS/nixpkgs/pull/93457
+          # include <local/transmission-daemon>
         }
       '')
     ];
   };
 
+  meta.maintainers = with lib.maintainers; [ julm ];
 }
diff --git a/nixos/modules/services/video/mirakurun.nix b/nixos/modules/services/video/mirakurun.nix
new file mode 100644
index 0000000000000..675b67f6ebf91
--- /dev/null
+++ b/nixos/modules/services/video/mirakurun.nix
@@ -0,0 +1,165 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.mirakurun;
+  mirakurun = pkgs.mirakurun;
+  username = config.users.users.mirakurun.name;
+  groupname = config.users.users.mirakurun.group;
+  settingsFmt = pkgs.formats.yaml {};
+in
+  {
+    options = {
+      services.mirakurun = {
+        enable = mkEnableOption mirakurun.meta.description;
+
+        port = mkOption {
+          type = with types; nullOr port;
+          default = 40772;
+          description = ''
+            Port to listen on. If null, it won't listen on any port.
+          '';
+        };
+
+        openFirewall = mkOption {
+          type = types.bool;
+          default = false;
+          description = ''
+            Open ports in the firewall for Mirakurun.
+          '';
+        };
+
+        serverSettings = mkOption {
+          type = settingsFmt.type;
+          default = {};
+          example = literalExample ''
+            {
+              highWaterMark = 25165824;
+              overflowTimeLimit = 30000;
+            };
+          '';
+          description = ''
+            Options for server.yml.
+
+            Documentation:
+            <link xlink:href="https://github.com/Chinachu/Mirakurun/blob/master/doc/Configuration.md"/>
+          '';
+        };
+
+        tunerSettings = mkOption {
+          type = with types; nullOr settingsFmt.type;
+          default = null;
+          example = literalExample ''
+            [
+              {
+                name = "tuner-name";
+                types = [ "GR" "BS" "CS" "SKY" ];
+                dvbDevicePath = "/dev/dvb/adapterX/dvrX";
+              }
+            ];
+          '';
+          description = ''
+            Options which are added to tuners.yml. If none is specified, it will
+            automatically be generated at runtime.
+
+            Documentation:
+            <link xlink:href="https://github.com/Chinachu/Mirakurun/blob/master/doc/Configuration.md"/>
+          '';
+        };
+
+        channelSettings = mkOption {
+          type = with types; nullOr settingsFmt.type;
+          default = null;
+          example = literalExample ''
+            [
+              {
+                name = "channel";
+                types = "GR";
+                channel = "0";
+              }
+            ];
+          '';
+          description = ''
+            Options which are added to channels.yml. If none is specified, it
+            will automatically be generated at runtime.
+
+            Documentation:
+            <link xlink:href="https://github.com/Chinachu/Mirakurun/blob/master/doc/Configuration.md"/>
+          '';
+        };
+      };
+    };
+
+    config = mkIf cfg.enable {
+      environment.systemPackages = [ mirakurun ];
+      environment.etc = {
+        "mirakurun/server.yml".source = settingsFmt.generate "server.yml" cfg.serverSettings;
+        "mirakurun/tuners.yml" = mkIf (cfg.tunerSettings != null) {
+          source = settingsFmt.generate "tuners.yml" cfg.tunerSettings;
+          mode = "0644";
+          user = username;
+          group = groupname;
+        };
+        "mirakurun/channels.yml" = mkIf (cfg.channelSettings != null) {
+          source = settingsFmt.generate "channels.yml" cfg.channelSettings;
+          mode = "0644";
+          user = username;
+          group = groupname;
+        };
+      };
+
+      networking.firewall = mkIf cfg.openFirewall {
+        allowedTCPPorts = mkIf (cfg.port != null) [ cfg.port ];
+      };
+
+      users.users.mirakurun = {
+        description = "Mirakurun user";
+        group = "video";
+        isSystemUser = true;
+      };
+
+      services.mirakurun.serverSettings = {
+        logLevel = mkDefault 2;
+        path = mkDefault "/var/run/mirakurun/mirakurun.sock";
+        port = mkIf (cfg.port != null) (mkDefault cfg.port);
+      };
+
+      systemd.tmpfiles.rules = [
+        "d '/etc/mirakurun' - ${username} ${groupname} - -"
+      ];
+
+      systemd.services.mirakurun = {
+        description = mirakurun.meta.description;
+        wantedBy = [ "multi-user.target" ];
+        after = [ "network.target" ];
+        serviceConfig = {
+          ExecStart = "${mirakurun}/bin/mirakurun";
+          User = username;
+          Group = groupname;
+          RuntimeDirectory="mirakurun";
+          StateDirectory="mirakurun";
+          Nice = -10;
+          IOSchedulingClass = "realtime";
+          IOSchedulingPriority = 7;
+        };
+
+        environment = {
+          SERVER_CONFIG_PATH = "/etc/mirakurun/server.yml";
+          TUNERS_CONFIG_PATH = "/etc/mirakurun/tuners.yml";
+          CHANNELS_CONFIG_PATH = "/etc/mirakurun/channels.yml";
+          SERVICES_DB_PATH = "/var/lib/mirakurun/services.json";
+          PROGRAMS_DB_PATH = "/var/lib/mirakurun/programs.json";
+          NODE_ENV = "production";
+        };
+
+        restartTriggers = let
+          getconf = target: config.environment.etc."mirakurun/${target}.yml".source;
+          targets = [
+            "server"
+          ] ++ optional (cfg.tunerSettings != null) "tuners"
+            ++ optional (cfg.channelSettings != null) "channels";
+        in (map getconf targets);
+      };
+    };
+  }
diff --git a/nixos/modules/services/wayland/cage.nix b/nixos/modules/services/wayland/cage.nix
index c59ca9983a6c6..50e424fccbfc1 100644
--- a/nixos/modules/services/wayland/cage.nix
+++ b/nixos/modules/services/wayland/cage.nix
@@ -73,8 +73,6 @@ in {
         TTYVTDisallocate = "yes";
         # Fail to start if not controlling the virtual terminal.
         StandardInput = "tty-fail";
-        StandardOutput = "syslog";
-        StandardError = "syslog";
         # Set up a full (custom) user session for the user, required by Cage.
         PAMName = "cage";
       };
diff --git a/nixos/modules/services/web-apps/codimd.nix b/nixos/modules/services/web-apps/codimd.nix
index 751f81649ddb4..ab922a38e5c68 100644
--- a/nixos/modules/services/web-apps/codimd.nix
+++ b/nixos/modules/services/web-apps/codimd.nix
@@ -93,7 +93,7 @@ in
           type = types.bool;
           default = true;
           description = ''
-            Wheter to enable HSTS if HTTPS is also enabled.
+            Whether to enable HSTS if HTTPS is also enabled.
           '';
         };
         maxAgeSeconds = mkOption {
@@ -385,7 +385,7 @@ in
         type = types.bool;
         default = true;
         description = ''
-          Wether to enable email registration.
+          Whether to enable email registration.
         '';
       };
       allowGravatar = mkOption {
diff --git a/nixos/modules/services/web-apps/convos.nix b/nixos/modules/services/web-apps/convos.nix
new file mode 100644
index 0000000000000..8be11eec9f31d
--- /dev/null
+++ b/nixos/modules/services/web-apps/convos.nix
@@ -0,0 +1,72 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.convos;
+in
+{
+  options.services.convos = {
+    enable = mkEnableOption "Convos";
+    listenPort = mkOption {
+      type = types.port;
+      default = 3000;
+      example = 8080;
+      description = "Port the web interface should listen on";
+    };
+    listenAddress = mkOption {
+      type = types.str;
+      default = "*";
+      example = "127.0.0.1";
+      description = "Address or host the web interface should listen on";
+    };
+    reverseProxy = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Enables reverse proxy support. This will allow Convos to automatically
+        pick up the <literal>X-Forwarded-For</literal> and
+        <literal>X-Request-Base</literal> HTTP headers set in your reverse proxy
+        web server. Note that enabling this option without a reverse proxy in
+        front will be a security issue.
+      '';
+    };
+  };
+  config = mkIf cfg.enable {
+    systemd.services.convos = {
+      description = "Convos Service";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "networking.target" ];
+      environment = {
+        CONVOS_HOME = "%S/convos";
+        CONVOS_REVERSE_PROXY = if cfg.reverseProxy then "1" else "0";
+        MOJO_LISTEN = "http://${toString cfg.listenAddress}:${toString cfg.listenPort}";
+      };
+      serviceConfig = {
+        ExecStart = "${pkgs.convos}/bin/convos daemon";
+        Restart = "on-failure";
+        StateDirectory = "convos";
+        WorkingDirectory = "%S/convos";
+        DynamicUser = true;
+        MemoryDenyWriteExecute = true;
+        ProtectHome = true;
+        ProtectClock = true;
+        ProtectHostname = true;
+        ProtectKernelTunables = true;
+        ProtectKernelModules = true;
+        ProtectKernelLogs = true;
+        ProtectControlGroups = true;
+        PrivateDevices = true;
+        PrivateMounts = true;
+        PrivateUsers = true;
+        LockPersonality = true;
+        RestrictRealtime = true;
+        RestrictNamespaces = true;
+        RestrictAddressFamilies = [ "AF_INET" "AF_INET6"];
+        SystemCallFilter = "@system-service";
+        SystemCallArchitectures = "native";
+        CapabilityBoundingSet = "";
+      };
+    };
+  };
+}
diff --git a/nixos/modules/services/web-apps/dokuwiki.nix b/nixos/modules/services/web-apps/dokuwiki.nix
index 33a828fa2cb3b..d9ebb3a98808c 100644
--- a/nixos/modules/services/web-apps/dokuwiki.nix
+++ b/nixos/modules/services/web-apps/dokuwiki.nix
@@ -2,7 +2,7 @@
 
 let
 
-  inherit (lib) mkEnableOption mkForce mkIf mkMerge mkOption optionalAttrs recursiveUpdate types;
+  inherit (lib) mkEnableOption mkForce mkIf mkMerge mkOption optionalAttrs recursiveUpdate types maintainers;
   inherit (lib) concatMapStringsSep flatten mapAttrs mapAttrs' mapAttrsToList nameValuePair concatMapStringSep;
 
   eachSite = config.services.dokuwiki;
@@ -95,7 +95,7 @@ let
 
       aclFile = mkOption {
         type = with types; nullOr str;
-        default = if (config.aclUse && config.acl == null) then "/var/lib/dokuwiki/${name}/users.auth.php" else null;
+        default = if (config.aclUse && config.acl == null) then "/var/lib/dokuwiki/${name}/acl.auth.php" else null;
         description = ''
           Location of the dokuwiki acl rules. Mutually exclusive with services.dokuwiki.acl
           Mutually exclusive with services.dokuwiki.acl which is preferred.
@@ -249,22 +249,19 @@ let
       nginx = mkOption {
         type = types.submodule (
           recursiveUpdate
-            (import ../web-servers/nginx/vhost-options.nix { inherit config lib; })
-            {
-              # Enable encryption by default,
-              options.forceSSL.default = true;
-              options.enableACME.default = true;
-            }
+            (import ../web-servers/nginx/vhost-options.nix { inherit config lib; }) {}
         );
-        default = {forceSSL = true; enableACME = true;};
+        default = {};
         example = {
           serverAliases = [
             "wiki.\${config.networking.domain}"
           ];
-          enableACME = false;
+          # To enable encryption and let let's encrypt take care of certificate
+          forceSSL = true;
+          enableACME = true;
         };
         description = ''
-          With this option, you can customize the nginx virtualHost which already has sensible defaults for DokuWiki.
+          With this option, you can customize the nginx virtualHost settings.
         '';
       };
     };
@@ -276,7 +273,7 @@ in
     services.dokuwiki = mkOption {
       type = types.attrsOf (types.submodule siteOpts);
       default = {};
-      description = "Sepcification of one or more dokuwiki sites to service.";
+      description = "Sepcification of one or more dokuwiki sites to serve.";
     };
   };
 
@@ -321,7 +318,7 @@ in
       enable = true;
       virtualHosts = mapAttrs (hostName: cfg:  mkMerge [ cfg.nginx {
         root = mkForce "${pkg hostName cfg}/share/dokuwiki";
-        extraConfig = "fastcgi_param HTTPS on;";
+        extraConfig = lib.optionalString (cfg.nginx.addSSL || cfg.nginx.forceSSL || cfg.nginx.onlySSL || cfg.nginx.enableACME) "fastcgi_param HTTPS on;";
 
         locations."~ /(conf/|bin/|inc/|install.php)" = {
           extraConfig = "deny all;";
@@ -359,7 +356,7 @@ in
               fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
               fastcgi_param REDIRECT_STATUS 200;
               fastcgi_pass unix:${config.services.phpfpm.pools."dokuwiki-${hostName}".socket};
-              fastcgi_param HTTPS on;
+              ${lib.optionalString (cfg.nginx.addSSL || cfg.nginx.forceSSL || cfg.nginx.onlySSL || cfg.nginx.enableACME) "fastcgi_param HTTPS on;"}
           '';
         };
       }]) eachSite;
@@ -385,4 +382,7 @@ in
       isSystemUser = true;
     };
   };
+
+  meta.maintainers = with maintainers; [ _1000101 ];
+
 }
diff --git a/nixos/modules/services/web-apps/jitsi-meet.nix b/nixos/modules/services/web-apps/jitsi-meet.nix
new file mode 100644
index 0000000000000..3b2b24404915e
--- /dev/null
+++ b/nixos/modules/services/web-apps/jitsi-meet.nix
@@ -0,0 +1,333 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.jitsi-meet;
+
+  # The configuration files are JS of format "var <<string>> = <<JSON>>;". In order to
+  # override only some settings, we need to extract the JSON, use jq to merge it with
+  # the config provided by user, and then reconstruct the file.
+  overrideJs =
+    source: varName: userCfg: appendExtra:
+    let
+      extractor = pkgs.writeText "extractor.js" ''
+        var fs = require("fs");
+        eval(fs.readFileSync(process.argv[2], 'utf8'));
+        process.stdout.write(JSON.stringify(eval(process.argv[3])));
+      '';
+      userJson = pkgs.writeText "user.json" (builtins.toJSON userCfg);
+    in (pkgs.runCommand "${varName}.js" { } ''
+      ${pkgs.nodejs}/bin/node ${extractor} ${source} ${varName} > default.json
+      (
+        echo "var ${varName} = "
+        ${pkgs.jq}/bin/jq -s '.[0] * .[1]' default.json ${userJson}
+        echo ";"
+        echo ${escapeShellArg appendExtra}
+      ) > $out
+    '');
+
+  # Essential config - it's probably not good to have these as option default because
+  # types.attrs doesn't do merging. Let's merge explicitly, can still be overriden if
+  # user desires.
+  defaultCfg = {
+    hosts = {
+      domain = cfg.hostName;
+      muc = "conference.${cfg.hostName}";
+      focus = "focus.${cfg.hostName}";
+    };
+    bosh = "//${cfg.hostName}/http-bind";
+  };
+in
+{
+  options.services.jitsi-meet = with types; {
+    enable = mkEnableOption "Jitsi Meet - Secure, Simple and Scalable Video Conferences";
+
+    hostName = mkOption {
+      type = str;
+      example = "meet.example.org";
+      description = ''
+        Hostname of the Jitsi Meet instance.
+      '';
+    };
+
+    config = mkOption {
+      type = attrs;
+      default = { };
+      example = literalExample ''
+        {
+          enableWelcomePage = false;
+          defaultLang = "fi";
+        }
+      '';
+      description = ''
+        Client-side web application settings that override the defaults in <filename>config.js</filename>.
+
+        See <link xlink:href="https://github.com/jitsi/jitsi-meet/blob/master/config.js" /> for default
+        configuration with comments.
+      '';
+    };
+
+    extraConfig = mkOption {
+      type = lines;
+      default = "";
+      description = ''
+        Text to append to <filename>config.js</filename> web application config file.
+
+        Can be used to insert JavaScript logic to determine user's region in cascading bridges setup.
+      '';
+    };
+
+    interfaceConfig = mkOption {
+      type = attrs;
+      default = { };
+      example = literalExample ''
+        {
+          SHOW_JITSI_WATERMARK = false;
+          SHOW_WATERMARK_FOR_GUESTS = false;
+        }
+      '';
+      description = ''
+        Client-side web-app interface settings that override the defaults in <filename>interface_config.js</filename>.
+
+        See <link xlink:href="https://github.com/jitsi/jitsi-meet/blob/master/interface_config.js" /> for
+        default configuration with comments.
+      '';
+    };
+
+    videobridge = {
+      enable = mkOption {
+        type = bool;
+        default = true;
+        description = ''
+          Whether to enable Jitsi Videobridge instance and configure it to connect to Prosody.
+
+          Additional configuration is possible with <option>services.jitsi-videobridge</option>.
+        '';
+      };
+
+      passwordFile = mkOption {
+        type = nullOr str;
+        default = null;
+        example = "/run/keys/videobridge";
+        description = ''
+          File containing password to the Prosody account for videobridge.
+
+          If <literal>null</literal>, a file with password will be generated automatically. Setting
+          this option is useful if you plan to connect additional videobridges to the XMPP server.
+        '';
+      };
+    };
+
+    jicofo.enable = mkOption {
+      type = bool;
+      default = true;
+      description = ''
+        Whether to enable JiCoFo instance and configure it to connect to Prosody.
+
+        Additional configuration is possible with <option>services.jicofo</option>.
+      '';
+    };
+
+    nginx.enable = mkOption {
+      type = bool;
+      default = true;
+      description = ''
+        Whether to enable nginx virtual host that will serve the javascript application and act as
+        a proxy for the XMPP server. Further nginx configuration can be done by adapting
+        <option>services.nginx.virtualHosts.&lt;hostName&gt;</option>.
+        When this is enabled, ACME will be used to retrieve a TLS certificate by default. To disable
+        this, set the <option>services.nginx.virtualHosts.&lt;hostName&gt;.enableACME</option> to
+        <literal>false</literal> and if appropriate do the same for
+        <option>services.nginx.virtualHosts.&lt;hostName&gt;.forceSSL</option>.
+      '';
+    };
+
+    prosody.enable = mkOption {
+      type = bool;
+      default = true;
+      description = ''
+        Whether to configure Prosody to relay XMPP messages between Jitsi Meet components. Turn this
+        off if you want to configure it manually.
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+    services.prosody = mkIf cfg.prosody.enable {
+      enable = mkDefault true;
+      xmppComplianceSuite = mkDefault false;
+      modules = {
+        admin_adhoc = mkDefault false;
+        bosh = mkDefault true;
+        ping = mkDefault true;
+        roster = mkDefault true;
+        saslauth = mkDefault true;
+        tls = mkDefault true;
+      };
+      muc = [
+        {
+          domain = "conference.${cfg.hostName}";
+          name = "Jitsi Meet MUC";
+          roomLocking = false;
+          roomDefaultPublicJids = true;
+          extraConfig = ''
+            storage = "memory"
+          '';
+        }
+        {
+          domain = "internal.${cfg.hostName}";
+          name = "Jitsi Meet Videobridge MUC";
+          extraConfig = ''
+            storage = "memory"
+            admins = { "focus@auth.${cfg.hostName}", "jvb@auth.${cfg.hostName}" }
+          '';
+          #-- muc_room_cache_size = 1000
+        }
+      ];
+      extraModules = [ "pubsub" ];
+      extraConfig = mkAfter ''
+        Component "focus.${cfg.hostName}"
+          component_secret = os.getenv("JICOFO_COMPONENT_SECRET")
+      '';
+      virtualHosts.${cfg.hostName} = {
+        enabled = true;
+        domain = cfg.hostName;
+        extraConfig = ''
+          authentication = "anonymous"
+          c2s_require_encryption = false
+          admins = { "focus@auth.${cfg.hostName}" }
+        '';
+        ssl = {
+          cert = "/var/lib/jitsi-meet/jitsi-meet.crt";
+          key = "/var/lib/jitsi-meet/jitsi-meet.key";
+        };
+      };
+      virtualHosts."auth.${cfg.hostName}" = {
+        enabled = true;
+        domain = "auth.${cfg.hostName}";
+        extraConfig = ''
+          authentication = "internal_plain"
+        '';
+        ssl = {
+          cert = "/var/lib/jitsi-meet/jitsi-meet.crt";
+          key = "/var/lib/jitsi-meet/jitsi-meet.key";
+        };
+      };
+    };
+    systemd.services.prosody.serviceConfig = mkIf cfg.prosody.enable {
+      EnvironmentFile = [ "/var/lib/jitsi-meet/secrets-env" ];
+      SupplementaryGroups = [ "jitsi-meet" ];
+    };
+
+    users.groups.jitsi-meet = {};
+    systemd.tmpfiles.rules = [
+      "d '/var/lib/jitsi-meet' 0750 root jitsi-meet - -"
+    ];
+
+    systemd.services.jitsi-meet-init-secrets = {
+      wantedBy = [ "multi-user.target" ];
+      before = [ "jicofo.service" "jitsi-videobridge2.service" ] ++ (optional cfg.prosody.enable "prosody.service");
+      serviceConfig = {
+        Type = "oneshot";
+      };
+
+      script = let
+        secrets = [ "jicofo-component-secret" "jicofo-user-secret" ] ++ (optional (cfg.videobridge.passwordFile == null) "videobridge-secret");
+        videobridgeSecret = if cfg.videobridge.passwordFile != null then cfg.videobridge.passwordFile else "/var/lib/jitsi-meet/videobridge-secret";
+      in
+      ''
+        cd /var/lib/jitsi-meet
+        ${concatMapStringsSep "\n" (s: ''
+          if [ ! -f ${s} ]; then
+            tr -dc a-zA-Z0-9 </dev/urandom | head -c 64 > ${s}
+            chown root:jitsi-meet ${s}
+            chmod 640 ${s}
+          fi
+        '') secrets}
+
+        # for easy access in prosody
+        echo "JICOFO_COMPONENT_SECRET=$(cat jicofo-component-secret)" > secrets-env
+        chown root:jitsi-meet secrets-env
+        chmod 640 secrets-env
+      ''
+      + optionalString cfg.prosody.enable ''
+        ${config.services.prosody.package}/bin/prosodyctl register focus auth.${cfg.hostName} "$(cat /var/lib/jitsi-meet/jicofo-user-secret)"
+        ${config.services.prosody.package}/bin/prosodyctl register jvb auth.${cfg.hostName} "$(cat ${videobridgeSecret})"
+
+        # generate self-signed certificates
+        if [ ! -f /var/lib/jitsi-meet.crt ]; then
+          ${getBin pkgs.openssl}/bin/openssl req \
+            -x509 \
+            -newkey rsa:4096 \
+            -keyout /var/lib/jitsi-meet/jitsi-meet.key \
+            -out /var/lib/jitsi-meet/jitsi-meet.crt \
+            -days 36500 \
+            -nodes \
+            -subj '/CN=${cfg.hostName}/CN=auth.${cfg.hostName}'
+          chmod 640 /var/lib/jitsi-meet/jitsi-meet.{crt,key}
+          chown root:jitsi-meet /var/lib/jitsi-meet/jitsi-meet.{crt,key}
+        fi
+      '';
+    };
+
+    services.nginx = mkIf cfg.nginx.enable {
+      enable = mkDefault true;
+      virtualHosts.${cfg.hostName} = {
+        enableACME = mkDefault true;
+        forceSSL = mkDefault true;
+        root = pkgs.jitsi-meet;
+        extraConfig = ''
+          ssi on;
+        '';
+        locations."@root_path".extraConfig = ''
+          rewrite ^/(.*)$ / break;
+        '';
+        locations."~ ^/([^/\\?&:'\"]+)$".tryFiles = "$uri @root_path";
+        locations."=/http-bind" = {
+          proxyPass = "http://localhost:5280/http-bind";
+          extraConfig = ''
+            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+            proxy_set_header Host $host;
+          '';
+        };
+        locations."=/external_api.js" = mkDefault {
+          alias = "${pkgs.jitsi-meet}/libs/external_api.min.js";
+        };
+        locations."=/config.js" = mkDefault {
+          alias = overrideJs "${pkgs.jitsi-meet}/config.js" "config" (recursiveUpdate defaultCfg cfg.config) cfg.extraConfig;
+        };
+        locations."=/interface_config.js" = mkDefault {
+          alias = overrideJs "${pkgs.jitsi-meet}/interface_config.js" "interfaceConfig" cfg.interfaceConfig "";
+        };
+      };
+    };
+
+    services.jitsi-videobridge = mkIf cfg.videobridge.enable {
+      enable = true;
+      xmppConfigs."localhost" = {
+        userName = "jvb";
+        domain = "auth.${cfg.hostName}";
+        passwordFile = "/var/lib/jitsi-meet/videobridge-secret";
+        mucJids = "jvbbrewery@internal.${cfg.hostName}";
+        disableCertificateVerification = true;
+      };
+    };
+
+    services.jicofo = mkIf cfg.jicofo.enable {
+      enable = true;
+      xmppHost = "localhost";
+      xmppDomain = cfg.hostName;
+      userDomain = "auth.${cfg.hostName}";
+      userName = "focus";
+      userPasswordFile = "/var/lib/jitsi-meet/jicofo-user-secret";
+      componentPasswordFile = "/var/lib/jitsi-meet/jicofo-component-secret";
+      bridgeMuc = "jvbbrewery@internal.${cfg.hostName}";
+      config = {
+        "org.jitsi.jicofo.ALWAYS_TRUST_MODE_ENABLED" = "true";
+      };
+    };
+  };
+
+  meta.maintainers = lib.teams.jitsi.members;
+}
diff --git a/nixos/modules/services/web-apps/moodle.nix b/nixos/modules/services/web-apps/moodle.nix
index 1196780cf6ef9..f45eaa24d544e 100644
--- a/nixos/modules/services/web-apps/moodle.nix
+++ b/nixos/modules/services/web-apps/moodle.nix
@@ -40,7 +40,7 @@ let
   $CFG->disableupdateautodeploy = true;
 
   $CFG->pathtogs = '${pkgs.ghostscript}/bin/gs';
-  $CFG->pathtophp = '${pkgs.php}/bin/php';
+  $CFG->pathtophp = '${phpExt}/bin/php';
   $CFG->pathtodu = '${pkgs.coreutils}/bin/du';
   $CFG->aspellpath = '${pkgs.aspell}/bin/aspell';
   $CFG->pathtodot = '${pkgs.graphviz}/bin/dot';
@@ -55,6 +55,9 @@ let
 
   mysqlLocal = cfg.database.createLocally && cfg.database.type == "mysql";
   pgsqlLocal = cfg.database.createLocally && cfg.database.type == "pgsql";
+
+  phpExt = pkgs.php.withExtensions
+        ({ enabled, all }: with all; [ iconv mbstring curl openssl tokenizer xmlrpc soap ctype zip gd simplexml dom  intl json sqlite3 pgsql pdo_sqlite pdo_pgsql pdo_odbc pdo_mysql pdo mysqli session zlib xmlreader fileinfo ]);
 in
 {
   # interface
@@ -222,6 +225,7 @@ in
 
     services.phpfpm.pools.moodle = {
       inherit user group;
+      phpPackage = phpExt;
       phpEnv.MOODLE_CONFIG = "${moodleConfig}";
       phpOptions = ''
         zend_extension = opcache.so
@@ -263,13 +267,13 @@ in
       after = optional mysqlLocal "mysql.service" ++ optional pgsqlLocal "postgresql.service";
       environment.MOODLE_CONFIG = moodleConfig;
       script = ''
-        ${pkgs.php}/bin/php ${cfg.package}/share/moodle/admin/cli/check_database_schema.php && rc=$? || rc=$?
+        ${phpExt}/bin/php ${cfg.package}/share/moodle/admin/cli/check_database_schema.php && rc=$? || rc=$?
 
-        [ "$rc" == 1 ] && ${pkgs.php}/bin/php ${cfg.package}/share/moodle/admin/cli/upgrade.php \
+        [ "$rc" == 1 ] && ${phpExt}/bin/php ${cfg.package}/share/moodle/admin/cli/upgrade.php \
           --non-interactive \
           --allow-unstable
 
-        [ "$rc" == 2 ] && ${pkgs.php}/bin/php ${cfg.package}/share/moodle/admin/cli/install_database.php \
+        [ "$rc" == 2 ] && ${phpExt}/bin/php ${cfg.package}/share/moodle/admin/cli/install_database.php \
           --agree-license \
           --adminpass=${cfg.initialPassword}
 
@@ -289,7 +293,7 @@ in
       serviceConfig = {
         User = user;
         Group = group;
-        ExecStart = "${pkgs.php}/bin/php ${cfg.package}/share/moodle/admin/cli/cron.php";
+        ExecStart = "${phpExt}/bin/php ${cfg.package}/share/moodle/admin/cli/cron.php";
       };
     };
 
diff --git a/nixos/modules/services/web-apps/nextcloud.nix b/nixos/modules/services/web-apps/nextcloud.nix
index 328561dc80078..7da119758fc91 100644
--- a/nixos/modules/services/web-apps/nextcloud.nix
+++ b/nixos/modules/services/web-apps/nextcloud.nix
@@ -45,6 +45,22 @@ let
   inherit (config.system) stateVersion;
 
 in {
+
+  imports = [
+    (mkRemovedOptionModule [ "services" "nextcloud" "nginx" "enable" ] ''
+      The nextcloud module supports `nginx` as reverse-proxy by default and doesn't
+      support other reverse-proxies officially.
+
+      However it's possible to use an alternative reverse-proxy by
+
+        * disabling nginx
+        * setting `listen.owner` & `listen.group` in the phpfpm-pool to a different value
+
+      Further details about this can be found in the `Nextcloud`-section of the NixOS-manual
+      (which can be openend e.g. by running `nixos-help`).
+    '')
+  ];
+
   options.services.nextcloud = {
     enable = mkEnableOption "nextcloud";
     hostName = mkOption {
@@ -91,16 +107,6 @@ in {
       '';
     };
 
-    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;
@@ -468,10 +474,18 @@ in {
           script = ''
             chmod og+x ${cfg.home}
             ln -sf ${cfg.package}/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
+            # create nextcloud directories.
+            # if the directories exist already with wrong permissions, we fix that
+            for dir in ${cfg.home}/config ${cfg.home}/data ${cfg.home}/store-apps; do
+              if [ ! -e $dir ]; then
+                install -o nextcloud -g nextcloud -d $dir
+              elif [ $(stat -c "%G" $dir) != "nextcloud" ]; then
+                chgrp -R nextcloud $dir
+              fi
+            done
+
+            ln -sf ${overrideConfig} ${cfg.home}/config/override.config.php
 
             # Do not install if already installed
             if [[ ! -e ${cfg.home}/config/config.php ]]; then
@@ -484,6 +498,7 @@ in {
             ${occSetTrustedDomainsCmd}
           '';
           serviceConfig.Type = "oneshot";
+          serviceConfig.User = "nextcloud";
         };
         nextcloud-cron = {
           environment.NEXTCLOUD_CONFIG_DIR = "${cfg.home}/config";
@@ -502,7 +517,7 @@ in {
       services.phpfpm = {
         pools.nextcloud = {
           user = "nextcloud";
-          group = "nginx";
+          group = "nextcloud";
           phpOptions = phpOptionsStr;
           phpPackage = phpPackage;
           phpEnv = {
@@ -510,128 +525,121 @@ in {
             PATH = "/run/wrappers/bin:/nix/var/nix/profiles/default/bin:/run/current-system/sw/bin:/usr/bin:/bin";
           };
           settings = mapAttrs (name: mkDefault) {
-            "listen.owner" = "nginx";
-            "listen.group" = "nginx";
+            "listen.owner" = config.services.nginx.user;
+            "listen.group" = config.services.nginx.group;
           } // cfg.poolSettings;
           extraConfig = cfg.poolConfig;
         };
       };
 
-      users.extraUsers.nextcloud = {
+      users.users.nextcloud = {
         home = "${cfg.home}";
-        group = "nginx";
+        group = "nextcloud";
         createHome = true;
       };
+      users.groups.nextcloud.members = [ "nextcloud" config.services.nginx.user ];
 
       environment.systemPackages = [ occ ];
-    }
 
-    (mkIf cfg.nginx.enable {
-      services.nginx = {
-        enable = true;
-        virtualHosts = {
-          ${cfg.hostName} = {
-            root = cfg.package;
-            locations = {
-              "= /robots.txt" = {
-                priority = 100;
-                extraConfig = ''
-                  allow all;
-                  log_not_found off;
-                  access_log off;
-                '';
-              };
-              "/" = {
-                priority = 200;
-                extraConfig = "rewrite ^ /index.php;";
-              };
-              "~ ^/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\\/.+|ocm-provider\\/.+)\\.php(?:$|\\/)" = {
-                priority = 500;
-                extraConfig = ''
-                  include ${config.services.nginx.package}/conf/fastcgi.conf;
-                  fastcgi_split_path_info ^(.+\.php)(\\/.*)$;
-                  try_files $fastcgi_script_name =404;
-                  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:${fpm.socket};
-                  fastcgi_intercept_errors on;
-                  fastcgi_request_buffering off;
-                  fastcgi_read_timeout 120s;
-                '';
-              };
-              "~ ^\\/(?:updater|ocs-provider|ocm-provider)(?:$|\\/)".extraConfig = ''
-                try_files $uri/ =404;
-                index index.php;
-              '';
-              "~ \\.(?:css|js|woff2?|svg|gif)$".extraConfig = ''
-                try_files $uri /index.php$request_uri;
-                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;
-                add_header X-Frame-Options sameorigin;
-                add_header Referrer-Policy no-referrer;
-                access_log off;
-              '';
-              "~ \\.(?:png|html|ttf|ico|jpg|jpeg|bcmap|mp4|webm)$".extraConfig = ''
-                try_files $uri /index.php$request_uri;
-                access_log off;
-              '';
-            };
+      services.nginx.enable = mkDefault true;
+      services.nginx.virtualHosts.${cfg.hostName} = {
+        root = cfg.package;
+        locations = {
+          "= /robots.txt" = {
+            priority = 100;
             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;
-              add_header X-Frame-Options sameorigin;
-              add_header Referrer-Policy no-referrer;
-              add_header Strict-Transport-Security "max-age=15552000; includeSubDomains" always;
-              error_page 403 /core/templates/403.php;
-              error_page 404 /core/templates/404.php;
-              client_max_body_size ${cfg.maxUploadSize};
-              fastcgi_buffers 64 4K;
-              fastcgi_hide_header X-Powered-By;
-              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;
-              ''}
+              allow all;
+              log_not_found off;
+              access_log off;
             '';
           };
+          "/" = {
+            priority = 900;
+            extraConfig = "try_files $uri $uri/ /index.php$request_uri;";
+          };
+          "~ ^/store-apps" = {
+            priority = 201;
+            extraConfig = "root ${cfg.home};";
+          };
+          "^~ /.well-known" = {
+            priority = 210;
+            extraConfig = ''
+              location = /.well-known/carddav {
+                return 301 $scheme://$host/remote.php/dav;
+              }
+              location = /.well-known/caldav {
+                return 301 $scheme://$host/remote.php/dav;
+              }
+              try_files $uri $uri/ =404;
+            '';
+          };
+          "~ ^/(?:build|tests|config|lib|3rdparty|templates|data)(?:$|/)".extraConfig = ''
+            return 404;
+          '';
+          "~ ^/(?:\\.|autotest|occ|issue|indie|db_|console)".extraConfig = ''
+            return 404;
+          '';
+          "~ \\.php(?:$|/)" = {
+            priority = 500;
+            extraConfig = ''
+              include ${config.services.nginx.package}/conf/fastcgi.conf;
+              fastcgi_split_path_info ^(.+?\.php)(\\/.*)$;
+              set $path_info $fastcgi_path_info;
+              try_files $fastcgi_script_name =404;
+              fastcgi_param PATH_INFO $path_info;
+              fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
+              fastcgi_param HTTPS ${if cfg.https then "on" else "off"};
+              fastcgi_param modHeadersAvailable true;
+              fastcgi_param front_controller_active true;
+              fastcgi_pass unix:${fpm.socket};
+              fastcgi_intercept_errors on;
+              fastcgi_request_buffering off;
+              fastcgi_read_timeout 120s;
+            '';
+          };
+          "~ \\.(?:css|js|svg|gif|map)$".extraConfig = ''
+            try_files $uri /index.php$request_uri;
+            expires 6M;
+            access_log off;
+          '';
+          "~ \\.woff2?$".extraConfig = ''
+            try_files $uri /index.php$request_uri;
+            expires 7d;
+            access_log off;
+          '';
+          "~ ^\\/(?:updater|ocs-provider|ocm-provider)(?:$|\\/)".extraConfig = ''
+            try_files $uri/ =404;
+            index index.php;
+          '';
         };
+        extraConfig = ''
+          index index.php index.html /index.php$request_uri;
+          expires 1m;
+          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;
+          add_header X-Frame-Options sameorigin;
+          add_header Referrer-Policy no-referrer;
+          add_header Strict-Transport-Security "max-age=15552000; includeSubDomains" always;
+          client_max_body_size ${cfg.maxUploadSize};
+          fastcgi_buffers 64 4K;
+          fastcgi_hide_header X-Powered-By;
+          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;
+          ''}
+        '';
       };
-    })
+    }
   ]);
 
   meta.doc = ./nextcloud.xml;
diff --git a/nixos/modules/services/web-apps/nextcloud.xml b/nixos/modules/services/web-apps/nextcloud.xml
index 332e4d1ff3e89..02e4dba286108 100644
--- a/nixos/modules/services/web-apps/nextcloud.xml
+++ b/nixos/modules/services/web-apps/nextcloud.xml
@@ -29,7 +29,6 @@
   services.nextcloud = {
     <link linkend="opt-services.nextcloud.enable">enable</link> = true;
     <link linkend="opt-services.nextcloud.hostName">hostName</link> = "nextcloud.tld";
-    <link linkend="opt-services.nextcloud.nginx.enable">nginx.enable</link> = true;
     config = {
       <link linkend="opt-services.nextcloud.config.dbtype">dbtype</link> = "pgsql";
       <link linkend="opt-services.nextcloud.config.dbuser">dbuser</link> = "nextcloud";
@@ -61,9 +60,8 @@
   </para>
 
   <para>
-   The options <literal>hostName</literal> and <literal>nginx.enable</literal>
-   are used internally to configure an HTTP server using
-   <literal><link xlink:href="https://php-fpm.org/">PHP-FPM</link></literal>
+   The <literal>hostName</literal> option is used internally to configure an HTTP
+   server using <literal><link xlink:href="https://php-fpm.org/">PHP-FPM</link></literal>
    and <literal>nginx</literal>. The <literal>config</literal> attribute set is
    used by the imperative installer and all values are written to an additional file
    to ensure that changes can be applied by changing the module's options.
@@ -125,6 +123,61 @@
   </para>
  </section>
 
+ <section xml:id="module-services-nextcloud-httpd">
+  <title>Using an alternative webserver as reverse-proxy (e.g. <literal>httpd</literal>)</title>
+  <para>
+   By default, <package>nginx</package> is used as reverse-proxy for <package>nextcloud</package>.
+   However, it's possible to use e.g. <package>httpd</package> by explicitly disabling
+   <package>nginx</package> using <xref linkend="opt-services.nginx.enable" /> and fixing the
+   settings <literal>listen.owner</literal> &amp; <literal>listen.group</literal> in the
+   <link linkend="opt-services.phpfpm.pools">corresponding <literal>phpfpm</literal> pool</link>.
+  </para>
+  <para>
+   An exemplary configuration may look like this:
+<programlisting>{ config, lib, pkgs, ... }: {
+  <link linkend="opt-services.nginx.enable">services.nginx.enable</link> = false;
+  services.nextcloud = {
+    <link linkend="opt-services.nextcloud.enable">enable</link> = true;
+    <link linkend="opt-services.nextcloud.hostName">hostName</link> = "localhost";
+
+    /* further, required options */
+  };
+  <link linkend="opt-services.phpfpm.pools._name_.settings">services.phpfpm.pools.nextcloud.settings</link> = {
+    "listen.owner" = config.services.httpd.user;
+    "listen.group" = config.services.httpd.group;
+  };
+  services.httpd = {
+    <link linkend="opt-services.httpd.enable">enable</link> = true;
+    <link linkend="opt-services.httpd.adminAddr">adminAddr</link> = "webmaster@localhost";
+    <link linkend="opt-services.httpd.extraModules">extraModules</link> = [ "proxy_fcgi" ];
+    virtualHosts."localhost" = {
+      <link linkend="opt-services.httpd.virtualHosts._name_.documentRoot">documentRoot</link> = config.services.nextcloud.package;
+      <link linkend="opt-services.httpd.virtualHosts._name_.extraConfig">extraConfig</link> = ''
+        &lt;Directory "${config.services.nextcloud.package}"&gt;
+          &lt;FilesMatch "\.php$"&gt;
+            &lt;If "-f %{REQUEST_FILENAME}"&gt;
+              SetHandler "proxy:unix:${config.services.phpfpm.pools.nextcloud.socket}|fcgi://localhost/"
+            &lt;/If&gt;
+          &lt;/FilesMatch&gt;
+          &lt;IfModule mod_rewrite.c&gt;
+            RewriteEngine On
+            RewriteBase /
+            RewriteRule ^index\.php$ - [L]
+            RewriteCond %{REQUEST_FILENAME} !-f
+            RewriteCond %{REQUEST_FILENAME} !-d
+            RewriteRule . /index.php [L]
+          &lt;/IfModule&gt;
+          DirectoryIndex index.php
+          Require all granted
+          Options +FollowSymLinks
+        &lt;/Directory&gt;
+      '';
+    };
+  };
+}</programlisting>
+  </para>
+ </section>
+
  <section xml:id="module-services-nextcloud-maintainer-info">
   <title>Maintainer information</title>
 
diff --git a/nixos/modules/services/web-apps/pgpkeyserver-lite.nix b/nixos/modules/services/web-apps/pgpkeyserver-lite.nix
index ad70ba70bbeff..838fd19ad2949 100644
--- a/nixos/modules/services/web-apps/pgpkeyserver-lite.nix
+++ b/nixos/modules/services/web-apps/pgpkeyserver-lite.nix
@@ -33,7 +33,7 @@ in
         description = "
           Which hostname to set the vHost to that is proxying to sks.
         ";
-      };     
+      };
 
       hkpAddress = mkOption {
         default = builtins.head sksCfg.hkpAddress;
diff --git a/nixos/modules/services/web-apps/rss-bridge.nix b/nixos/modules/services/web-apps/rss-bridge.nix
new file mode 100644
index 0000000000000..f1d5b7660f320
--- /dev/null
+++ b/nixos/modules/services/web-apps/rss-bridge.nix
@@ -0,0 +1,127 @@
+{ config, lib, pkgs, ... }:
+with lib;
+let
+  cfg = config.services.rss-bridge;
+
+  poolName = "rss-bridge";
+
+  whitelist = pkgs.writeText "rss-bridge_whitelist.txt"
+    (concatStringsSep "\n" cfg.whitelist);
+in
+{
+  options = {
+    services.rss-bridge = {
+      enable = mkEnableOption "rss-bridge";
+
+      user = mkOption {
+        type = types.str;
+        default = "nginx";
+        example = "nginx";
+        description = ''
+          User account under which both the service and the web-application run.
+        '';
+      };
+
+      group = mkOption {
+        type = types.str;
+        default = "nginx";
+        example = "nginx";
+        description = ''
+          Group under which the web-application run.
+        '';
+      };
+
+      pool = mkOption {
+        type = types.str;
+        default = poolName;
+        description = ''
+          Name of existing phpfpm pool that is used to run web-application.
+          If not specified a pool will be created automatically with
+          default values.
+        '';
+      };
+
+      dataDir = mkOption {
+        type = types.str;
+        default = "/var/lib/rss-bridge";
+        description = ''
+          Location in which cache directory will be created.
+          You can put <literal>config.ini.php</literal> in here.
+        '';
+      };
+
+      virtualHost = mkOption {
+        type = types.nullOr types.str;
+        default = "rss-bridge";
+        description = ''
+          Name of the nginx virtualhost to use and setup. If null, do not setup any virtualhost.
+        '';
+      };
+
+      whitelist = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        example = options.literalExample ''
+          [
+            "Facebook"
+            "Instagram"
+            "Twitter"
+          ]
+        '';
+        description = ''
+          List of bridges to be whitelisted.
+          If the list is empty, rss-bridge will use whitelist.default.txt.
+          Use <literal>[ "*" ]</literal> to whitelist all.
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    services.phpfpm.pools = mkIf (cfg.pool == poolName) {
+      ${poolName} = {
+        user = cfg.user;
+        settings = mapAttrs (name: mkDefault) {
+          "listen.owner" = cfg.user;
+          "listen.group" = cfg.user;
+          "listen.mode" = "0600";
+          "pm" = "dynamic";
+          "pm.max_children" = 75;
+          "pm.start_servers" = 10;
+          "pm.min_spare_servers" = 5;
+          "pm.max_spare_servers" = 20;
+          "pm.max_requests" = 500;
+          "catch_workers_output" = 1;
+        };
+      };
+    };
+    systemd.tmpfiles.rules = [
+      "d '${cfg.dataDir}/cache' 0750 ${cfg.user} ${cfg.group} - -"
+      (mkIf (cfg.whitelist != []) "L+ ${cfg.dataDir}/whitelist.txt - - - - ${whitelist}")
+      "z '${cfg.dataDir}/config.ini.php' 0750 ${cfg.user} ${cfg.group} - -"
+    ];
+
+    services.nginx = mkIf (cfg.virtualHost != null) {
+      enable = true;
+      virtualHosts = {
+        ${cfg.virtualHost} = {
+          root = "${pkgs.rss-bridge}";
+
+          locations."/" = {
+            tryFiles = "$uri /index.php$is_args$args";
+          };
+
+          locations."~ ^/index.php(/|$)" = {
+            extraConfig = ''
+              include ${pkgs.nginx}/conf/fastcgi_params;
+              fastcgi_split_path_info ^(.+\.php)(/.+)$;
+              fastcgi_pass unix:${config.services.phpfpm.pools.${cfg.pool}.socket};
+              fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
+              fastcgi_param RSSBRIDGE_DATA ${cfg.dataDir};
+            '';
+          };
+        };
+      };
+    };
+  };
+}
diff --git a/nixos/modules/services/web-apps/trilium.nix b/nixos/modules/services/web-apps/trilium.nix
index 6f47193c62b9d..3fa8dad049083 100644
--- a/nixos/modules/services/web-apps/trilium.nix
+++ b/nixos/modules/services/web-apps/trilium.nix
@@ -83,7 +83,7 @@ in
     };
   };
 
-  config = lib.mkIf cfg.enable (lib.mkMerge [ 
+  config = lib.mkIf cfg.enable (lib.mkMerge [
   {
     meta.maintainers = with lib.maintainers; [ kampka ];
 
diff --git a/nixos/modules/services/web-apps/tt-rss.nix b/nixos/modules/services/web-apps/tt-rss.nix
index 2ea9537b93de0..6a29f10d11954 100644
--- a/nixos/modules/services/web-apps/tt-rss.nix
+++ b/nixos/modules/services/web-apps/tt-rss.nix
@@ -632,8 +632,6 @@ let
           User = "${cfg.user}";
           Group = "tt_rss";
           ExecStart = "${pkgs.php}/bin/php ${cfg.root}/update.php --daemon --quiet";
-          StandardOutput = "syslog";
-          StandardError = "syslog";
           Restart = "on-failure";
           RestartSec = "60";
           SyslogIdentifier = "tt-rss";
diff --git a/nixos/modules/services/web-servers/apache-httpd/default.nix b/nixos/modules/services/web-servers/apache-httpd/default.nix
index 8abee7130d7c7..fc4c2945394c3 100644
--- a/nixos/modules/services/web-servers/apache-httpd/default.nix
+++ b/nixos/modules/services/web-servers/apache-httpd/default.nix
@@ -10,6 +10,12 @@ let
 
   pkg = cfg.package.out;
 
+  apachectl = pkgs.runCommand "apachectl" { meta.priority = -1; } ''
+    mkdir -p $out/bin
+    cp ${pkg}/bin/apachectl $out/bin/apachectl
+    sed -i $out/bin/apachectl -e 's|$HTTPD -t|$HTTPD -t -f ${httpdConf}|'
+  '';
+
   httpdConf = cfg.configFile;
 
   php = cfg.phpPackage.override { apacheHttpd = pkg; };
@@ -650,10 +656,29 @@ in
       postRun = "systemctl reload httpd.service";
     }) (filterAttrs (name: hostOpts: hostOpts.enableACME) cfg.virtualHosts);
 
-    environment.systemPackages = [ pkg ];
+    environment.systemPackages = [
+      apachectl
+      pkg
+    ];
 
-    # required for "apachectl configtest"
-    environment.etc."httpd/httpd.conf".source = httpdConf;
+    services.logrotate = optionalAttrs (cfg.logFormat != "none") {
+      enable = mkDefault true;
+      paths.httpd = {
+        path = "${cfg.logDir}/*.log";
+        user = cfg.user;
+        group = cfg.group;
+        frequency = "daily";
+        keep = 28;
+        extraConfig = ''
+          sharedscripts
+          compress
+          delaycompress
+          postrotate
+            systemctl reload httpd.service > /dev/null 2>/dev/null || true
+          endscript
+        '';
+      };
+    };
 
     services.httpd.phpOptions =
       ''
@@ -708,6 +733,7 @@ in
         wantedBy = [ "multi-user.target" ];
         wants = concatLists (map (hostOpts: [ "acme-${hostOpts.hostName}.service" "acme-selfsigned-${hostOpts.hostName}.service" ]) vhostsACME);
         after = [ "network.target" "fs.target" ] ++ map (hostOpts: "acme-selfsigned-${hostOpts.hostName}.service") vhostsACME;
+        before = map (hostOpts: "acme-${hostOpts.hostName}.service") vhostsACME;
 
         path = [ pkg pkgs.coreutils pkgs.gnugrep ];
 
diff --git a/nixos/modules/services/web-servers/jboss/builder.sh b/nixos/modules/services/web-servers/jboss/builder.sh
index 2eb89a90f67d5..0e5af324c13fe 100644
--- a/nixos/modules/services/web-servers/jboss/builder.sh
+++ b/nixos/modules/services/web-servers/jboss/builder.sh
@@ -28,11 +28,11 @@ stop()
 if test "\$1" = start
 then
   trap stop 15
-  
+
   start
 elif test "\$1" = stop
 then
-  stop  
+  stop
 elif test "\$1" = init
 then
   echo "Are you sure you want to create a new server instance (old server instance will be lost!)?"
@@ -42,21 +42,21 @@ then
   then
     exit 1
   fi
-  
+
   rm -rf $serverDir
   mkdir -p $serverDir
   cd $serverDir
   cp -av $jboss/server/default .
   sed -i -e "s|deploy/|$deployDir|" default/conf/jboss-service.xml
-  
+
   if ! test "$useJK" = ""
   then
     sed -i -e 's|<attribute name="UseJK">false</attribute>|<attribute name="UseJK">true</attribute>|' default/deploy/jboss-web.deployer/META-INF/jboss-service.xml
     sed -i -e 's|<Engine name="jboss.web" defaultHost="localhost">|<Engine name="jboss.web" defaultHost="localhost" jvmRoute="node1">|' default/deploy/jboss-web.deployer/server.xml
   fi
-  
+
   # Make files accessible for the server user
-  
+
   chown -R $user $serverDir
   for i in \`find $serverDir -type d\`
   do
diff --git a/nixos/modules/services/web-servers/meguca.nix b/nixos/modules/services/web-servers/meguca.nix
deleted file mode 100644
index 5a00070dc9416..0000000000000
--- a/nixos/modules/services/web-servers/meguca.nix
+++ /dev/null
@@ -1,174 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-let
-  cfg = config.services.meguca;
-  postgres = config.services.postgresql;
-in with lib; {
-  options.services.meguca = {
-    enable = mkEnableOption "meguca";
-
-    dataDir = mkOption {
-      type = types.path;
-      default = "/var/lib/meguca";
-      example = "/home/okina/meguca";
-      description = "Location where meguca stores it's database and links.";
-    };
-
-    password = mkOption {
-      type = types.str;
-      default = "meguca";
-      example = "dumbpass";
-      description = "Password for the meguca database.";
-    };
-
-    passwordFile = mkOption {
-      type = types.path;
-      default = "/run/keys/meguca-password-file";
-      example = "/home/okina/meguca/keys/pass";
-      description = "Password file for the meguca database.";
-    };
-
-    reverseProxy = mkOption {
-      type = types.nullOr types.str;
-      default = null;
-      example = "192.168.1.5";
-      description = "Reverse proxy IP.";
-    };
-
-    sslCertificate = mkOption {
-      type = types.nullOr types.str;
-      default = null;
-      example = "/home/okina/meguca/ssl.cert";
-      description = "Path to the SSL certificate.";
-    };
-
-    listenAddress = mkOption {
-      type = types.nullOr types.str;
-      default = null;
-      example = "127.0.0.1:8000";
-      description = "Listen on a specific IP address and port.";
-    };
-
-    cacheSize = mkOption {
-      type = types.nullOr types.int;
-      default = null;
-      example = 256;
-      description = "Cache size in MB.";
-    };
-
-    postgresArgs = mkOption {
-      type = types.str;
-      example = "user=meguca password=dumbpass dbname=meguca sslmode=disable";
-      description = "Postgresql connection arguments.";
-    };
-
-    postgresArgsFile = mkOption {
-      type = types.path;
-      default = "/run/keys/meguca-postgres-args";
-      example = "/home/okina/meguca/keys/postgres";
-      description = "Postgresql connection arguments file.";
-    };
-
-    compressTraffic = mkOption {
-      type = types.bool;
-      default = false;
-      description = "Compress all traffic with gzip.";
-    };
-
-    assumeReverseProxy = mkOption {
-      type = types.bool;
-      default = false;
-      description = "Assume the server is behind a reverse proxy, when resolving client IPs.";
-    };
-
-    httpsOnly = mkOption {
-      type = types.bool;
-      default = false;
-      description = "Serve and listen only through HTTPS.";
-    };
-
-    videoPaths = mkOption {
-      type = types.listOf types.path;
-      default = [];
-      example = [ "/home/okina/Videos/tehe_pero.webm" ];
-      description = "Videos that will be symlinked into www/videos.";
-    };
-  };
-
-  config = mkIf cfg.enable {
-    security.sudo.enable = cfg.enable;
-    services.postgresql.enable = cfg.enable;
-    services.postgresql.package = pkgs.postgresql_11;
-    services.meguca.passwordFile = mkDefault (pkgs.writeText "meguca-password-file" cfg.password);
-    services.meguca.postgresArgsFile = mkDefault (pkgs.writeText "meguca-postgres-args" cfg.postgresArgs);
-    services.meguca.postgresArgs = mkDefault "user=meguca password=${cfg.password} dbname=meguca sslmode=disable";
-
-    systemd.services.meguca = {
-      description = "meguca";
-      after = [ "network.target" "postgresql.service" ];
-      wantedBy = [ "multi-user.target" ];
-
-      preStart = ''
-        # Ensure folder exists or create it and links and permissions are correct
-        mkdir -p ${escapeShellArg cfg.dataDir}/www
-        rm -rf ${escapeShellArg cfg.dataDir}/www/videos
-        ln -sf ${pkgs.meguca}/share/meguca/www/* ${escapeShellArg cfg.dataDir}/www
-        unlink ${escapeShellArg cfg.dataDir}/www/videos
-        mkdir -p ${escapeShellArg cfg.dataDir}/www/videos
-
-        for vid in ${escapeShellArg cfg.videoPaths}; do
-          ln -sf $vid ${escapeShellArg cfg.dataDir}/www/videos
-        done
-
-        chmod 750 ${escapeShellArg cfg.dataDir}
-        chown -R meguca:meguca ${escapeShellArg cfg.dataDir}
-
-        # Ensure the database is correct or create it
-        ${pkgs.sudo}/bin/sudo -u ${postgres.superUser} ${postgres.package}/bin/createuser \
-          -SDR meguca || true
-        ${pkgs.sudo}/bin/sudo -u ${postgres.superUser} ${postgres.package}/bin/createdb \
-          -T template0 -E UTF8 -O meguca meguca || true
-        ${pkgs.sudo}/bin/sudo -u meguca ${postgres.package}/bin/psql \
-          -c "ALTER ROLE meguca WITH PASSWORD '$(cat ${escapeShellArg cfg.passwordFile})';" || true
-      '';
-
-    script = ''
-      cd ${escapeShellArg cfg.dataDir}
-
-      ${pkgs.meguca}/bin/meguca -d "$(cat ${escapeShellArg cfg.postgresArgsFile})"''
-      + optionalString (cfg.reverseProxy != null) " -R ${cfg.reverseProxy}"
-      + optionalString (cfg.sslCertificate != null) " -S ${cfg.sslCertificate}"
-      + optionalString (cfg.listenAddress != null) " -a ${cfg.listenAddress}"
-      + optionalString (cfg.cacheSize != null) " -c ${toString cfg.cacheSize}"
-      + optionalString (cfg.compressTraffic) " -g"
-      + optionalString (cfg.assumeReverseProxy) " -r"
-      + optionalString (cfg.httpsOnly) " -s" + " start";
-
-      serviceConfig = {
-        PermissionsStartOnly = true;
-        Type = "forking";
-        User = "meguca";
-        Group = "meguca";
-        ExecStop = "${pkgs.meguca}/bin/meguca stop";
-      };
-    };
-
-    users = {
-      groups.meguca.gid = config.ids.gids.meguca;
-
-      users.meguca = {
-        description = "meguca server service user";
-        home = cfg.dataDir;
-        createHome = true;
-        group = "meguca";
-        uid = config.ids.uids.meguca;
-      };
-    };
-  };
-
-  imports = [
-    (mkRenamedOptionModule [ "services" "meguca" "baseDir" ] [ "services" "meguca" "dataDir" ])
-  ];
-
-  meta.maintainers = with maintainers; [ chiiruno ];
-}
diff --git a/nixos/modules/services/web-servers/molly-brown.nix b/nixos/modules/services/web-servers/molly-brown.nix
new file mode 100644
index 0000000000000..e9052a184b2db
--- /dev/null
+++ b/nixos/modules/services/web-servers/molly-brown.nix
@@ -0,0 +1,117 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.molly-brown;
+
+  settingsType = with types;
+    attrsOf (oneOf [
+      int
+      str
+      (listOf str)
+      (attrsOf (oneOf [ int str (listOf str) (attrsOf str) ]))
+    ]) // {
+      description = "primitive expression convertable to TOML";
+    };
+
+  configFile = pkgs.runCommand "molly-brown.toml" {
+    buildInputs = [ pkgs.remarshal ];
+    preferLocalBuild = true;
+    passAsFile = [ "settings" ];
+    settings = builtins.toJSON cfg.settings;
+  } "remarshal -if json -of toml < $settingsPath > $out";
+in {
+
+  options.services.molly-brown = {
+
+    enable = mkEnableOption "Molly-Brown Gemini server";
+
+    port = mkOption {
+      default = 1965;
+      type = types.port;
+      description = ''
+        TCP port for molly-brown to bind to.
+      '';
+    };
+
+    hostName = mkOption {
+      type = types.str;
+      example = literalExample "config.networking.hostName";
+      default = config.networking.hostName;
+      description = ''
+        The hostname to respond to requests for. Requests for URLs with
+        other hosts will result in a status 53 (PROXY REQUEST REFUSED)
+        response.
+      '';
+    };
+
+    certPath = mkOption {
+      type = types.path;
+      example = "/var/lib/acme/example.com/cert.pem";
+      description = ''
+        Path to TLS certificate. An ACME certificate and key may be
+        shared with an HTTP server, but only if molly-brown has
+        permissions allowing it to read such keys.
+
+        As an example:
+        <programlisting>
+        security.acme.certs."example.com".allowKeysForGroup = true;
+        systemd.services.molly-brown.serviceConfig.SupplementaryGroups =
+          [ config.security.acme.certs."example.com".group ];
+        </programlisting>
+      '';
+    };
+
+    keyPath = mkOption {
+      type = types.path;
+      example = "/var/lib/acme/example.com/key.pem";
+      description = "Path to TLS key. See <option>CertPath</option>.";
+    };
+
+    docBase = mkOption {
+      type = types.path;
+      example = "/var/lib/molly-brown";
+      description = "Base directory for Gemini content.";
+    };
+
+    settings = mkOption {
+      type = settingsType;
+      default = { };
+      description = ''
+        molly-brown configuration. Refer to
+        <link xlink:href="https://tildegit.org/solderpunk/molly-brown/src/branch/master/example.conf"/>
+        for details on supported values.
+      '';
+    };
+
+  };
+
+  config = mkIf cfg.enable {
+
+    services.molly-brown.settings = let logDir = "/var/log/molly-brown";
+    in {
+      Port = cfg.port;
+      Hostname = cfg.hostName;
+      CertPath = cfg.certPath;
+      KeyPath = cfg.keyPath;
+      DocBase = cfg.docBase;
+      AccessLog = "${logDir}/access.log";
+      ErrorLog = "${logDir}/error.log";
+    };
+
+    systemd.services.molly-brown = {
+      description = "Molly Brown gemini server";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig = {
+        DynamicUser = true;
+        LogsDirectory = "molly-brown";
+        ExecStart = "${pkgs.molly-brown}/bin/molly-brown -c ${configFile}";
+        Restart = "always";
+      };
+    };
+
+  };
+
+}
diff --git a/nixos/modules/services/web-servers/nginx/default.nix b/nixos/modules/services/web-servers/nginx/default.nix
index 8a015bb3556c1..461888c4cc4f0 100644
--- a/nixos/modules/services/web-servers/nginx/default.nix
+++ b/nixos/modules/services/web-servers/nginx/default.nix
@@ -693,6 +693,10 @@ in
       wantedBy = [ "multi-user.target" ];
       wants = concatLists (map (vhostConfig: ["acme-${vhostConfig.serverName}.service" "acme-selfsigned-${vhostConfig.serverName}.service"]) acmeEnabledVhosts);
       after = [ "network.target" ] ++ map (vhostConfig: "acme-selfsigned-${vhostConfig.serverName}.service") acmeEnabledVhosts;
+      # Nginx needs to be started in order to be able to request certificates
+      # (it's hosting the acme challenge after all)
+      # This fixes https://github.com/NixOS/nixpkgs/issues/81842
+      before = map (vhostConfig: "acme-${vhostConfig.serverName}.service") acmeEnabledVhosts;
       stopIfChanged = false;
       preStart = ''
         ${cfg.preStart}
@@ -700,7 +704,10 @@ in
       '';
       serviceConfig = {
         ExecStart = execCommand;
-        ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+        ExecReload = [
+          "${execCommand} -t"
+          "${pkgs.coreutils}/bin/kill -HUP $MAINPID"
+        ];
         Restart = "always";
         RestartSec = "10s";
         StartLimitInterval = "1min";
@@ -757,8 +764,7 @@ in
       serviceConfig.TimeoutSec = 60;
       script = ''
         if /run/current-system/systemd/bin/systemctl -q is-active nginx.service ; then
-          ${execCommand} -t && \
-            /run/current-system/systemd/bin/systemctl reload nginx.service
+          /run/current-system/systemd/bin/systemctl reload nginx.service
         fi
       '';
       serviceConfig.RemainAfterExit = true;
diff --git a/nixos/modules/services/web-servers/shellinabox.nix b/nixos/modules/services/web-servers/shellinabox.nix
index 58a02ac59c358..c7c51f873eba8 100644
--- a/nixos/modules/services/web-servers/shellinabox.nix
+++ b/nixos/modules/services/web-servers/shellinabox.nix
@@ -51,7 +51,7 @@ in
           Whether or not to enable SSL (https) support.
         '';
       };
-        
+
       certDirectory = mkOption {
         type = types.nullOr types.path;
         default = null;
diff --git a/nixos/modules/services/web-servers/traefik.nix b/nixos/modules/services/web-servers/traefik.nix
index 0631a26569c8c..4ab7307c3b671 100644
--- a/nixos/modules/services/web-servers/traefik.nix
+++ b/nixos/modules/services/web-servers/traefik.nix
@@ -20,10 +20,10 @@ let
     in valueType;
   dynamicConfigFile = if cfg.dynamicConfigFile == null then
     pkgs.runCommand "config.toml" {
-      buildInputs = [ pkgs.yj ];
+      buildInputs = [ pkgs.remarshal ];
       preferLocalBuild = true;
     } ''
-      yj -jt -i \
+      remarshal -if json -of toml \
         < ${
           pkgs.writeText "dynamic_config.json"
           (builtins.toJSON cfg.dynamicConfigOptions)
diff --git a/nixos/modules/services/web-servers/unit/default.nix b/nixos/modules/services/web-servers/unit/default.nix
index 989866144e1ee..894271d1e55e4 100644
--- a/nixos/modules/services/web-servers/unit/default.nix
+++ b/nixos/modules/services/web-servers/unit/default.nix
@@ -102,7 +102,7 @@ in {
         PIDFile = "/run/unit/unit.pid";
         ExecStart = ''
           ${cfg.package}/bin/unitd --control 'unix:/run/unit/control.unit.sock' --pid '/run/unit/unit.pid' \
-                                   --log '${cfg.logDir}/unit.log' --state '${cfg.stateDir}' \
+                                   --log '${cfg.logDir}/unit.log' --state '${cfg.stateDir}' --tmp '/tmp' \
                                    --user ${cfg.user} --group ${cfg.group}
         '';
         ExecStop = ''
@@ -120,9 +120,12 @@ in {
         ProtectHome = true;
         PrivateTmp = true;
         PrivateDevices = true;
+        PrivateUsers = false;
         ProtectHostname = true;
+        ProtectClock = true;
         ProtectKernelTunables = true;
         ProtectKernelModules = true;
+        ProtectKernelLogs = true;
         ProtectControlGroups = true;
         RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" ];
         LockPersonality = true;
diff --git a/nixos/modules/services/x11/colord.nix b/nixos/modules/services/x11/colord.nix
index cf113ad2af8c9..31ccee6aa33f4 100644
--- a/nixos/modules/services/x11/colord.nix
+++ b/nixos/modules/services/x11/colord.nix
@@ -26,7 +26,7 @@ in {
 
     systemd.packages = [ pkgs.colord ];
 
-    environment.etc."tmpfiles.d/colord.conf".source = "${pkgs.colord}/lib/tmpfiles.d/colord.conf";
+    systemd.tmpfiles.packages = [ pkgs.colord ];
 
     users.users.colord = {
       isSystemUser = true;
diff --git a/nixos/modules/services/x11/desktop-managers/plasma5.nix b/nixos/modules/services/x11/desktop-managers/plasma5.nix
index 6d48b899d231f..75bf55a26396d 100644
--- a/nixos/modules/services/x11/desktop-managers/plasma5.nix
+++ b/nixos/modules/services/x11/desktop-managers/plasma5.nix
@@ -321,7 +321,7 @@ in
 
       fonts.fonts = with pkgs; [ noto-fonts hack-font ];
       fonts.fontconfig.defaultFonts = {
-        monospace = [ "Hack" "Noto Mono" ];
+        monospace = [ "Hack" "Noto Sans Mono" ];
         sansSerif = [ "Noto Sans" ];
         serif = [ "Noto Serif" ];
       };
diff --git a/nixos/modules/services/x11/display-managers/default.nix b/nixos/modules/services/x11/display-managers/default.nix
index aa6a5ec42be85..b8b36aa05324a 100644
--- a/nixos/modules/services/x11/display-managers/default.nix
+++ b/nixos/modules/services/x11/display-managers/default.nix
@@ -332,12 +332,45 @@ in
 
       };
 
+      # Configuration for automatic login. Common for all DM.
+      autoLogin = mkOption {
+        type = types.submodule {
+          options = {
+            enable = mkOption {
+              type = types.bool;
+              default = cfg.displayManager.autoLogin.user != null;
+              description = ''
+                Automatically log in as <option>autoLogin.user</option>.
+              '';
+            };
+
+            user = mkOption {
+              type = types.nullOr types.str;
+              default = null;
+              description = ''
+                User to be used for the automatic login.
+              '';
+            };
+          };
+        };
+
+        default = {};
+        description = ''
+          Auto login configuration attrset.
+        '';
+      };
+
     };
 
   };
 
   config = {
     assertions = [
+      { assertion = cfg.displayManager.autoLogin.enable -> cfg.displayManager.autoLogin.user != null;
+        message = ''
+          services.xserver.displayManager.autoLogin.enable requires services.xserver.displayManager.autoLogin.user to be set
+        '';
+      }
       {
         assertion = cfg.desktopManager.default != null || cfg.windowManager.default != null -> cfg.displayManager.defaultSession == defaultSessionFromLegacyOptions;
         message = "You cannot use both services.xserver.displayManager.defaultSession option and legacy options (services.xserver.desktopManager.default and services.xserver.windowManager.default).";
diff --git a/nixos/modules/services/x11/display-managers/gdm.nix b/nixos/modules/services/x11/display-managers/gdm.nix
index 622ea62f3a917..23ab7f2ae4332 100644
--- a/nixos/modules/services/x11/display-managers/gdm.nix
+++ b/nixos/modules/services/x11/display-managers/gdm.nix
@@ -37,6 +37,22 @@ let
 in
 
 {
+  imports = [
+    (mkRenamedOptionModule [ "services" "xserver" "displayManager" "gdm" "autoLogin" "enable" ] [
+      "services"
+      "xserver"
+      "displayManager"
+      "autoLogin"
+      "enable"
+    ])
+    (mkRenamedOptionModule [ "services" "xserver" "displayManager" "gdm" "autoLogin" "user" ] [
+      "services"
+      "xserver"
+      "displayManager"
+      "autoLogin"
+      "user"
+    ])
+  ];
 
   meta = {
     maintainers = teams.gnome.members;
@@ -56,40 +72,13 @@ in
         debugging messages in GDM
       '';
 
-      autoLogin = mkOption {
-        default = {};
+      # Auto login options specific to GDM
+      autoLogin.delay = mkOption {
+        type = types.int;
+        default = 0;
         description = ''
-          Auto login configuration attrset.
+          Seconds of inactivity after which the autologin will be performed.
         '';
-
-        type = types.submodule {
-          options = {
-            enable = mkOption {
-              type = types.bool;
-              default = false;
-              description = ''
-                Automatically log in as the sepecified <option>autoLogin.user</option>.
-              '';
-            };
-
-            user = mkOption {
-              type = types.nullOr types.str;
-              default = null;
-              description = ''
-                User to be used for the autologin.
-              '';
-            };
-
-            delay = mkOption {
-              type = types.int;
-              default = 0;
-              description = ''
-                Seconds of inactivity after which the autologin will be performed.
-              '';
-            };
-
-          };
-        };
       };
 
       wayland = mkOption {
@@ -128,12 +117,6 @@ in
 
   config = mkIf cfg.gdm.enable {
 
-    assertions = [
-      { assertion = cfg.gdm.autoLogin.enable -> cfg.gdm.autoLogin.user != null;
-        message = "GDM auto-login requires services.xserver.displayManager.gdm.autoLogin.user to be set";
-      }
-    ];
-
     services.xserver.displayManager.lightdm.enable = false;
 
     users.users.gdm =
@@ -217,7 +200,6 @@ in
       KillMode = "mixed";
       IgnoreSIGPIPE = "no";
       BusName = "org.gnome.DisplayManager";
-      StandardOutput = "syslog";
       StandardError = "inherit";
       ExecReload = "${pkgs.coreutils}/bin/kill -SIGHUP $MAINPID";
       KeyringMode = "shared";
@@ -287,14 +269,14 @@ in
     environment.etc."gdm/custom.conf".text = ''
       [daemon]
       WaylandEnable=${if cfg.gdm.wayland then "true" else "false"}
-      ${optionalString cfg.gdm.autoLogin.enable (
+      ${optionalString cfg.autoLogin.enable (
         if cfg.gdm.autoLogin.delay > 0 then ''
           TimedLoginEnable=true
-          TimedLogin=${cfg.gdm.autoLogin.user}
+          TimedLogin=${cfg.autoLogin.user}
           TimedLoginDelay=${toString cfg.gdm.autoLogin.delay}
         '' else ''
           AutomaticLoginEnable=true
-          AutomaticLogin=${cfg.gdm.autoLogin.user}
+          AutomaticLogin=${cfg.autoLogin.user}
         '')
       }
 
diff --git a/nixos/modules/services/x11/display-managers/lightdm-greeters/pantheon.nix b/nixos/modules/services/x11/display-managers/lightdm-greeters/pantheon.nix
index 087c6b9c38ac0..9bc9e2bf6162c 100644
--- a/nixos/modules/services/x11/display-managers/lightdm-greeters/pantheon.nix
+++ b/nixos/modules/services/x11/display-managers/lightdm-greeters/pantheon.nix
@@ -43,7 +43,7 @@ in
     services.xserver.displayManager.lightdm.extraSeatDefaults = "greeter-show-manual-login=true";
 
     environment.etc."lightdm/io.elementary.greeter.conf".source = "${pkgs.pantheon.elementary-greeter}/etc/lightdm/io.elementary.greeter.conf";
-    environment.etc."wingpanel.d/io.elementary.greeter.whitelist".source = "${pkgs.pantheon.elementary-default-settings}/etc/wingpanel.d/io.elementary.greeter.whitelist";
+    environment.etc."wingpanel.d/io.elementary.greeter.allowed".source = "${pkgs.pantheon.elementary-default-settings}/etc/wingpanel.d/io.elementary.greeter.allowed";
 
   };
 }
diff --git a/nixos/modules/services/x11/display-managers/lightdm.nix b/nixos/modules/services/x11/display-managers/lightdm.nix
index 678cade444274..143785db0b4fc 100644
--- a/nixos/modules/services/x11/display-managers/lightdm.nix
+++ b/nixos/modules/services/x11/display-managers/lightdm.nix
@@ -53,8 +53,8 @@ let
       ${optionalString cfg.greeter.enable ''
         greeter-session = ${cfg.greeter.name}
       ''}
-      ${optionalString cfg.autoLogin.enable ''
-        autologin-user = ${cfg.autoLogin.user}
+      ${optionalString dmcfg.autoLogin.enable ''
+        autologin-user = ${dmcfg.autoLogin.user}
         autologin-user-timeout = ${toString cfg.autoLogin.timeout}
         autologin-session = ${sessionData.autologinSession}
       ''}
@@ -82,6 +82,20 @@ in
     ./lightdm-greeters/enso-os.nix
     ./lightdm-greeters/pantheon.nix
     ./lightdm-greeters/tiny.nix
+    (mkRenamedOptionModule [ "services" "xserver" "displayManager" "lightdm" "autoLogin" "enable" ] [
+      "services"
+      "xserver"
+      "displayManager"
+      "autoLogin"
+      "enable"
+    ])
+    (mkRenamedOptionModule [ "services" "xserver" "displayManager" "lightdm" "autoLogin" "user" ] [
+     "services"
+     "xserver"
+     "displayManager"
+     "autoLogin"
+     "user"
+    ])
   ];
 
   options = {
@@ -149,39 +163,13 @@ in
         description = "Extra lines to append to SeatDefaults section.";
       };
 
-      autoLogin = mkOption {
-        default = {};
+      # Configuration for automatic login specific to LightDM
+      autoLogin.timeout = mkOption {
+        type = types.int;
+        default = 0;
         description = ''
-          Configuration for automatic login.
+          Show the greeter for this many seconds before automatic login occurs.
         '';
-
-        type = types.submodule {
-          options = {
-            enable = mkOption {
-              type = types.bool;
-              default = false;
-              description = ''
-                Automatically log in as the specified <option>autoLogin.user</option>.
-              '';
-            };
-
-            user = mkOption {
-              type = types.nullOr types.str;
-              default = null;
-              description = ''
-                User to be used for the automatic login.
-              '';
-            };
-
-            timeout = mkOption {
-              type = types.int;
-              default = 0;
-              description = ''
-                Show the greeter for this many seconds before automatic login occurs.
-              '';
-            };
-          };
-        };
       };
 
     };
@@ -195,17 +183,12 @@ in
           LightDM requires services.xserver.enable to be true
         '';
       }
-      { assertion = cfg.autoLogin.enable -> cfg.autoLogin.user != null;
-        message = ''
-          LightDM auto-login requires services.xserver.displayManager.lightdm.autoLogin.user to be set
-        '';
-      }
-      { assertion = cfg.autoLogin.enable -> sessionData.autologinSession != null;
+      { assertion = dmcfg.autoLogin.enable -> sessionData.autologinSession != null;
         message = ''
           LightDM auto-login requires that services.xserver.displayManager.defaultSession is set.
         '';
       }
-      { assertion = !cfg.greeter.enable -> (cfg.autoLogin.enable && cfg.autoLogin.timeout == 0);
+      { assertion = !cfg.greeter.enable -> (dmcfg.autoLogin.enable && cfg.autoLogin.timeout == 0);
         message = ''
           LightDM can only run without greeter if automatic login is enabled and the timeout for it
           is set to zero.
@@ -218,7 +201,7 @@ in
 
     # Set default session in session chooser to a specified values – basically ignore session history.
     # Auto-login is already covered by a config value.
-    services.xserver.displayManager.job.preStart = optionalString (!cfg.autoLogin.enable && dmcfg.defaultSession != null) ''
+    services.xserver.displayManager.job.preStart = optionalString (!dmcfg.autoLogin.enable && dmcfg.defaultSession != null) ''
       ${setSessionScript}/bin/set-session ${dmcfg.defaultSession}
     '';
 
@@ -270,7 +253,6 @@ in
       KeyringMode = "shared";
       KillMode = "mixed";
       StandardError = "inherit";
-      StandardOutput = "syslog";
     };
 
     environment.etc."lightdm/lightdm.conf".source = lightdmConf;
diff --git a/nixos/modules/services/x11/display-managers/sddm.nix b/nixos/modules/services/x11/display-managers/sddm.nix
index 2f42271da872e..e63bb2e445396 100644
--- a/nixos/modules/services/x11/display-managers/sddm.nix
+++ b/nixos/modules/services/x11/display-managers/sddm.nix
@@ -61,9 +61,9 @@ let
     EnableHidpi=${if cfg.enableHidpi then "true" else "false"}
     SessionDir=${dmcfg.sessionData.desktops}/share/wayland-sessions
 
-    ${optionalString cfg.autoLogin.enable ''
+    ${optionalString dmcfg.autoLogin.enable ''
     [Autologin]
-    User=${cfg.autoLogin.user}
+    User=${dmcfg.autoLogin.user}
     Session=${autoLoginSessionName}.desktop
     Relogin=${boolToString cfg.autoLogin.relogin}
     ''}
@@ -78,6 +78,20 @@ in
   imports = [
     (mkRemovedOptionModule [ "services" "xserver" "displayManager" "sddm" "themes" ]
       "Set the option `services.xserver.displayManager.sddm.package' instead.")
+    (mkRenamedOptionModule [ "services" "xserver" "displayManager" "sddm" "autoLogin" "enable" ] [
+      "services"
+      "xserver"
+      "displayManager"
+      "autoLogin"
+      "enable"
+    ])
+    (mkRenamedOptionModule [ "services" "xserver" "displayManager" "sddm" "autoLogin" "user" ] [
+      "services"
+      "xserver"
+      "displayManager"
+      "autoLogin"
+      "user"
+    ])
   ];
 
   options = {
@@ -153,40 +167,14 @@ in
         '';
       };
 
-      autoLogin = mkOption {
-        default = {};
+      # Configuration for automatic login specific to SDDM
+      autoLogin.relogin = mkOption {
+        type = types.bool;
+        default = false;
         description = ''
-          Configuration for automatic login.
+          If true automatic login will kick in again on session exit (logout), otherwise it
+          will only log in automatically when the display-manager is started.
         '';
-
-        type = types.submodule {
-          options = {
-            enable = mkOption {
-              type = types.bool;
-              default = false;
-              description = ''
-                Automatically log in as <option>autoLogin.user</option>.
-              '';
-            };
-
-            user = mkOption {
-              type = types.nullOr types.str;
-              default = null;
-              description = ''
-                User to be used for the automatic login.
-              '';
-            };
-
-            relogin = mkOption {
-              type = types.bool;
-              default = false;
-              description = ''
-                If true automatic login will kick in again on session exit (logout), otherwise it
-                will only log in automatically when the display-manager is started.
-              '';
-            };
-          };
-        };
       };
 
     };
@@ -201,12 +189,7 @@ in
           SDDM requires services.xserver.enable to be true
         '';
       }
-      { assertion = cfg.autoLogin.enable -> cfg.autoLogin.user != null;
-        message = ''
-          SDDM auto-login requires services.xserver.displayManager.sddm.autoLogin.user to be set
-        '';
-      }
-      { assertion = cfg.autoLogin.enable -> autoLoginSessionName != null;
+      { assertion = dmcfg.autoLogin.enable -> autoLoginSessionName != null;
         message = ''
           SDDM auto-login requires that services.xserver.displayManager.defaultSession is set.
         '';
diff --git a/nixos/modules/services/x11/imwheel.nix b/nixos/modules/services/x11/imwheel.nix
index 3923df498e794..51f72dadbd43e 100644
--- a/nixos/modules/services/x11/imwheel.nix
+++ b/nixos/modules/services/x11/imwheel.nix
@@ -61,7 +61,8 @@ in
             "--kill"
           ] ++ cfg.extraOptions);
           ExecStop = "${pkgs.procps}/bin/pkill imwheel";
-          Restart = "on-failure";
+          RestartSec = 3;
+          Restart = "always";
         };
       };
     };
diff --git a/nixos/modules/services/x11/urserver.nix b/nixos/modules/services/x11/urserver.nix
new file mode 100644
index 0000000000000..0beb62eb766a3
--- /dev/null
+++ b/nixos/modules/services/x11/urserver.nix
@@ -0,0 +1,38 @@
+# urserver service
+{ config, lib, pkgs, ... }:
+
+let
+  cfg = config.services.urserver;
+in {
+
+  options.services.urserver.enable = lib.mkEnableOption "urserver";
+
+  config = lib.mkIf cfg.enable {
+
+    networking.firewall = {
+      allowedTCPPorts = [ 9510 9512 ];
+      allowedUDPPorts = [ 9511 9512 ];
+    };
+
+    systemd.user.services.urserver =  {
+      description = ''
+        Server for Unified Remote: The one-and-only remote for your computer.
+      '';
+      wantedBy = [ "graphical-session.target" ];
+      partOf = [ "graphical-session.target" ];
+      after = [ "network.target" ];
+      serviceConfig = {
+        Type = "forking";
+        ExecStart = ''
+          ${pkgs.urserver}/bin/urserver --daemon
+        '';
+        ExecStop = ''
+          ${pkgs.procps}/bin/pkill urserver
+        '';
+        RestartSec = 3;
+        Restart = "on-failure";
+      };
+    };
+  };
+
+}
diff --git a/nixos/modules/services/x11/window-managers/qtile.nix b/nixos/modules/services/x11/window-managers/qtile.nix
index ad3b65150b01d..cadc316bbc4f0 100644
--- a/nixos/modules/services/x11/window-managers/qtile.nix
+++ b/nixos/modules/services/x11/window-managers/qtile.nix
@@ -19,7 +19,7 @@ in
         waitPID=$!
       '';
     }];
-    
+
     environment.systemPackages = [ pkgs.qtile ];
   };
 }
diff --git a/nixos/modules/services/x11/window-managers/xmonad.nix b/nixos/modules/services/x11/window-managers/xmonad.nix
index 30c59b88f82f9..070758720fe33 100644
--- a/nixos/modules/services/x11/window-managers/xmonad.nix
+++ b/nixos/modules/services/x11/window-managers/xmonad.nix
@@ -82,12 +82,11 @@ in
     services.xserver.windowManager = {
       session = [{
         name = "xmonad";
-        start = if (cfg.config != null) then ''
-          ${xmonadBin}
-          waitPID=$!
-        '' else ''
-          systemd-cat -t xmonad ${xmonad}/bin/xmonad &
-          waitPID=$!
+        start = let
+          xmonadCommand = if (cfg.config != null) then xmonadBin else "${xmonad}/bin/xmonad";
+        in ''
+           systemd-cat -t xmonad ${xmonadCommand} &
+           waitPID=$!
         '';
       }];
     };
diff --git a/nixos/modules/system/activation/top-level.nix b/nixos/modules/system/activation/top-level.nix
index f6739977fa4f1..fb8644dd13a60 100644
--- a/nixos/modules/system/activation/top-level.nix
+++ b/nixos/modules/system/activation/top-level.nix
@@ -92,9 +92,7 @@ let
   # `switch-to-configuration' that activates the configuration and
   # makes it bootable.
   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}";
+    name = "nixos-system-${config.system.name}-${config.system.nixos.label}";
     preferLocalBuild = true;
     allowSubstitutes = false;
     buildCommand = systemBuilder;
@@ -265,6 +263,21 @@ in
       '';
     };
 
+    system.name = mkOption {
+      type = types.str;
+      default =
+        if config.networking.hostName == ""
+        then "unnamed"
+        else config.networking.hostName;
+      defaultText = '''networking.hostName' if non empty else "unnamed"'';
+      description = ''
+        The name of the system used in the <option>system.build.toplevel</option> derivation.
+        </para><para>
+        That derivation has the following name:
+        <literal>"nixos-system-''${config.system.name}-''${config.system.nixos.label}"</literal>
+      '';
+    };
+
   };
 
 
diff --git a/nixos/modules/system/boot/emergency-mode.nix b/nixos/modules/system/boot/emergency-mode.nix
index 9cdab84161925..ec697bcee2680 100644
--- a/nixos/modules/system/boot/emergency-mode.nix
+++ b/nixos/modules/system/boot/emergency-mode.nix
@@ -34,4 +34,4 @@ with lib;
 
   };
 
-}
\ No newline at end of file
+}
diff --git a/nixos/modules/system/boot/initrd-network.nix b/nixos/modules/system/boot/initrd-network.nix
index 0ab6e626b340e..ec794d6eb014d 100644
--- a/nixos/modules/system/boot/initrd-network.nix
+++ b/nixos/modules/system/boot/initrd-network.nix
@@ -139,7 +139,7 @@ in
     boot.initrd.postMountCommands = mkIf cfg.flushBeforeStage2 ''
       for iface in $ifaces; do
         ip address flush "$iface"
-        ip link down "$iface"
+        ip link set "$iface" down
       done
     '';
 
diff --git a/nixos/modules/system/boot/initrd-openvpn.nix b/nixos/modules/system/boot/initrd-openvpn.nix
new file mode 100644
index 0000000000000..e59bc7b6678f2
--- /dev/null
+++ b/nixos/modules/system/boot/initrd-openvpn.nix
@@ -0,0 +1,81 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.boot.initrd.network.openvpn;
+
+in
+
+{
+
+  options = {
+
+    boot.initrd.network.openvpn.enable = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Starts an OpenVPN client during initrd boot. It can be used to e.g.
+        remotely accessing the SSH service controlled by
+        <option>boot.initrd.network.ssh</option> or other network services
+        included. Service is killed when stage-1 boot is finished.
+      '';
+    };
+
+    boot.initrd.network.openvpn.configuration = mkOption {
+      type = types.path; # Same type as boot.initrd.secrets
+      description = ''
+        The configuration file for OpenVPN.
+
+        <warning>
+          <para>
+            Unless your bootloader supports initrd secrets, this configuration
+            is stored insecurely in the global Nix store.
+          </para>
+        </warning>
+      '';
+      example = "./configuration.ovpn";
+    };
+
+  };
+
+  config = mkIf (config.boot.initrd.network.enable && cfg.enable) {
+    assertions = [
+      {
+        assertion = cfg.configuration != null;
+        message = "You should specify a configuration for initrd OpenVPN";
+      }
+    ];
+
+    # Add kernel modules needed for OpenVPN
+    boot.initrd.kernelModules = [ "tun" "tap" ];
+
+    # Add openvpn and ip binaries to the initrd
+    # The shared libraries are required for DNS resolution
+    boot.initrd.extraUtilsCommands = ''
+      copy_bin_and_libs ${pkgs.openvpn}/bin/openvpn
+      copy_bin_and_libs ${pkgs.iproute}/bin/ip
+
+      cp -pv ${pkgs.glibc}/lib/libresolv.so.2 $out/lib
+      cp -pv ${pkgs.glibc}/lib/libnss_dns.so.2 $out/lib
+    '';
+
+    boot.initrd.secrets = {
+      "/etc/initrd.ovpn" = cfg.configuration;
+    };
+
+    # openvpn --version would exit with 1 instead of 0
+    boot.initrd.extraUtilsCommandsTest = ''
+      $out/bin/openvpn --show-gateway
+    '';
+
+    # Add `iproute /bin/ip` to the config, to ensure that openvpn
+    # is able to set the routes
+    boot.initrd.network.postCommands = ''
+      (cat /etc/initrd.ovpn; echo -e '\niproute /bin/ip') | \
+        openvpn /dev/stdin &
+    '';
+  };
+
+}
diff --git a/nixos/modules/system/boot/kernel_config.nix b/nixos/modules/system/boot/kernel_config.nix
index 85bd420e30446..783685c9dfe41 100644
--- a/nixos/modules/system/boot/kernel_config.nix
+++ b/nixos/modules/system/boot/kernel_config.nix
@@ -54,7 +54,7 @@ let
         type = types.bool // { merge = mergeFalseByDefault; };
         default = false;
         description = ''
-          Wether option should generate a failure when unused.
+          Whether option should generate a failure when unused.
           Upon merging values, mandatory wins over optional.
         '';
       };
diff --git a/nixos/modules/system/boot/loader/generations-dir/generations-dir-builder.sh b/nixos/modules/system/boot/loader/generations-dir/generations-dir-builder.sh
index e723b9eb7cb31..8ae23dc988c2d 100644
--- a/nixos/modules/system/boot/loader/generations-dir/generations-dir-builder.sh
+++ b/nixos/modules/system/boot/loader/generations-dir/generations-dir-builder.sh
@@ -63,7 +63,7 @@ addEntry() {
         copyToKernelsDir $kernel; kernel=$result
         copyToKernelsDir $initrd; initrd=$result
     fi
-    
+
     mkdir -p $outdir
     ln -sf $(readlink -f $path) $outdir/system
     ln -sf $(readlink -f $path/init) $outdir/init
diff --git a/nixos/modules/system/boot/loader/generic-extlinux-compatible/default.nix b/nixos/modules/system/boot/loader/generic-extlinux-compatible/default.nix
index af39c7bb68418..bd508bbe8eaa9 100644
--- a/nixos/modules/system/boot/loader/generic-extlinux-compatible/default.nix
+++ b/nixos/modules/system/boot/loader/generic-extlinux-compatible/default.nix
@@ -4,11 +4,15 @@ with lib;
 
 let
   blCfg = config.boot.loader;
+  dtCfg = config.hardware.deviceTree;
   cfg = blCfg.generic-extlinux-compatible;
 
   timeoutStr = if blCfg.timeout == null then "-1" else toString blCfg.timeout;
 
+  # The builder used to write during system activation
   builder = import ./extlinux-conf-builder.nix { inherit pkgs; };
+  # The builder exposed in populateCmd, which runs on the build architecture
+  populateBuilder = import ./extlinux-conf-builder.nix { pkgs = pkgs.buildPackages; };
 in
 {
   options = {
@@ -34,11 +38,28 @@ in
           Maximum number of configurations in the boot menu.
         '';
       };
+
+      populateCmd = mkOption {
+        type = types.str;
+        readOnly = true;
+        description = ''
+          Contains the builder command used to populate an image,
+          honoring all options except the <literal>-c &lt;path-to-default-configuration&gt;</literal>
+          argument.
+          Useful to have for sdImage.populateRootCommands
+        '';
+      };
+
     };
   };
 
-  config = mkIf cfg.enable {
-    system.build.installBootLoader = "${builder} -g ${toString cfg.configurationLimit} -t ${timeoutStr} -c";
-    system.boot.loader.id = "generic-extlinux-compatible";
-  };
+  config = let
+    builderArgs = "-g ${toString cfg.configurationLimit} -t ${timeoutStr}" + lib.optionalString (dtCfg.name != null) " -n ${dtCfg.name}";
+  in
+    mkIf cfg.enable {
+      system.build.installBootLoader = "${builder} ${builderArgs} -c";
+      system.boot.loader.id = "generic-extlinux-compatible";
+
+      boot.loader.generic-extlinux-compatible.populateCmd = "${populateBuilder} ${builderArgs}";
+    };
 }
diff --git a/nixos/modules/system/boot/loader/generic-extlinux-compatible/extlinux-conf-builder.sh b/nixos/modules/system/boot/loader/generic-extlinux-compatible/extlinux-conf-builder.sh
index 0092ee92b62fd..854684b87face 100644
--- a/nixos/modules/system/boot/loader/generic-extlinux-compatible/extlinux-conf-builder.sh
+++ b/nixos/modules/system/boot/loader/generic-extlinux-compatible/extlinux-conf-builder.sh
@@ -6,7 +6,7 @@ export PATH=/empty
 for i in @path@; do PATH=$PATH:$i/bin; done
 
 usage() {
-    echo "usage: $0 -t <timeout> -c <path-to-default-configuration> [-d <boot-dir>] [-g <num-generations>]" >&2
+    echo "usage: $0 -t <timeout> -c <path-to-default-configuration> [-d <boot-dir>] [-g <num-generations>] [-n <dtbName>]" >&2
     exit 1
 }
 
@@ -15,7 +15,7 @@ default=                # Default configuration
 target=/boot            # Target directory
 numGenerations=0        # Number of other generations to include in the menu
 
-while getopts "t:c:d:g:" opt; do
+while getopts "t:c:d:g:n:" opt; do
     case "$opt" in
         t) # U-Boot interprets '0' as infinite and negative as instant boot
             if [ "$OPTARG" -lt 0 ]; then
@@ -29,6 +29,7 @@ while getopts "t:c:d:g:" opt; do
         c) default="$OPTARG" ;;
         d) target="$OPTARG" ;;
         g) numGenerations="$OPTARG" ;;
+        n) dtbName="$OPTARG" ;;
         \?) usage ;;
     esac
 done
@@ -96,7 +97,17 @@ addEntry() {
     echo "  LINUX ../nixos/$(basename $kernel)"
     echo "  INITRD ../nixos/$(basename $initrd)"
     if [ -d "$dtbDir" ]; then
-        echo "  FDTDIR ../nixos/$(basename $dtbs)"
+        # if a dtbName was specified explicitly, use that, else use FDTDIR
+        if [ -n "$dtbName" ]; then
+            echo "  FDT ../nixos/$(basename $dtbs)/${dtbName}"
+        else
+            echo "  FDTDIR ../nixos/$(basename $dtbs)"
+        fi
+    else
+        if [ -n "$dtbName" ]; then
+            echo "Explicitly requested dtbName $dtbName, but there's no FDTDIR - bailing out." >&2
+            exit 1
+        fi
     fi
     echo "  APPEND systemConfig=$path init=$path/init $extraParams"
 }
diff --git a/nixos/modules/system/boot/loader/grub/grub.nix b/nixos/modules/system/boot/loader/grub/grub.nix
index 3975372e15ec5..20e39628eabbc 100644
--- a/nixos/modules/system/boot/loader/grub/grub.nix
+++ b/nixos/modules/system/boot/loader/grub/grub.nix
@@ -55,11 +55,14 @@ let
       storePath = config.boot.loader.grub.storePath;
       bootloaderId = if args.efiBootloaderId == null then "NixOS${efiSysMountPoint'}" else args.efiBootloaderId;
       timeout = if config.boot.loader.timeout == null then -1 else config.boot.loader.timeout;
+      users = if cfg.users == {} || cfg.version != 1 then cfg.users else throw "GRUB version 1 does not support user accounts.";
+      theme = f cfg.theme;
       inherit efiSysMountPoint;
       inherit (args) devices;
       inherit (efi) canTouchEfiVariables;
       inherit (cfg)
         version extraConfig extraPerEntryConfig extraEntries forceInstall useOSProber
+        extraGrubInstallArgs
         extraEntriesBeforeNixOS extraPrepareConfig configurationLimit copyKernels
         default fsIdentifier efiSupport efiInstallAsRemovable gfxmodeEfi gfxmodeBios gfxpayloadEfi gfxpayloadBios;
       path = with pkgs; makeBinPath (
@@ -137,6 +140,67 @@ in
         '';
       };
 
+      users = mkOption {
+        default = {};
+        example = {
+          root = { hashedPasswordFile = "/path/to/file"; };
+        };
+        description = ''
+          User accounts for GRUB. When specified, the GRUB command line and
+          all boot options except the default are password-protected.
+          All passwords and hashes provided will be stored in /boot/grub/grub.cfg,
+          and will be visible to any local user who can read this file. Additionally,
+          any passwords and hashes provided directly in a Nix configuration
+          (as opposed to external files) will be copied into the Nix store, and
+          will be visible to all local users.
+        '';
+        type = with types; attrsOf (submodule {
+          options = {
+            hashedPasswordFile = mkOption {
+              example = "/path/to/file";
+              default = null;
+              type = with types; uniq (nullOr str);
+              description = ''
+                Specifies the path to a file containing the password hash
+                for the account, generated with grub-mkpasswd-pbkdf2.
+                This hash will be stored in /boot/grub/grub.cfg, and will
+                be visible to any local user who can read this file.
+              '';
+            };
+            hashedPassword = mkOption {
+              example = "grub.pbkdf2.sha512.10000.674DFFDEF76E13EA...2CC972B102CF4355";
+              default = null;
+              type = with types; uniq (nullOr str);
+              description = ''
+                Specifies the password hash for the account,
+                generated with grub-mkpasswd-pbkdf2.
+                This hash will be copied to the Nix store, and will be visible to all local users.
+              '';
+            };
+            passwordFile = mkOption {
+              example = "/path/to/file";
+              default = null;
+              type = with types; uniq (nullOr str);
+              description = ''
+                Specifies the path to a file containing the
+                clear text password for the account.
+                This password will be stored in /boot/grub/grub.cfg, and will
+                be visible to any local user who can read this file.
+              '';
+            };
+            password = mkOption {
+              example = "Pa$$w0rd!";
+              default = null;
+              type = with types; uniq (nullOr str);
+              description = ''
+                Specifies the clear text password for the account.
+                This password will be copied to the Nix store, and will be visible to all local users.
+              '';
+            };
+          };
+        });
+      };
+
       mirroredBoots = mkOption {
         default = [ ];
         example = [
@@ -236,6 +300,33 @@ in
         '';
       };
 
+      extraGrubInstallArgs = mkOption {
+        default = [ ];
+        example = [ "--modules=nativedisk ahci pata part_gpt part_msdos diskfilter mdraid1x lvm ext2" ];
+        type = types.listOf types.str;
+        description = ''
+          Additional arguments passed to <literal>grub-install</literal>.
+
+          A use case for this is to build specific GRUB2 modules
+          directly into the GRUB2 kernel image, so that they are available
+          and activated even in the <literal>grub rescue</literal> shell.
+
+          They are also necessary when the BIOS/UEFI is bugged and cannot
+          correctly read large disks (e.g. above 2 TB), so GRUB2's own
+          <literal>nativedisk</literal> and related modules can be used
+          to use its own disk drivers. The example shows one such case.
+          This is also useful for booting from USB.
+          See the
+          <link xlink:href="http://git.savannah.gnu.org/cgit/grub.git/tree/grub-core/commands/nativedisk.c?h=grub-2.04#n326">
+          GRUB source code
+          </link>
+          for which disk modules are available.
+
+          The list elements are passed directly as <literal>argv</literal>
+          arguments to the <literal>grub-install</literal> program, in order.
+        '';
+      };
+
       extraPerEntryConfig = mkOption {
         default = "";
         example = "root (hd0)";
@@ -336,6 +427,19 @@ in
         '';
       };
 
+      theme = mkOption {
+        type = types.nullOr types.path;
+        example = literalExample "pkgs.nixos-grub2-theme";
+        default = null;
+        description = ''
+          Grub theme to be used.
+
+          <note><para>
+          This options has no effect for GRUB 1.
+          </para></note>
+        '';
+      };
+
       splashMode = mkOption {
         type = types.enum [ "normal" "stretch" ];
         default = "stretch";
@@ -607,7 +711,7 @@ in
         in pkgs.writeScript "install-grub.sh" (''
         #!${pkgs.runtimeShell}
         set -e
-        export PERL5LIB=${with pkgs.perlPackages; makePerlPath [ FileSlurp XMLLibXML XMLSAX XMLSAXBase ListCompare ]}
+        export PERL5LIB=${with pkgs.perlPackages; makePerlPath [ FileSlurp FileCopyRecursive XMLLibXML XMLSAX XMLSAXBase ListCompare JSON ]}
         ${optionalString cfg.enableCryptodisk "export GRUB_ENABLE_CRYPTODISK=y"}
       '' + flip concatMapStrings cfg.mirroredBoots (args: ''
         ${pkgs.perl}/bin/perl ${install-grub-pl} ${grubConfig args} $@
diff --git a/nixos/modules/system/boot/loader/grub/install-grub.pl b/nixos/modules/system/boot/loader/grub/install-grub.pl
index e469b18abd098..59f5638044fe9 100644
--- a/nixos/modules/system/boot/loader/grub/install-grub.pl
+++ b/nixos/modules/system/boot/loader/grub/install-grub.pl
@@ -6,8 +6,11 @@ use File::Basename;
 use File::Path;
 use File::stat;
 use File::Copy;
+use File::Copy::Recursive qw(rcopy pathrm);
 use File::Slurp;
 use File::Temp;
+use JSON;
+use File::Find;
 require List::Compare;
 use POSIX;
 use Cwd;
@@ -20,6 +23,16 @@ my $dom = XML::LibXML->load_xml(location => $ARGV[0]);
 
 sub get { my ($name) = @_; return $dom->findvalue("/expr/attrs/attr[\@name = '$name']/*/\@value"); }
 
+sub getList {
+    my ($name) = @_;
+    my @list = ();
+    foreach my $entry ($dom->findnodes("/expr/attrs/attr[\@name = '$name']/list/string/\@value")) {
+        $entry = $entry->findvalue(".") or die;
+        push(@list, $entry);
+    }
+    return @list;
+}
+
 sub readFile {
     my ($fn) = @_; local $/ = undef;
     open FILE, "<$fn" or return undef; my $s = <FILE>; close FILE;
@@ -71,6 +84,7 @@ my $gfxpayloadBios = get("gfxpayloadBios");
 my $bootloaderId = get("bootloaderId");
 my $forceInstall = get("forceInstall");
 my $font = get("font");
+my $theme = get("theme");
 $ENV{'PATH'} = get("path");
 
 die "unsupported GRUB version\n" if $grubVersion != 1 && $grubVersion != 2;
@@ -241,12 +255,51 @@ if ($grubVersion == 1) {
         timeout $timeout
     ";
     if ($splashImage) {
-        copy $splashImage, "$bootPath/background.xpm.gz" or die "cannot copy $splashImage to $bootPath\n";
+        copy $splashImage, "$bootPath/background.xpm.gz" or die "cannot copy $splashImage to $bootPath: $!\n";
         $conf .= "splashimage " . ($grubBoot->path eq "/" ? "" : $grubBoot->path) . "/background.xpm.gz\n";
     }
 }
 
 else {
+    my @users = ();
+    foreach my $user ($dom->findnodes('/expr/attrs/attr[@name = "users"]/attrs/attr')) {
+        my $name = $user->findvalue('@name') or die;
+        my $hashedPassword = $user->findvalue('./attrs/attr[@name = "hashedPassword"]/string/@value');
+        my $hashedPasswordFile = $user->findvalue('./attrs/attr[@name = "hashedPasswordFile"]/string/@value');
+        my $password = $user->findvalue('./attrs/attr[@name = "password"]/string/@value');
+        my $passwordFile = $user->findvalue('./attrs/attr[@name = "passwordFile"]/string/@value');
+
+        if ($hashedPasswordFile) {
+            open(my $f, '<', $hashedPasswordFile) or die "Can't read file '$hashedPasswordFile'!";
+            $hashedPassword = <$f>;
+            chomp $hashedPassword;
+        }
+        if ($passwordFile) {
+            open(my $f, '<', $passwordFile) or die "Can't read file '$passwordFile'!";
+            $password = <$f>;
+            chomp $password;
+        }
+
+        if ($hashedPassword) {
+            if (index($hashedPassword, "grub.pbkdf2.") == 0) {
+                $conf .= "\npassword_pbkdf2 $name $hashedPassword";
+            }
+            else {
+                die "Password hash for GRUB user '$name' is not valid!";
+            }
+        }
+        elsif ($password) {
+            $conf .= "\npassword $name $password";
+        }
+        else {
+            die "GRUB user '$name' has no password!";
+        }
+        push(@users, $name);
+    }
+    if (@users) {
+        $conf .= "\nset superusers=\"" . join(' ',@users) . "\"\n";
+    }
+
     if ($copyKernels == 0) {
         $conf .= "
             " . $grubStore->search;
@@ -280,7 +333,7 @@ else {
     ";
 
     if ($font) {
-        copy $font, "$bootPath/converted-font.pf2" or die "cannot copy $font to $bootPath\n";
+        copy $font, "$bootPath/converted-font.pf2" or die "cannot copy $font to $bootPath: $!\n";
         $conf .= "
             insmod font
             if loadfont " . ($grubBoot->path eq "/" ? "" : $grubBoot->path) . "/converted-font.pf2; then
@@ -308,7 +361,7 @@ else {
 		    background_color '$backgroundColor'
 		    ";
 		}
-        copy $splashImage, "$bootPath/background$suffix" or die "cannot copy $splashImage to $bootPath\n";
+        copy $splashImage, "$bootPath/background$suffix" or die "cannot copy $splashImage to $bootPath: $!\n";
         $conf .= "
             insmod " . substr($suffix, 1) . "
             if background_image --mode '$splashMode' " . ($grubBoot->path eq "/" ? "" : $grubBoot->path) . "/background$suffix; then
@@ -320,6 +373,28 @@ else {
             fi
         ";
     }
+
+    rmtree("$bootPath/theme") or die "cannot clean up theme folder in $bootPath\n" if -e "$bootPath/theme";
+
+    if ($theme) {
+        # Copy theme
+        rcopy($theme, "$bootPath/theme") or die "cannot copy $theme to $bootPath\n";
+        $conf .= "
+            # Sets theme.
+            set theme=" . ($grubBoot->path eq "/" ? "" : $grubBoot->path) . "/theme/theme.txt
+            export theme
+            # Load theme fonts, if any
+         ";
+
+         find( { wanted => sub {
+             if ($_ =~ /\.pf2$/i) {
+                 $font = File::Spec->abs2rel($File::Find::name, $theme);
+                 $conf .= "
+                     loadfont " . ($grubBoot->path eq "/" ? "" : $grubBoot->path) . "/theme/$font
+                 ";
+             }
+         }, no_chdir => 1 }, $theme );
+     }
 }
 
 $conf .= "$extraConfig\n";
@@ -342,15 +417,15 @@ sub copyToKernelsDir {
     # kernels or initrd if this script is ever interrupted.
     if (! -e $dst) {
         my $tmp = "$dst.tmp";
-        copy $path, $tmp or die "cannot copy $path to $tmp\n";
-        rename $tmp, $dst or die "cannot rename $tmp to $dst\n";
+        copy $path, $tmp or die "cannot copy $path to $tmp: $!\n";
+        rename $tmp, $dst or die "cannot rename $tmp to $dst: $!\n";
     }
     $copied{$dst} = 1;
     return ($grubBoot->path eq "/" ? "" : $grubBoot->path) . "/kernels/$name";
 }
 
 sub addEntry {
-    my ($name, $path) = @_;
+    my ($name, $path, $options) = @_;
     return unless -e "$path/kernel" && -e "$path/initrd";
 
     my $kernel = copyToKernelsDir(Cwd::abs_path("$path/kernel"));
@@ -366,10 +441,10 @@ sub addEntry {
       # Make sure initrd is not world readable (won't work if /boot is FAT)
       umask 0137;
       my $initrdSecretsPathTemp = File::Temp::mktemp("$initrdSecretsPath.XXXXXXXX");
-      system("$path/append-initrd-secrets", $initrdSecretsPathTemp) == 0 or die "failed to create initrd secrets\n";
+      system("$path/append-initrd-secrets", $initrdSecretsPathTemp) == 0 or die "failed to create initrd secrets: $!\n";
       # Check whether any secrets were actually added
       if (-e $initrdSecretsPathTemp && ! -z _) {
-        rename $initrdSecretsPathTemp, $initrdSecretsPath or die "failed to move initrd secrets into place\n";
+        rename $initrdSecretsPathTemp, $initrdSecretsPath or die "failed to move initrd secrets into place: $!\n";
         $copied{$initrdSecretsPath} = 1;
         $initrd .= " " . ($grubBoot->path eq "/" ? "" : $grubBoot->path) . "/kernels/$initrdName-secrets";
       } else {
@@ -396,7 +471,7 @@ sub addEntry {
         $conf .= "  " . ($xen ? "module" : "kernel") . " $kernel $kernelParams\n";
         $conf .= "  " . ($xen ? "module" : "initrd") . " $initrd\n\n";
     } else {
-        $conf .= "menuentry \"$name\" {\n";
+        $conf .= "menuentry \"$name\" " . ($options||"") . " {\n";
         $conf .= $grubBoot->search . "\n";
         if ($copyKernels == 0) {
             $conf .= $grubStore->search . "\n";
@@ -413,7 +488,7 @@ sub addEntry {
 # Add default entries.
 $conf .= "$extraEntries\n" if $extraEntriesBeforeNixOS;
 
-addEntry("NixOS - Default", $defaultConfig);
+addEntry("NixOS - Default", $defaultConfig, "--unrestricted");
 
 $conf .= "$extraEntries\n" unless $extraEntriesBeforeNixOS;
 
@@ -536,7 +611,7 @@ if (get("useOSProber") eq "true") {
 }
 
 # Atomically switch to the new config
-rename $tmpFile, $confFile or die "cannot rename $tmpFile to $confFile\n";
+rename $tmpFile, $confFile or die "cannot rename $tmpFile to $confFile: $!\n";
 
 
 # Remove obsolete files from $bootPath/kernels.
@@ -557,9 +632,12 @@ struct(GrubState => {
     efi => '$',
     devices => '$',
     efiMountPoint => '$',
+    extraGrubInstallArgs => '@',
 });
+# If you add something to the state file, only add it to the end
+# because it is read line-by-line.
 sub readGrubState {
-    my $defaultGrubState = GrubState->new(name => "", version => "", efi => "", devices => "", efiMountPoint => "" );
+    my $defaultGrubState = GrubState->new(name => "", version => "", efi => "", devices => "", efiMountPoint => "", extraGrubInstallArgs => () );
     open FILE, "<$bootPath/grub/state" or return $defaultGrubState;
     local $/ = "\n";
     my $name = <FILE>;
@@ -572,24 +650,37 @@ sub readGrubState {
     chomp($devices);
     my $efiMountPoint = <FILE>;
     chomp($efiMountPoint);
+    # Historically, arguments in the state file were one per each line, but that
+    # gets really messy when newlines are involved, structured arguments
+    # like lists are needed (they have to have a separator encoding), or even worse,
+    # when we need to remove a setting in the future. Thus, the 6th line is a JSON
+    # object that can store structured data, with named keys, and all new state
+    # should go in there.
+    my $jsonStateLine = <FILE>;
+    # For historical reasons we do not check the values above for un-definedness
+    # (that is, when the state file has too few lines and EOF is reached),
+    # because the above come from the first version of this logic and are thus
+    # guaranteed to be present.
+    $jsonStateLine = defined $jsonStateLine ? $jsonStateLine : '{}'; # empty JSON object
+    chomp($jsonStateLine);
+    if ($jsonStateLine eq "") {
+        $jsonStateLine = '{}'; # empty JSON object
+    }
+    my %jsonState = %{decode_json($jsonStateLine)};
+    my @extraGrubInstallArgs = exists($jsonState{'extraGrubInstallArgs'}) ? @{$jsonState{'extraGrubInstallArgs'}} : ();
     close FILE;
-    my $grubState = GrubState->new(name => $name, version => $version, efi => $efi, devices => $devices, efiMountPoint => $efiMountPoint );
+    my $grubState = GrubState->new(name => $name, version => $version, efi => $efi, devices => $devices, efiMountPoint => $efiMountPoint, extraGrubInstallArgs => \@extraGrubInstallArgs );
     return $grubState
 }
 
-sub getDeviceTargets {
-    my @devices = ();
-    foreach my $dev ($dom->findnodes('/expr/attrs/attr[@name = "devices"]/list/string/@value')) {
-        $dev = $dev->findvalue(".") or die;
-        push(@devices, $dev);
-    }
-    return @devices;
-}
-my @deviceTargets = getDeviceTargets();
+my @deviceTargets = getList('devices');
 my $prevGrubState = readGrubState();
 my @prevDeviceTargets = split/,/, $prevGrubState->devices;
+my @extraGrubInstallArgs = getList('extraGrubInstallArgs');
+my @prevExtraGrubInstallArgs = @{$prevGrubState->extraGrubInstallArgs};
 
 my $devicesDiffer = scalar (List::Compare->new( '-u', '-a', \@deviceTargets, \@prevDeviceTargets)->get_symmetric_difference());
+my $extraGrubInstallArgsDiffer = scalar (List::Compare->new( '-u', '-a', \@extraGrubInstallArgs, \@prevExtraGrubInstallArgs)->get_symmetric_difference());
 my $nameDiffer = get("fullName") ne $prevGrubState->name;
 my $versionDiffer = get("fullVersion") ne $prevGrubState->version;
 my $efiDiffer = $efiTarget ne $prevGrubState->efi;
@@ -598,25 +689,25 @@ if (($ENV{'NIXOS_INSTALL_GRUB'} // "") eq "1") {
     warn "NIXOS_INSTALL_GRUB env var deprecated, use NIXOS_INSTALL_BOOTLOADER";
     $ENV{'NIXOS_INSTALL_BOOTLOADER'} = "1";
 }
-my $requireNewInstall = $devicesDiffer || $nameDiffer || $versionDiffer || $efiDiffer || $efiMountPointDiffer || (($ENV{'NIXOS_INSTALL_BOOTLOADER'} // "") eq "1");
+my $requireNewInstall = $devicesDiffer || $extraGrubInstallArgsDiffer || $nameDiffer || $versionDiffer || $efiDiffer || $efiMountPointDiffer || (($ENV{'NIXOS_INSTALL_BOOTLOADER'} // "") eq "1");
 
 # install a symlink so that grub can detect the boot drive
-my $tmpDir = File::Temp::tempdir(CLEANUP => 1) or die "Failed to create temporary space";
-symlink "$bootPath", "$tmpDir/boot" or die "Failed to symlink $tmpDir/boot";
+my $tmpDir = File::Temp::tempdir(CLEANUP => 1) or die "Failed to create temporary space: $!";
+symlink "$bootPath", "$tmpDir/boot" or die "Failed to symlink $tmpDir/boot: $!";
 
 # install non-EFI GRUB
 if (($requireNewInstall != 0) && ($efiTarget eq "no" || $efiTarget eq "both")) {
     foreach my $dev (@deviceTargets) {
         next if $dev eq "nodev";
         print STDERR "installing the GRUB $grubVersion boot loader on $dev...\n";
-        my @command = ("$grub/sbin/grub-install", "--recheck", "--root-directory=$tmpDir", Cwd::abs_path($dev));
+        my @command = ("$grub/sbin/grub-install", "--recheck", "--root-directory=$tmpDir", Cwd::abs_path($dev), @extraGrubInstallArgs);
         if ($forceInstall eq "true") {
             push @command, "--force";
         }
         if ($grubTarget ne "") {
             push @command, "--target=$grubTarget";
         }
-        (system @command) == 0 or die "$0: installation of GRUB on $dev failed\n";
+        (system @command) == 0 or die "$0: installation of GRUB on $dev failed: $!\n";
     }
 }
 
@@ -624,7 +715,7 @@ if (($requireNewInstall != 0) && ($efiTarget eq "no" || $efiTarget eq "both")) {
 # install EFI GRUB
 if (($requireNewInstall != 0) && ($efiTarget eq "only" || $efiTarget eq "both")) {
     print STDERR "installing the GRUB $grubVersion EFI boot loader into $efiSysMountPoint...\n";
-    my @command = ("$grubEfi/sbin/grub-install", "--recheck", "--target=$grubTargetEfi", "--boot-directory=$bootPath", "--efi-directory=$efiSysMountPoint");
+    my @command = ("$grubEfi/sbin/grub-install", "--recheck", "--target=$grubTargetEfi", "--boot-directory=$bootPath", "--efi-directory=$efiSysMountPoint", @extraGrubInstallArgs);
     if ($forceInstall eq "true") {
         push @command, "--force";
     }
@@ -635,17 +726,29 @@ if (($requireNewInstall != 0) && ($efiTarget eq "only" || $efiTarget eq "both"))
         push @command, "--removable" if $efiInstallAsRemovable eq "true";
     }
 
-    (system @command) == 0 or die "$0: installation of GRUB EFI into $efiSysMountPoint failed\n";
+    (system @command) == 0 or die "$0: installation of GRUB EFI into $efiSysMountPoint failed: $!\n";
 }
 
 
 # update GRUB state file
 if ($requireNewInstall != 0) {
-    open FILE, ">$bootPath/grub/state" or die "cannot create $bootPath/grub/state: $!\n";
+    # Temp file for atomic rename.
+    my $stateFile = "$bootPath/grub/state";
+    my $stateFileTmp = $stateFile . ".tmp";
+
+    open FILE, ">$stateFileTmp" or die "cannot create $stateFileTmp: $!\n";
     print FILE get("fullName"), "\n" or die;
     print FILE get("fullVersion"), "\n" or die;
     print FILE $efiTarget, "\n" or die;
     print FILE join( ",", @deviceTargets ), "\n" or die;
     print FILE $efiSysMountPoint, "\n" or die;
+    my %jsonState = (
+        extraGrubInstallArgs => \@extraGrubInstallArgs
+    );
+    my $jsonStateLine = encode_json(\%jsonState);
+    print FILE $jsonStateLine, "\n" or die;
     close FILE or die;
+
+    # Atomically switch to the new state file
+    rename $stateFileTmp, $stateFile or die "cannot rename $stateFileTmp to $stateFile: $!\n";
 }
diff --git a/nixos/modules/system/boot/loader/init-script/init-script-builder.sh b/nixos/modules/system/boot/loader/init-script/init-script-builder.sh
index 6f48d2539acee..2a1ec479fea04 100644
--- a/nixos/modules/system/boot/loader/init-script/init-script-builder.sh
+++ b/nixos/modules/system/boot/loader/init-script/init-script-builder.sh
@@ -53,7 +53,7 @@ addEntry() {
       echo "exec $stage2"
     )"
 
-    [ "$path" != "$defaultConfig" ] || { 
+    [ "$path" != "$defaultConfig" ] || {
       echo "$content" > $tmp
       echo "# older configurations: $targetOther" >> $tmp
       chmod +x $tmp
diff --git a/nixos/modules/system/boot/loader/systemd-boot/systemd-boot.nix b/nixos/modules/system/boot/loader/systemd-boot/systemd-boot.nix
index 22d459ceb04f1..f0bd76a3c1d23 100644
--- a/nixos/modules/system/boot/loader/systemd-boot/systemd-boot.nix
+++ b/nixos/modules/system/boot/loader/systemd-boot/systemd-boot.nix
@@ -64,10 +64,10 @@ in {
       example = 120;
       type = types.nullOr types.int;
       description = ''
-        Maximum number of latest generations in the boot menu. 
+        Maximum number of latest generations in the boot menu.
         Useful to prevent boot partition running out of disk space.
 
-        <literal>null</literal> means no limit i.e. all generations 
+        <literal>null</literal> means no limit i.e. all generations
         that were not garbage collected yet.
       '';
     };
diff --git a/nixos/modules/system/boot/luksroot.nix b/nixos/modules/system/boot/luksroot.nix
index 31f1e22cda32c..166f89c706611 100644
--- a/nixos/modules/system/boot/luksroot.nix
+++ b/nixos/modules/system/boot/luksroot.nix
@@ -140,7 +140,7 @@ let
     umount /crypt-ramfs 2>/dev/null
   '';
 
-  openCommand = name': { name, device, header, keyFile, keyFileSize, keyFileOffset, allowDiscards, yubikey, gpgCard, fido2, fallbackToPassword, ... }: assert name' == name;
+  openCommand = name': { name, device, header, keyFile, keyFileSize, keyFileOffset, allowDiscards, yubikey, gpgCard, fido2, fallbackToPassword, preOpenCommands, postOpenCommands,... }: assert name' == name;
   let
     csopen   = "cryptsetup luksOpen ${device} ${name} ${optionalString allowDiscards "--allow-discards"} ${optionalString (header != null) "--header=${header}"}";
     cschange = "cryptsetup luksChangeKey ${device} ${optionalString (header != null) "--header=${header}"}";
@@ -412,11 +412,17 @@ let
     }
     ''}
 
+    # commands to run right before we mount our device
+    ${preOpenCommands}
+
     ${if (luks.yubikeySupport && (yubikey != null)) || (luks.gpgSupport && (gpgCard != null)) || (luks.fido2Support && (fido2.credential != null)) then ''
     open_with_hardware
     '' else ''
     open_normally
     ''}
+
+    # commands to run right after we mounted our device
+    ${postOpenCommands}
   '';
 
   askPass = pkgs.writeScriptBin "cryptsetup-askpass" ''
@@ -467,8 +473,6 @@ in
         [ "aes" "aes_generic" "blowfish" "twofish"
           "serpent" "cbc" "xts" "lrw" "sha1" "sha256" "sha512"
           "af_alg" "algif_skcipher"
-
-          (if pkgs.stdenv.hostPlatform.system == "x86_64-linux" then "aes_x86_64" else "aes_i586")
         ];
       description = ''
         A list of cryptographic kernel modules needed to decrypt the root device(s).
@@ -735,6 +739,30 @@ in
               };
             });
           };
+
+          preOpenCommands = mkOption {
+            type = types.lines;
+            default = "";
+            example = ''
+              mkdir -p /tmp/persistent
+              mount -t zfs rpool/safe/persistent /tmp/persistent
+            '';
+            description = ''
+              Commands that should be run right before we try to mount our LUKS device.
+              This can be useful, if the keys needed to open the drive is on another partion.
+            '';
+          };
+
+          postOpenCommands = mkOption {
+            type = types.lines;
+            default = "";
+            example = ''
+              umount /tmp/persistent
+            '';
+            description = ''
+              Commands that should be run right after we have mounted our LUKS device.
+            '';
+          };
         };
       }));
     };
diff --git a/nixos/modules/system/boot/modprobe.nix b/nixos/modules/system/boot/modprobe.nix
index dee0ab470c992..c75f32c4d99ab 100644
--- a/nixos/modules/system/boot/modprobe.nix
+++ b/nixos/modules/system/boot/modprobe.nix
@@ -28,7 +28,7 @@ with lib;
         Any additional configuration to be appended to the generated
         <filename>modprobe.conf</filename>.  This is typically used to
         specify module options.  See
-        <citerefentry><refentrytitle>modprobe.conf</refentrytitle>
+        <citerefentry><refentrytitle>modprobe.d</refentrytitle>
         <manvolnum>5</manvolnum></citerefentry> for details.
       '';
       type = types.lines;
diff --git a/nixos/modules/system/boot/networkd.nix b/nixos/modules/system/boot/networkd.nix
index b0545363b3300..47689b2a4700a 100644
--- a/nixos/modules/system/boot/networkd.nix
+++ b/nixos/modules/system/boot/networkd.nix
@@ -8,359 +8,714 @@ let
 
   cfg = config.systemd.network;
 
-  checkLink = checkUnitConfig "Link" [
-    (assertOnlyFields [
-      "Description" "Alias" "MACAddressPolicy" "MACAddress" "NamePolicy" "Name" "OriginalName"
-      "MTUBytes" "BitsPerSecond" "Duplex" "AutoNegotiation" "WakeOnLan" "Port" "Advertise"
-      "TCPSegmentationOffload" "TCP6SegmentationOffload" "GenericSegmentationOffload"
-      "GenericReceiveOffload" "LargeReceiveOffload" "RxChannels" "TxChannels"
-      "OtherChannels" "CombinedChannels"
-    ])
-    (assertValueOneOf "MACAddressPolicy" ["persistent" "random" "none"])
-    (assertMacAddress "MACAddress")
-    (assertByteFormat "MTUBytes")
-    (assertByteFormat "BitsPerSecond")
-    (assertValueOneOf "Duplex" ["half" "full"])
-    (assertValueOneOf "AutoNegotiation" boolValues)
-    (assertValueOneOf "WakeOnLan" ["phy" "unicast" "multicast" "broadcast" "arp" "magic" "secureon" "off"])
-    (assertValueOneOf "Port" ["tp" "aui" "bnc" "mii" "fibre"])
-    (assertValueOneOf "TCPSegmentationOffload" boolValues)
-    (assertValueOneOf "TCP6SegmentationOffload" boolValues)
-    (assertValueOneOf "GenericSegmentationOffload" boolValues)
-    (assertValueOneOf "UDPSegmentationOffload" boolValues)
-    (assertValueOneOf "GenericReceiveOffload" boolValues)
-    (assertValueOneOf "LargeReceiveOffload" boolValues)
-    (assertInt "RxChannels")
-    (assertMinimum "RxChannels" 1)
-    (assertInt "TxChannels")
-    (assertMinimum "TxChannels" 1)
-    (assertInt "OtherChannels")
-    (assertMinimum "OtherChannels" 1)
-    (assertInt "CombinedChannels")
-    (assertMinimum "CombinedChannels" 1)
-  ];
-
-  checkNetdev = checkUnitConfig "Netdev" [
-    (assertOnlyFields [
-      "Description" "Name" "Kind" "MTUBytes" "MACAddress"
-    ])
-    (assertHasField "Name")
-    (assertHasField "Kind")
-    (assertValueOneOf "Kind" [
-      "bond" "bridge" "dummy" "gre" "gretap" "ip6gre" "ip6tnl" "ip6gretap" "ipip"
-      "ipvlan" "macvlan" "macvtap" "sit" "tap" "tun" "veth" "vlan" "vti" "vti6"
-      "vxlan" "geneve" "vrf" "vcan" "vxcan" "wireguard" "netdevsim" "xfrm"
-    ])
-    (assertByteFormat "MTUBytes")
-    (assertMacAddress "MACAddress")
-  ];
-
-  checkVRF = checkUnitConfig "VRF" [
-    (assertOnlyFields [ "Table" ])
-    (assertMinimum "Table" 0)
-  ];
+  check = {
+
+    link = {
+
+      sectionLink = checkUnitConfig "Link" [
+        (assertOnlyFields [
+          "Description"
+          "Alias"
+          "MACAddressPolicy"
+          "MACAddress"
+          "NamePolicy"
+          "Name"
+          "AlternativeNamesPolicy"
+          "AlternativeName"
+          "MTUBytes"
+          "BitsPerSecond"
+          "Duplex"
+          "AutoNegotiation"
+          "WakeOnLan"
+          "Port"
+          "Advertise"
+          "ReceiveChecksumOffload"
+          "TransmitChecksumOffload"
+          "TCPSegmentationOffload"
+          "TCP6SegmentationOffload"
+          "GenericSegmentationOffload"
+          "GenericReceiveOffload"
+          "LargeReceiveOffload"
+          "RxChannels"
+          "TxChannels"
+          "OtherChannels"
+          "CombinedChannels"
+          "RxBufferSize"
+          "TxBufferSize"
+        ])
+        (assertValueOneOf "MACAddressPolicy" ["persistent" "random" "none"])
+        (assertMacAddress "MACAddress")
+        (assertByteFormat "MTUBytes")
+        (assertByteFormat "BitsPerSecond")
+        (assertValueOneOf "Duplex" ["half" "full"])
+        (assertValueOneOf "AutoNegotiation" boolValues)
+        (assertValueOneOf "WakeOnLan" ["phy" "unicast" "multicast" "broadcast" "arp" "magic" "secureon" "off"])
+        (assertValueOneOf "Port" ["tp" "aui" "bnc" "mii" "fibre"])
+        (assertValueOneOf "ReceiveChecksumOffload" boolValues)
+        (assertValueOneOf "TransmitChecksumOffload" boolValues)
+        (assertValueOneOf "TCPSegmentationOffload" boolValues)
+        (assertValueOneOf "TCP6SegmentationOffload" boolValues)
+        (assertValueOneOf "GenericSegmentationOffload" boolValues)
+        (assertValueOneOf "GenericReceiveOffload" boolValues)
+        (assertValueOneOf "LargeReceiveOffload" boolValues)
+        (assertInt "RxChannels")
+        (assertRange "RxChannels" 1 4294967295)
+        (assertInt "TxChannels")
+        (assertRange "TxChannels" 1 4294967295)
+        (assertInt "OtherChannels")
+        (assertRange "OtherChannels" 1 4294967295)
+        (assertInt "CombinedChannels")
+        (assertRange "CombinedChannels" 1 4294967295)
+        (assertInt "RxBufferSize")
+        (assertInt "TxBufferSize")
+      ];
+    };
 
-  # NOTE The PrivateKey directive is missing on purpose here, please
-  # do not add it to this list. The nix store is world-readable let's
-  # refrain ourselves from providing a footgun.
-  checkWireGuard = checkUnitConfig "WireGuard" [
-    (assertOnlyFields [
-      "PrivateKeyFile" "ListenPort" "FwMark"
-    ])
-    # The following check won't work on nix <= 2.2
-    # see https://github.com/NixOS/nix/pull/2378
-    #
-    # Add this again when we'll have drop the
-    # nix < 2.2 support.
-    # (assertRange "FwMark" 1 4294967295)
-  ];
+    netdev = let
+
+      tunChecks = [
+        (assertOnlyFields [
+          "MultiQueue"
+          "PacketInfo"
+          "VNetHeader"
+          "User"
+          "Group"
+        ])
+        (assertValueOneOf "MultiQueue" boolValues)
+        (assertValueOneOf "PacketInfo" boolValues)
+        (assertValueOneOf "VNetHeader" boolValues)
+      ];
+    in {
+
+      sectionNetdev = checkUnitConfig "Netdev" [
+        (assertOnlyFields [
+          "Description"
+          "Name"
+          "Kind"
+          "MTUBytes"
+          "MACAddress"
+        ])
+        (assertHasField "Name")
+        (assertHasField "Kind")
+        (assertValueOneOf "Kind" [
+          "bond"
+          "bridge"
+          "dummy"
+          "gre"
+          "gretap"
+          "erspan"
+          "ip6gre"
+          "ip6tnl"
+          "ip6gretap"
+          "ipip"
+          "ipvlan"
+          "macvlan"
+          "macvtap"
+          "sit"
+          "tap"
+          "tun"
+          "veth"
+          "vlan"
+          "vti"
+          "vti6"
+          "vxlan"
+          "geneve"
+          "l2tp"
+          "macsec"
+          "vrf"
+          "vcan"
+          "vxcan"
+          "wireguard"
+          "netdevsim"
+          "nlmon"
+          "fou"
+          "xfrm"
+          "ifb"
+        ])
+        (assertByteFormat "MTUBytes")
+        (assertMacAddress "MACAddress")
+      ];
 
-  # NOTE The PresharedKey directive is missing on purpose here, please
-  # do not add it to this list. The nix store is world-readable,let's
-  # refrain ourselves from providing a footgun.
-  checkWireGuardPeer = checkUnitConfig "WireGuardPeer" [
-    (assertOnlyFields [
-      "PublicKey" "PresharedKeyFile" "AllowedIPs"
-      "Endpoint" "PersistentKeepalive"
-    ])
-    (assertRange "PersistentKeepalive" 1 65535)
-  ];
+      sectionVLAN = checkUnitConfig "VLAN" [
+        (assertOnlyFields [
+          "Id"
+          "GVRP"
+          "MVRP"
+          "LooseBinding"
+          "ReorderHeader"
+        ])
+        (assertInt "Id")
+        (assertRange "Id" 0 4094)
+        (assertValueOneOf "GVRP" boolValues)
+        (assertValueOneOf "MVRP" boolValues)
+        (assertValueOneOf "LooseBinding" boolValues)
+        (assertValueOneOf "ReorderHeader" boolValues)
+      ];
 
-  checkVlan = checkUnitConfig "VLAN" [
-    (assertOnlyFields ["Id" "GVRP" "MVRP" "LooseBinding" "ReorderHeader"])
-    (assertRange "Id" 0 4094)
-    (assertValueOneOf "GVRP" boolValues)
-    (assertValueOneOf "MVRP" boolValues)
-    (assertValueOneOf "LooseBinding" boolValues)
-    (assertValueOneOf "ReorderHeader" boolValues)
-  ];
+      sectionMACVLAN = checkUnitConfig "MACVLAN" [
+        (assertOnlyFields [
+          "Mode"
+        ])
+        (assertValueOneOf "Mode" ["private" "vepa" "bridge" "passthru"])
+      ];
 
-  checkMacvlan = checkUnitConfig "MACVLAN" [
-    (assertOnlyFields ["Mode"])
-    (assertValueOneOf "Mode" ["private" "vepa" "bridge" "passthru"])
-  ];
+      sectionVXLAN = checkUnitConfig "VXLAN" [
+        (assertOnlyFields [
+          "VNI"
+          "Remote"
+          "Local"
+          "Group"
+          "TOS"
+          "TTL"
+          "MacLearning"
+          "FDBAgeingSec"
+          "MaximumFDBEntries"
+          "ReduceARPProxy"
+          "L2MissNotification"
+          "L3MissNotification"
+          "RouteShortCircuit"
+          "UDPChecksum"
+          "UDP6ZeroChecksumTx"
+          "UDP6ZeroChecksumRx"
+          "RemoteChecksumTx"
+          "RemoteChecksumRx"
+          "GroupPolicyExtension"
+          "GenericProtocolExtension"
+          "DestinationPort"
+          "PortRange"
+          "FlowLabel"
+          "IPDoNotFragment"
+        ])
+        (assertInt "VNI")
+        (assertRange "VNI" 1 16777215)
+        (assertValueOneOf "MacLearning" boolValues)
+        (assertInt "MaximumFDBEntries")
+        (assertValueOneOf "ReduceARPProxy" boolValues)
+        (assertValueOneOf "L2MissNotification" boolValues)
+        (assertValueOneOf "L3MissNotification" boolValues)
+        (assertValueOneOf "RouteShortCircuit" boolValues)
+        (assertValueOneOf "UDPChecksum" boolValues)
+        (assertValueOneOf "UDP6ZeroChecksumTx" boolValues)
+        (assertValueOneOf "UDP6ZeroChecksumRx" boolValues)
+        (assertValueOneOf "RemoteChecksumTx" boolValues)
+        (assertValueOneOf "RemoteChecksumRx" boolValues)
+        (assertValueOneOf "GroupPolicyExtension" boolValues)
+        (assertValueOneOf "GenericProtocolExtension" boolValues)
+        (assertInt "FlowLabel")
+        (assertRange "FlowLabel" 0 1048575)
+        (assertValueOneOf "IPDoNotFragment" (boolValues + ["inherit"]))
+      ];
 
-  checkVxlan = checkUnitConfig "VXLAN" [
-    (assertOnlyFields [
-      "Id" "Remote" "Local" "TOS" "TTL" "MacLearning" "FDBAgeingSec"
-      "MaximumFDBEntries" "ReduceARPProxy" "L2MissNotification"
-      "L3MissNotification" "RouteShortCircuit" "UDPChecksum"
-      "UDP6ZeroChecksumTx" "UDP6ZeroChecksumRx" "RemoteChecksumTx"
-      "RemoteChecksumRx" "GroupPolicyExtension" "DestinationPort" "PortRange"
-      "FlowLabel"
-    ])
-    (assertRange "TTL" 0 255)
-    (assertValueOneOf "MacLearning" boolValues)
-    (assertValueOneOf "ReduceARPProxy" boolValues)
-    (assertValueOneOf "L2MissNotification" boolValues)
-    (assertValueOneOf "L3MissNotification" boolValues)
-    (assertValueOneOf "RouteShortCircuit" boolValues)
-    (assertValueOneOf "UDPChecksum" boolValues)
-    (assertValueOneOf "UDP6ZeroChecksumTx" boolValues)
-    (assertValueOneOf "UDP6ZeroChecksumRx" boolValues)
-    (assertValueOneOf "RemoteChecksumTx" boolValues)
-    (assertValueOneOf "RemoteChecksumRx" boolValues)
-    (assertValueOneOf "GroupPolicyExtension" boolValues)
-    (assertRange "FlowLabel" 0 1048575)
-  ];
+      sectionTunnel = checkUnitConfig "Tunnel" [
+        (assertOnlyFields [
+          "Local"
+          "Remote"
+          "TOS"
+          "TTL"
+          "DiscoverPathMTU"
+          "IPv6FlowLabel"
+          "CopyDSCP"
+          "EncapsulationLimit"
+          "Key"
+          "InputKey"
+          "OutputKey"
+          "Mode"
+          "Independent"
+          "AssignToLoopback"
+          "AllowLocalRemote"
+          "FooOverUDP"
+          "FOUDestinationPort"
+          "FOUSourcePort"
+          "Encapsulation"
+          "IPv6RapidDeploymentPrefix"
+          "ISATAP"
+          "SerializeTunneledPackets"
+          "ERSPANIndex"
+        ])
+        (assertInt "TTL")
+        (assertRange "TTL" 0 255)
+        (assertValueOneOf "DiscoverPathMTU" boolValues)
+        (assertValueOneOf "CopyDSCP" boolValues)
+        (assertValueOneOf "Mode" ["ip6ip6" "ipip6" "any"])
+        (assertValueOneOf "Independent" boolValues)
+        (assertValueOneOf "AssignToLoopback" boolValues)
+        (assertValueOneOf "AllowLocalRemote" boolValues)
+        (assertValueOneOf "FooOverUDP" boolValues)
+        (assertPort "FOUDestinationPort")
+        (assertPort "FOUSourcePort")
+        (assertValueOneOf "Encapsulation" ["FooOverUDP" "GenericUDPEncapsulation"])
+        (assertValueOneOf "ISATAP" boolValues)
+        (assertValueOneOf "SerializeTunneledPackets" boolValues)
+        (assertInt "ERSPANIndex")
+        (assertRange "ERSPANIndex" 1 1048575)
+      ];
 
-  checkTunnel = checkUnitConfig "Tunnel" [
-    (assertOnlyFields [
-      "Local" "Remote" "TOS" "TTL" "DiscoverPathMTU" "IPv6FlowLabel" "CopyDSCP"
-      "EncapsulationLimit" "Key" "InputKey" "OutputKey" "Mode" "Independent"
-      "AllowLocalRemote"
-    ])
-    (assertRange "TTL" 0 255)
-    (assertValueOneOf "DiscoverPathMTU" boolValues)
-    (assertValueOneOf "CopyDSCP" boolValues)
-    (assertValueOneOf "Mode" ["ip6ip6" "ipip6" "any"])
-    (assertValueOneOf "Independent" boolValues)
-    (assertValueOneOf "AllowLocalRemote" boolValues)
-  ];
+      sectionPeer = checkUnitConfig "Peer" [
+        (assertOnlyFields [
+          "Name"
+          "MACAddress"
+        ])
+        (assertMacAddress "MACAddress")
+      ];
 
-  checkPeer = checkUnitConfig "Peer" [
-    (assertOnlyFields ["Name" "MACAddress"])
-    (assertMacAddress "MACAddress")
-  ];
+      sectionTun = checkUnitConfig "Tun" tunChecks;
+
+      sectionTap = checkUnitConfig "Tap" tunChecks;
+
+      # NOTE The PrivateKey directive is missing on purpose here, please
+      # do not add it to this list. The nix store is world-readable let's
+      # refrain ourselves from providing a footgun.
+      sectionWireGuard = checkUnitConfig "WireGuard" [
+        (assertOnlyFields [
+          "PrivateKeyFile"
+          "ListenPort"
+          "FirewallMark"
+        ])
+        (assertInt "FirewallMark")
+        (assertRange "FirewallMark" 1 4294967295)
+      ];
 
-  tunTapChecks = [
-    (assertOnlyFields ["OneQueue" "MultiQueue" "PacketInfo" "VNetHeader" "User" "Group"])
-    (assertValueOneOf "OneQueue" boolValues)
-    (assertValueOneOf "MultiQueue" boolValues)
-    (assertValueOneOf "PacketInfo" boolValues)
-    (assertValueOneOf "VNetHeader" boolValues)
-  ];
+      # NOTE The PresharedKey directive is missing on purpose here, please
+      # do not add it to this list. The nix store is world-readable,let's
+      # refrain ourselves from providing a footgun.
+      sectionWireGuardPeer = checkUnitConfig "WireGuardPeer" [
+        (assertOnlyFields [
+          "PublicKey"
+          "PresharedKeyFile"
+          "AllowedIPs"
+          "Endpoint"
+          "PersistentKeepalive"
+        ])
+        (assertInt "PersistentKeepalive")
+        (assertRange "PersistentKeepalive" 0 65535)
+      ];
 
-  checkTun = checkUnitConfig "Tun" tunTapChecks;
-
-  checkTap = checkUnitConfig "Tap" tunTapChecks;
-
-  checkBond = checkUnitConfig "Bond" [
-    (assertOnlyFields [
-      "Mode" "TransmitHashPolicy" "LACPTransmitRate" "MIIMonitorSec"
-      "UpDelaySec" "DownDelaySec" "LearnPacketIntervalSec" "AdSelect"
-      "FailOverMACPolicy" "ARPValidate" "ARPIntervalSec" "ARPIPTargets"
-      "ARPAllTargets" "PrimaryReselectPolicy" "ResendIGMP" "PacketsPerSlave"
-      "GratuitousARP" "AllSlavesActive" "MinLinks"
-    ])
-    (assertValueOneOf "Mode" [
-      "balance-rr" "active-backup" "balance-xor"
-      "broadcast" "802.3ad" "balance-tlb" "balance-alb"
-    ])
-    (assertValueOneOf "TransmitHashPolicy" [
-      "layer2" "layer3+4" "layer2+3" "encap2+3" "encap3+4"
-    ])
-    (assertValueOneOf "LACPTransmitRate" ["slow" "fast"])
-    (assertValueOneOf "AdSelect" ["stable" "bandwidth" "count"])
-    (assertValueOneOf "FailOverMACPolicy" ["none" "active" "follow"])
-    (assertValueOneOf "ARPValidate" ["none" "active" "backup" "all"])
-    (assertValueOneOf "ARPAllTargets" ["any" "all"])
-    (assertValueOneOf "PrimaryReselectPolicy" ["always" "better" "failure"])
-    (assertRange "ResendIGMP" 0 255)
-    (assertRange "PacketsPerSlave" 0 65535)
-    (assertRange "GratuitousARP" 0 255)
-    (assertValueOneOf "AllSlavesActive" boolValues)
-  ];
+      sectionBond = checkUnitConfig "Bond" [
+        (assertOnlyFields [
+          "Mode"
+          "TransmitHashPolicy"
+          "LACPTransmitRate"
+          "MIIMonitorSec"
+          "UpDelaySec"
+          "DownDelaySec"
+          "LearnPacketIntervalSec"
+          "AdSelect"
+          "AdActorSystemPriority"
+          "AdUserPortKey"
+          "AdActorSystem"
+          "FailOverMACPolicy"
+          "ARPValidate"
+          "ARPIntervalSec"
+          "ARPIPTargets"
+          "ARPAllTargets"
+          "PrimaryReselectPolicy"
+          "ResendIGMP"
+          "PacketsPerSlave"
+          "GratuitousARP"
+          "AllSlavesActive"
+          "DynamicTransmitLoadBalancing"
+          "MinLinks"
+        ])
+        (assertValueOneOf "Mode" [
+          "balance-rr"
+          "active-backup"
+          "balance-xor"
+          "broadcast"
+          "802.3ad"
+          "balance-tlb"
+          "balance-alb"
+        ])
+        (assertValueOneOf "TransmitHashPolicy" [
+          "layer2"
+          "layer3+4"
+          "layer2+3"
+          "encap2+3"
+          "encap3+4"
+        ])
+        (assertValueOneOf "LACPTransmitRate" ["slow" "fast"])
+        (assertValueOneOf "AdSelect" ["stable" "bandwidth" "count"])
+        (assertInt "AdActorSystemPriority")
+        (assertRange "AdActorSystemPriority" 1 65535)
+        (assertInt "AdUserPortKey")
+        (assertRange "AdUserPortKey" 0 1023)
+        (assertValueOneOf "FailOverMACPolicy" ["none" "active" "follow"])
+        (assertValueOneOf "ARPValidate" ["none" "active" "backup" "all"])
+        (assertValueOneOf "ARPAllTargets" ["any" "all"])
+        (assertValueOneOf "PrimaryReselectPolicy" ["always" "better" "failure"])
+        (assertInt "ResendIGMP")
+        (assertRange "ResendIGMP" 0 255)
+        (assertInt "PacketsPerSlave")
+        (assertRange "PacketsPerSlave" 0 65535)
+        (assertInt "GratuitousARP")
+        (assertRange "GratuitousARP" 0 255)
+        (assertValueOneOf "AllSlavesActive" boolValues)
+        (assertValueOneOf "DynamicTransmitLoadBalancing" boolValues)
+        (assertInt "MinLinks")
+        (assertMinimum "MinLinks" 0)
+      ];
 
-  checkXfrm = checkUnitConfig "Xfrm" [
-    (assertOnlyFields [
-      "InterfaceId" "Independent"
-    ])
-    # The following check won't work on nix <= 2.2
-    # see https://github.com/NixOS/nix/pull/2378
-    #
-    # Add this again when we'll have drop the
-    # nix < 2.2 support.
-    # (assertRange "InterfaceId" 1 4294967295)
-    (assertValueOneOf "Independent" boolValues)
-  ];
+      sectionXfrm = checkUnitConfig "Xfrm" [
+        (assertOnlyFields [
+          "InterfaceId"
+          "Independent"
+        ])
+        (assertInt "InterfaceId")
+        (assertRange "InterfaceId" 1 4294967295)
+        (assertValueOneOf "Independent" boolValues)
+      ];
 
-  checkNetwork = checkUnitConfig "Network" [
-    (assertOnlyFields [
-      "Description" "DHCP" "DHCPServer" "LinkLocalAddressing" "IPv4LLRoute"
-      "IPv6Token" "LLMNR" "MulticastDNS" "DNSOverTLS" "DNSSEC"
-      "DNSSECNegativeTrustAnchors" "LLDP" "EmitLLDP" "BindCarrier" "Address"
-      "Gateway" "DNS" "Domains" "NTP" "IPForward" "IPMasquerade"
-      "IPv6PrivacyExtensions" "IPv6AcceptRA" "IPv6DuplicateAddressDetection"
-      "IPv6HopLimit" "IPv4ProxyARP" "IPv6ProxyNDP" "IPv6ProxyNDPAddress"
-      "IPv6PrefixDelegation" "IPv6MTUBytes" "Bridge" "Bond" "VRF" "VLAN"
-      "IPVLAN" "MACVLAN" "VXLAN" "Tunnel" "ActiveSlave" "PrimarySlave"
-      "ConfigureWithoutCarrier" "Xfrm" "KeepConfiguration"
-    ])
-    # Note: For DHCP the values both, none, v4, v6 are deprecated
-    (assertValueOneOf "DHCP" ["yes" "no" "ipv4" "ipv6" "both" "none" "v4" "v6"])
-    (assertValueOneOf "DHCPServer" boolValues)
-    (assertValueOneOf "LinkLocalAddressing" ["yes" "no" "ipv4" "ipv6" "ipv4-fallback" "fallback"])
-    (assertValueOneOf "IPv4LLRoute" boolValues)
-    (assertValueOneOf "LLMNR" ["yes" "resolve" "no"])
-    (assertValueOneOf "MulticastDNS" ["yes" "resolve" "no"])
-    (assertValueOneOf "DNSOverTLS" ["opportunistic" "no"])
-    (assertValueOneOf "DNSSEC" ["yes" "allow-downgrade" "no"])
-    (assertValueOneOf "LLDP" ["yes" "routers-only" "no"])
-    (assertValueOneOf "EmitLLDP" ["yes" "no" "nearest-bridge" "non-tpmr-bridge" "customer-bridge"])
-    (assertValueOneOf "IPForward" ["yes" "no" "ipv4" "ipv6"])
-    (assertValueOneOf "IPMasquerade" boolValues)
-    (assertValueOneOf "IPv6PrivacyExtensions" ["yes" "no" "prefer-public" "kernel"])
-    (assertValueOneOf "IPv6AcceptRA" boolValues)
-    (assertValueOneOf "IPv4ProxyARP" boolValues)
-    (assertValueOneOf "IPv6ProxyNDP" boolValues)
-    (assertValueOneOf "IPv6PrefixDelegation" (boolValues ++ [ "dhcpv6" "static" ]))
-    (assertValueOneOf "ActiveSlave" boolValues)
-    (assertValueOneOf "PrimarySlave" boolValues)
-    (assertValueOneOf "ConfigureWithoutCarrier" boolValues)
-    (assertValueOneOf "KeepConfiguration" (boolValues ++ ["static" "dhcp-on-stop" "dhcp"]))
-  ];
+      sectionVRF = checkUnitConfig "VRF" [
+        (assertOnlyFields [
+          "Table"
+        ])
+        (assertInt "Table")
+        (assertMinimum "Table" 0)
+      ];
+    };
 
-  checkAddress = checkUnitConfig "Address" [
-    (assertOnlyFields [
-      "Address" "Peer" "Broadcast" "Label" "PreferredLifetime" "Scope"
-      "HomeAddress" "DuplicateAddressDetection" "ManageTemporaryAddress"
-      "PrefixRoute" "AutoJoin"
-    ])
-    (assertHasField "Address")
-    (assertValueOneOf "PreferredLifetime" ["forever" "infinity" "0" 0])
-    (assertValueOneOf "HomeAddress" boolValues)
-    (assertValueOneOf "DuplicateAddressDetection" boolValues)
-    (assertValueOneOf "ManageTemporaryAddress" boolValues)
-    (assertValueOneOf "PrefixRoute" boolValues)
-    (assertValueOneOf "AutoJoin" boolValues)
-  ];
+    network = {
+
+      sectionLink = checkUnitConfig "Link" [
+        (assertOnlyFields [
+          "MACAddress"
+          "MTUBytes"
+          "ARP"
+          "Multicast"
+          "AllMulticast"
+          "Unmanaged"
+          "RequiredForOnline"
+        ])
+        (assertMacAddress "MACAddress")
+        (assertByteFormat "MTUBytes")
+        (assertValueOneOf "ARP" boolValues)
+        (assertValueOneOf "Multicast" boolValues)
+        (assertValueOneOf "AllMulticast" boolValues)
+        (assertValueOneOf "Unmanaged" boolValues)
+        (assertValueOneOf "RequiredForOnline" (boolValues ++ [
+          "missing"
+          "off"
+          "no-carrier"
+          "dormant"
+          "degraded-carrier"
+          "carrier"
+          "degraded"
+          "enslaved"
+          "routable"
+        ]))
+      ];
 
-  checkRoutingPolicyRule = checkUnitConfig "RoutingPolicyRule" [
-    (assertOnlyFields [
-      "TypeOfService" "From" "To" "FirewallMark" "Table" "Priority"
-      "IncomingInterface" "OutgoingInterface" "SourcePort" "DestinationPort"
-      "IPProtocol" "InvertRule" "Family"
-    ])
-    (assertRange "TypeOfService" 0 255)
-    # The following check won't work on nix <= 2.2
-    # see https://github.com/NixOS/nix/pull/2378
-    #
-    # Add this again when we'll have drop the
-    # nix < 2.2 support.
-    #  (assertRange "FirewallMark" 1 4294967295)
-    (assertInt "Priority")
-    (assertPort "SourcePort")
-    (assertPort "DestinationPort")
-    (assertValueOneOf "InvertRule" boolValues)
-    (assertValueOneOf "Family" ["ipv4" "ipv6" "both"])
-  ];
+      sectionNetwork = checkUnitConfig "Network" [
+        (assertOnlyFields [
+          "Description"
+          "DHCP"
+          "DHCPServer"
+          "LinkLocalAddressing"
+          "IPv4LLRoute"
+          "DefaultRouteOnDevice"
+          "IPv6Token"
+          "LLMNR"
+          "MulticastDNS"
+          "DNSOverTLS"
+          "DNSSEC"
+          "DNSSECNegativeTrustAnchors"
+          "LLDP"
+          "EmitLLDP"
+          "BindCarrier"
+          "Address"
+          "Gateway"
+          "DNS"
+          "Domains"
+          "DNSDefaultRoute"
+          "NTP"
+          "IPForward"
+          "IPMasquerade"
+          "IPv6PrivacyExtensions"
+          "IPv6AcceptRA"
+          "IPv6DuplicateAddressDetection"
+          "IPv6HopLimit"
+          "IPv4ProxyARP"
+          "IPv6ProxyNDP"
+          "IPv6ProxyNDPAddress"
+          "IPv6PrefixDelegation"
+          "IPv6MTUBytes"
+          "Bridge"
+          "Bond"
+          "VRF"
+          "VLAN"
+          "IPVLAN"
+          "MACVLAN"
+          "VXLAN"
+          "Tunnel"
+          "MACsec"
+          "ActiveSlave"
+          "PrimarySlave"
+          "ConfigureWithoutCarrier"
+          "IgnoreCarrierLoss"
+          "Xfrm"
+          "KeepConfiguration"
+        ])
+        # Note: For DHCP the values both, none, v4, v6 are deprecated
+        (assertValueOneOf "DHCP" ["yes" "no" "ipv4" "ipv6"])
+        (assertValueOneOf "DHCPServer" boolValues)
+        (assertValueOneOf "LinkLocalAddressing" ["yes" "no" "ipv4" "ipv6" "fallback" "ipv4-fallback"])
+        (assertValueOneOf "IPv4LLRoute" boolValues)
+        (assertValueOneOf "DefaultRouteOnDevice" boolValues)
+        (assertValueOneOf "LLMNR" (boolValues ++ ["resolve"]))
+        (assertValueOneOf "MulticastDNS" (boolValues ++ ["resolve"]))
+        (assertValueOneOf "DNSOverTLS" (boolValues ++ ["opportunistic"]))
+        (assertValueOneOf "DNSSEC" (boolValues ++ ["allow-downgrade"]))
+        (assertValueOneOf "LLDP" (boolValues ++ ["routers-only"]))
+        (assertValueOneOf "EmitLLDP" (boolValues ++ ["nearest-bridge" "non-tpmr-bridge" "customer-bridge"]))
+        (assertValueOneOf "DNSDefaultRoute" boolValues)
+        (assertValueOneOf "IPForward" (boolValues ++ ["ipv4" "ipv6"]))
+        (assertValueOneOf "IPMasquerade" boolValues)
+        (assertValueOneOf "IPv6PrivacyExtensions" (boolValues ++ ["prefer-public" "kernel"]))
+        (assertValueOneOf "IPv6AcceptRA" boolValues)
+        (assertInt "IPv6DuplicateAddressDetection")
+        (assertMinimum "IPv6DuplicateAddressDetection" 0)
+        (assertInt "IPv6HopLimit")
+        (assertMinimum "IPv6HopLimit" 0)
+        (assertValueOneOf "IPv4ProxyARP" boolValues)
+        (assertValueOneOf "IPv6ProxyNDP" boolValues)
+        (assertValueOneOf "IPv6PrefixDelegation" ["static" "dhcpv6" "yes" "false"])
+        (assertByteFormat "IPv6MTUBytes")
+        (assertValueOneOf "ActiveSlave" boolValues)
+        (assertValueOneOf "PrimarySlave" boolValues)
+        (assertValueOneOf "ConfigureWithoutCarrier" boolValues)
+        (assertValueOneOf "IgnoreCarrierLoss" boolValues)
+        (assertValueOneOf "KeepConfiguration" (boolValues ++ ["static" "dhcp-on-stop" "dhcp"]))
+      ];
 
-  checkRoute = checkUnitConfig "Route" [
-    (assertOnlyFields [
-      "Gateway" "GatewayOnLink" "Destination" "Source" "Metric"
-      "IPv6Preference" "Scope" "PreferredSource" "Table" "Protocol" "Type"
-      "InitialCongestionWindow" "InitialAdvertisedReceiveWindow" "QuickAck"
-      "MTUBytes"
-    ])
-  ];
+      sectionAddress = checkUnitConfig "Address" [
+        (assertOnlyFields [
+          "Address"
+          "Peer"
+          "Broadcast"
+          "Label"
+          "PreferredLifetime"
+          "Scope"
+          "HomeAddress"
+          "DuplicateAddressDetection"
+          "ManageTemporaryAddress"
+          "AddPrefixRoute"
+          "AutoJoin"
+        ])
+        (assertHasField "Address")
+        (assertValueOneOf "PreferredLifetime" ["forever" "infinity" "0" 0])
+        (assertValueOneOf "HomeAddress" boolValues)
+        (assertValueOneOf "DuplicateAddressDetection" ["ipv4" "ipv6" "both" "none"])
+        (assertValueOneOf "ManageTemporaryAddress" boolValues)
+        (assertValueOneOf "AddPrefixRoute" boolValues)
+        (assertValueOneOf "AutoJoin" boolValues)
+      ];
 
-  checkDhcpV4 = checkUnitConfig "DHCPv4" [
-    (assertOnlyFields [
-      "UseDNS" "RoutesToDNS" "UseNTP" "UseMTU" "Anonymize" "SendHostname" "UseHostname"
-      "Hostname" "UseDomains" "UseRoutes" "UseTimezone"
-      "ClientIdentifier" "VendorClassIdentifier" "UserClass" "MaxAttempts"
-      "DUIDType" "DUIDRawData" "IAID" "RequestBroadcast" "RouteMetric" "RouteTable"
-      "ListenPort" "SendRelease"
-    ])
-    (assertValueOneOf "UseDNS" boolValues)
-    (assertValueOneOf "RoutesToDNS" boolValues)
-    (assertValueOneOf "UseNTP" boolValues)
-    (assertValueOneOf "UseMTU" boolValues)
-    (assertValueOneOf "Anonymize" boolValues)
-    (assertValueOneOf "SendHostname" boolValues)
-    (assertValueOneOf "UseHostname" boolValues)
-    (assertValueOneOf "UseDomains" ["yes" "no" "route"])
-    (assertValueOneOf "UseRoutes" boolValues)
-    (assertValueOneOf "UseTimezone" boolValues)
-    (assertMinimum "MaxAttempts" 0)
-    (assertValueOneOf "RequestBroadcast" boolValues)
-    (assertInt "RouteTable")
-    (assertMinimum "RouteTable" 0)
-    (assertValueOneOf "SendRelease" boolValues)
-  ];
+      sectionRoutingPolicyRule = checkUnitConfig "RoutingPolicyRule" [
+        (assertOnlyFields [
+          "TypeOfService"
+          "From"
+          "To"
+          "FirewallMark"
+          "Table"
+          "Priority"
+          "IncomingInterface"
+          "OutgoingInterface"
+          "SourcePort"
+          "DestinationPort"
+          "IPProtocol"
+          "InvertRule"
+          "Family"
+          "User"
+          "SuppressPrefixLength"
+        ])
+        (assertInt "TypeOfService")
+        (assertRange "TypeOfService" 0 255)
+        (assertInt "FirewallMark")
+        (assertRange "FirewallMark" 1 4294967295)
+        (assertInt "Priority")
+        (assertPort "SourcePort")
+        (assertPort "DestinationPort")
+        (assertValueOneOf "InvertRule" boolValues)
+        (assertValueOneOf "Family" ["ipv4" "ipv6" "both"])
+        (assertInt "SuppressPrefixLength")
+        (assertRange "SuppressPrefixLength" 0 128)
+      ];
 
-  checkDhcpV6 = checkUnitConfig "DHCPv6" [
-    (assertOnlyFields [
-      "UseDns" "UseNTP" "RapidCommit" "ForceDHCPv6PDOtherInformation"
-      "PrefixDelegationHint"
-    ])
-    (assertValueOneOf "UseDNS" boolValues)
-    (assertValueOneOf "UseNTP" boolValues)
-    (assertValueOneOf "RapidCommit" boolValues)
-    (assertValueOneOf "ForceDHCPv6PDOtherInformation" boolValues)
-  ];
+      sectionRoute = checkUnitConfig "Route" [
+        (assertOnlyFields [
+          "Gateway"
+          "GatewayOnLink"
+          "Destination"
+          "Source"
+          "Metric"
+          "IPv6Preference"
+          "Scope"
+          "PreferredSource"
+          "Table"
+          "Protocol"
+          "Type"
+          "InitialCongestionWindow"
+          "InitialAdvertisedReceiveWindow"
+          "QuickAck"
+          "FastOpenNoCookie"
+          "TTLPropagate"
+          "MTUBytes"
+          "IPServiceType"
+          "MultiPathRoute"
+        ])
+        (assertValueOneOf "GatewayOnLink" boolValues)
+        (assertInt "Metric")
+        (assertValueOneOf "IPv6Preference" ["low" "medium" "high"])
+        (assertValueOneOf "Scope" ["global" "site" "link" "host" "nowhere"])
+        (assertValueOneOf "Type" [
+          "unicast"
+          "local"
+          "broadcast"
+          "anycast"
+          "multicast"
+          "blackhole"
+          "unreachable"
+          "prohibit"
+          "throw"
+          "nat"
+          "xresolve"
+        ])
+        (assertValueOneOf "QuickAck" boolValues)
+        (assertValueOneOf "FastOpenNoCookie" boolValues)
+        (assertValueOneOf "TTLPropagate" boolValues)
+        (assertByteFormat "MTUBytes")
+        (assertValueOneOf "IPServiceType" ["CS6" "CS4"])
+      ];
 
-  checkIpv6PrefixDelegation = checkUnitConfig "IPv6PrefixDelegation" [
-    (assertOnlyFields [
-      "Managed"  "OtherInformation"  "RouterLifetimeSec"
-      "RouterPreference"  "EmitDNS"  "DNS"  "EmitDomains"  "Domains"
-      "DNSLifetimeSec"
-    ])
-    (assertValueOneOf "Managed" boolValues)
-    (assertValueOneOf "OtherInformation" boolValues)
-    (assertValueOneOf "RouterPreference" ["high" "medium" "low" "normal" "default"])
-    (assertValueOneOf "EmitDNS" boolValues)
-    (assertValueOneOf "EmitDomains" boolValues)
-    (assertMinimum "DNSLifetimeSec" 0)
-  ];
+      sectionDHCPv4 = checkUnitConfig "DHCPv4" [
+        (assertOnlyFields [
+          "UseDNS"
+          "RoutesToDNS"
+          "UseNTP"
+          "UseSIP"
+          "UseMTU"
+          "Anonymize"
+          "SendHostname"
+          "UseHostname"
+          "Hostname"
+          "UseDomains"
+          "UseRoutes"
+          "UseTimezone"
+          "ClientIdentifier"
+          "VendorClassIdentifier"
+          "UserClass"
+          "MaxAttempts"
+          "DUIDType"
+          "DUIDRawData"
+          "IAID"
+          "RequestBroadcast"
+          "RouteMetric"
+          "RouteTable"
+          "RouteMTUBytes"
+          "ListenPort"
+          "SendRelease"
+          "SendDecline"
+          "BlackList"
+          "RequestOptions"
+          "SendOption"
+        ])
+        (assertValueOneOf "UseDNS" boolValues)
+        (assertValueOneOf "RoutesToDNS" boolValues)
+        (assertValueOneOf "UseNTP" boolValues)
+        (assertValueOneOf "UseSIP" boolValues)
+        (assertValueOneOf "UseMTU" boolValues)
+        (assertValueOneOf "Anonymize" boolValues)
+        (assertValueOneOf "SendHostname" boolValues)
+        (assertValueOneOf "UseHostname" boolValues)
+        (assertValueOneOf "UseDomains" (boolValues ++ ["route"]))
+        (assertValueOneOf "UseRoutes" boolValues)
+        (assertValueOneOf "UseTimezone" boolValues)
+        (assertValueOneOf "ClientIdentifier" ["mac" "duid" "duid-only"])
+        (assertInt "IAID")
+        (assertValueOneOf "RequestBroadcast" boolValues)
+        (assertInt "RouteMetric")
+        (assertInt "RouteTable")
+        (assertRange "RouteTable" 0 4294967295)
+        (assertByteFormat "RouteMTUBytes")
+        (assertPort "ListenPort")
+        (assertValueOneOf "SendRelease" boolValues)
+        (assertValueOneOf "SendDecline" boolValues)
+      ];
 
-  checkIpv6Prefix = checkUnitConfig "IPv6Prefix" [
-    (assertOnlyFields [
-      "AddressAutoconfiguration"  "OnLink"  "Prefix"
-      "PreferredLifetimeSec" "ValidLifetimeSec"
-    ])
-    (assertValueOneOf "AddressAutoconfiguration" boolValues)
-    (assertValueOneOf "OnLink" boolValues)
-    (assertMinimum "PreferredLifetimeSec" 0)
-    (assertMinimum "ValidLifetimeSec" 0)
-  ];
+      sectionDHCPv6 = checkUnitConfig "DHCPv6" [
+        (assertOnlyFields [
+          "UseDNS"
+          "UseNTP"
+          "RapidCommit"
+          "ForceDHCPv6PDOtherInformation"
+          "PrefixDelegationHint"
+        ])
+        (assertValueOneOf "UseDNS" boolValues)
+        (assertValueOneOf "UseNTP" boolValues)
+        (assertValueOneOf "RapidCommit" boolValues)
+        (assertValueOneOf "ForceDHCPv6PDOtherInformation" boolValues)
+      ];
 
+      sectionDHCPServer = checkUnitConfig "DHCPServer" [
+        (assertOnlyFields [
+          "PoolOffset"
+          "PoolSize"
+          "DefaultLeaseTimeSec"
+          "MaxLeaseTimeSec"
+          "EmitDNS"
+          "DNS"
+          "EmitNTP"
+          "NTP"
+          "EmitSIP"
+          "SIP"
+          "EmitRouter"
+          "EmitTimezone"
+          "Timezone"
+          "SendOption"
+        ])
+        (assertInt "PoolOffset")
+        (assertMinimum "PoolOffset" 0)
+        (assertInt "PoolSize")
+        (assertMinimum "PoolSize" 0)
+        (assertValueOneOf "EmitDNS" boolValues)
+        (assertValueOneOf "EmitNTP" boolValues)
+        (assertValueOneOf "EmitSIP" boolValues)
+        (assertValueOneOf "EmitRouter" boolValues)
+        (assertValueOneOf "EmitTimezone" boolValues)
+      ];
 
-  checkDhcpServer = checkUnitConfig "DHCPServer" [
-    (assertOnlyFields [
-      "PoolOffset" "PoolSize" "DefaultLeaseTimeSec" "MaxLeaseTimeSec"
-      "EmitDNS" "DNS" "EmitNTP" "NTP" "EmitRouter" "EmitTimezone" "Timezone"
-    ])
-    (assertValueOneOf "EmitDNS" boolValues)
-    (assertValueOneOf "EmitNTP" boolValues)
-    (assertValueOneOf "EmitRouter" boolValues)
-    (assertValueOneOf "EmitTimezone" boolValues)
-  ];
+      sectionIPv6PrefixDelegation = checkUnitConfig "IPv6PrefixDelegation" [
+        (assertOnlyFields [
+          "Managed"
+          "OtherInformation"
+          "RouterLifetimeSec"
+          "RouterPreference"
+          "EmitDNS"
+          "DNS"
+          "EmitDomains"
+          "Domains"
+          "DNSLifetimeSec"
+        ])
+        (assertValueOneOf "Managed" boolValues)
+        (assertValueOneOf "OtherInformation" boolValues)
+        (assertValueOneOf "RouterPreference" ["high" "medium" "low" "normal" "default"])
+        (assertValueOneOf "EmitDNS" boolValues)
+        (assertValueOneOf "EmitDomains" boolValues)
+      ];
 
-  # .network files have a [Link] section with different options than in .netlink files
-  checkNetworkLink = checkUnitConfig "Link" [
-    (assertOnlyFields [
-      "MACAddress" "MTUBytes" "ARP" "Multicast" "Unmanaged" "RequiredForOnline"
-    ])
-    (assertMacAddress "MACAddress")
-    (assertByteFormat "MTUBytes")
-    (assertValueOneOf "ARP" boolValues)
-    (assertValueOneOf "Multicast" boolValues)
-    (assertValueOneOf "Unmanaged" boolValues)
-    (assertValueOneOf "RequiredForOnline" (boolValues ++ ["off" "no-carrier" "dormant" "degraded-carrier" "carrier" "degraded" "enslaved" "routable"]))
-  ];
+      sectionIPv6Prefix = checkUnitConfig "IPv6Prefix" [
+        (assertOnlyFields [
+          "AddressAutoconfiguration"
+          "OnLink"
+          "Prefix"
+          "PreferredLifetimeSec"
+          "ValidLifetimeSec"
+        ])
+        (assertValueOneOf "AddressAutoconfiguration" boolValues)
+        (assertValueOneOf "OnLink" boolValues)
+      ];
 
+    };
+  };
 
   commonNetworkOptions = {
 
@@ -406,7 +761,7 @@ let
     linkConfig = mkOption {
       default = {};
       example = { MACAddress = "00:ff:ee:aa:cc:dd"; };
-      type = types.addCheck (types.attrsOf unitOption) checkLink;
+      type = types.addCheck (types.attrsOf unitOption) check.link.sectionLink;
       description = ''
         Each attribute in this set specifies an option in the
         <literal>[Link]</literal> section of the unit.  See
@@ -417,12 +772,28 @@ let
 
   };
 
+  wireguardPeerOptions = {
+    options = {
+      wireguardPeerConfig = mkOption {
+        default = {};
+        example = { };
+        type = types.addCheck (types.attrsOf unitOption) check.netdev.sectionWireGuardPeer;
+        description = ''
+          Each attribute in this set specifies an option in the
+          <literal>[WireGuardPeer]</literal> section of the unit.  See
+          <citerefentry><refentrytitle>systemd.network</refentrytitle>
+          <manvolnum>5</manvolnum></citerefentry> for details.
+        '';
+      };
+    };
+  };
+
   netdevOptions = commonNetworkOptions // {
 
     netdevConfig = mkOption {
       default = {};
       example = { Name = "mybridge"; Kind = "bridge"; };
-      type = types.addCheck (types.attrsOf unitOption) checkNetdev;
+      type = types.addCheck (types.attrsOf unitOption) check.netdev.sectionNetdev;
       description = ''
         Each attribute in this set specifies an option in the
         <literal>[Netdev]</literal> section of the unit.  See
@@ -431,65 +802,10 @@ let
       '';
     };
 
-    vrfConfig = mkOption {
-      default = {};
-      example = { Table = 2342; };
-      type = types.addCheck (types.attrsOf unitOption) checkVRF;
-      description = ''
-        Each attribute in this set specifies an option in the
-        <literal>[VRF]</literal> section of the unit. See
-        <citerefentry><refentrytitle>systemd.netdev</refentrytitle>
-        <manvolnum>5</manvolnum></citerefentry> for details.
-        A detailed explanation about how VRFs work can be found in the
-        <link xlink:href="https://www.kernel.org/doc/Documentation/networking/vrf.txt">kernel
-        docs</link>.
-      '';
-    };
-
-    wireguardConfig = mkOption {
-      default = {};
-      example = {
-        PrivateKeyFile = "/etc/wireguard/secret.key";
-        ListenPort = 51820;
-        FwMark = 42;
-      };
-      type = types.addCheck (types.attrsOf unitOption) checkWireGuard;
-      description = ''
-        Each attribute in this set specifies an option in the
-        <literal>[WireGuard]</literal> section of the unit. See
-        <citerefentry><refentrytitle>systemd.netdev</refentrytitle>
-        <manvolnum>5</manvolnum></citerefentry> for details.
-        Use <literal>PrivateKeyFile</literal> instead of
-        <literal>PrivateKey</literal>: the nix store is
-        world-readable.
-      '';
-    };
-
-    wireguardPeers = mkOption {
-      default = [];
-      example = [ { wireguardPeerConfig={
-        Endpoint = "192.168.1.1:51820";
-        PublicKey = "27s0OvaBBdHoJYkH9osZpjpgSOVNw+RaKfboT/Sfq0g=";
-        PresharedKeyFile = "/etc/wireguard/psk.key";
-        AllowedIPs = [ "10.0.0.1/32" ];
-        PersistentKeepalive = 15;
-      };}];
-      type = with types; listOf (submodule wireguardPeerOptions);
-      description = ''
-        Each item in this array specifies an option in the
-        <literal>[WireGuardPeer]</literal> section of the unit. See
-        <citerefentry><refentrytitle>systemd.netdev</refentrytitle>
-        <manvolnum>5</manvolnum></citerefentry> for details.
-        Use <literal>PresharedKeyFile</literal> instead of
-        <literal>PresharedKey</literal>: the nix store is
-        world-readable.
-      '';
-    };
-
     vlanConfig = mkOption {
       default = {};
-      example = { Id = "4"; };
-      type = types.addCheck (types.attrsOf unitOption) checkVlan;
+      example = { Id = 4; };
+      type = types.addCheck (types.attrsOf unitOption) check.netdev.sectionVLAN;
       description = ''
         Each attribute in this set specifies an option in the
         <literal>[VLAN]</literal> section of the unit.  See
@@ -501,7 +817,7 @@ let
     macvlanConfig = mkOption {
       default = {};
       example = { Mode = "private"; };
-      type = types.addCheck (types.attrsOf unitOption) checkMacvlan;
+      type = types.addCheck (types.attrsOf unitOption) check.netdev.sectionMACVLAN;
       description = ''
         Each attribute in this set specifies an option in the
         <literal>[MACVLAN]</literal> section of the unit.  See
@@ -513,7 +829,7 @@ let
     vxlanConfig = mkOption {
       default = {};
       example = { Id = "4"; };
-      type = types.addCheck (types.attrsOf unitOption) checkVxlan;
+      type = types.addCheck (types.attrsOf unitOption) check.netdev.sectionVXLAN;
       description = ''
         Each attribute in this set specifies an option in the
         <literal>[VXLAN]</literal> section of the unit.  See
@@ -525,7 +841,7 @@ let
     tunnelConfig = mkOption {
       default = {};
       example = { Remote = "192.168.1.1"; };
-      type = types.addCheck (types.attrsOf unitOption) checkTunnel;
+      type = types.addCheck (types.attrsOf unitOption) check.netdev.sectionTunnel;
       description = ''
         Each attribute in this set specifies an option in the
         <literal>[Tunnel]</literal> section of the unit.  See
@@ -537,7 +853,7 @@ let
     peerConfig = mkOption {
       default = {};
       example = { Name = "veth2"; };
-      type = types.addCheck (types.attrsOf unitOption) checkPeer;
+      type = types.addCheck (types.attrsOf unitOption) check.netdev.sectionPeer;
       description = ''
         Each attribute in this set specifies an option in the
         <literal>[Peer]</literal> section of the unit.  See
@@ -549,7 +865,7 @@ let
     tunConfig = mkOption {
       default = {};
       example = { User = "openvpn"; };
-      type = types.addCheck (types.attrsOf unitOption) checkTun;
+      type = types.addCheck (types.attrsOf unitOption) check.netdev.sectionTun;
       description = ''
         Each attribute in this set specifies an option in the
         <literal>[Tun]</literal> section of the unit.  See
@@ -561,7 +877,7 @@ let
     tapConfig = mkOption {
       default = {};
       example = { User = "openvpn"; };
-      type = types.addCheck (types.attrsOf unitOption) checkTap;
+      type = types.addCheck (types.attrsOf unitOption) check.netdev.sectionTap;
       description = ''
         Each attribute in this set specifies an option in the
         <literal>[Tap]</literal> section of the unit.  See
@@ -570,10 +886,50 @@ let
       '';
     };
 
+    wireguardConfig = mkOption {
+      default = {};
+      example = {
+        PrivateKeyFile = "/etc/wireguard/secret.key";
+        ListenPort = 51820;
+        FwMark = 42;
+      };
+      type = types.addCheck (types.attrsOf unitOption) check.netdev.sectionWireGuard;
+      description = ''
+        Each attribute in this set specifies an option in the
+        <literal>[WireGuard]</literal> section of the unit. See
+        <citerefentry><refentrytitle>systemd.netdev</refentrytitle>
+        <manvolnum>5</manvolnum></citerefentry> for details.
+        Use <literal>PrivateKeyFile</literal> instead of
+        <literal>PrivateKey</literal>: the nix store is
+        world-readable.
+      '';
+    };
+
+    wireguardPeers = mkOption {
+      default = [];
+      example = [ { wireguardPeerConfig={
+        Endpoint = "192.168.1.1:51820";
+        PublicKey = "27s0OvaBBdHoJYkH9osZpjpgSOVNw+RaKfboT/Sfq0g=";
+        PresharedKeyFile = "/etc/wireguard/psk.key";
+        AllowedIPs = [ "10.0.0.1/32" ];
+        PersistentKeepalive = 15;
+      };}];
+      type = with types; listOf (submodule wireguardPeerOptions);
+      description = ''
+        Each item in this array specifies an option in the
+        <literal>[WireGuardPeer]</literal> section of the unit. See
+        <citerefentry><refentrytitle>systemd.netdev</refentrytitle>
+        <manvolnum>5</manvolnum></citerefentry> for details.
+        Use <literal>PresharedKeyFile</literal> instead of
+        <literal>PresharedKey</literal>: the nix store is
+        world-readable.
+      '';
+    };
+
     bondConfig = mkOption {
       default = {};
       example = { Mode = "802.3ad"; };
-      type = types.addCheck (types.attrsOf unitOption) checkBond;
+      type = types.addCheck (types.attrsOf unitOption) check.netdev.sectionBond;
       description = ''
         Each attribute in this set specifies an option in the
         <literal>[Bond]</literal> section of the unit.  See
@@ -585,7 +941,7 @@ let
     xfrmConfig = mkOption {
       default = {};
       example = { InterfaceId = 1; };
-      type = types.addCheck (types.attrsOf unitOption) checkXfrm;
+      type = types.addCheck (types.attrsOf unitOption) check.netdev.sectionXfrm;
       description = ''
         Each attribute in this set specifies an option in the
         <literal>[Xfrm]</literal> section of the unit.  See
@@ -594,6 +950,21 @@ let
       '';
     };
 
+    vrfConfig = mkOption {
+      default = {};
+      example = { Table = 2342; };
+      type = types.addCheck (types.attrsOf unitOption) check.netdev.sectionVRF;
+      description = ''
+        Each attribute in this set specifies an option in the
+        <literal>[VRF]</literal> section of the unit. See
+        <citerefentry><refentrytitle>systemd.netdev</refentrytitle>
+        <manvolnum>5</manvolnum></citerefentry> for details.
+        A detailed explanation about how VRFs work can be found in the
+        <link xlink:href="https://www.kernel.org/doc/Documentation/networking/vrf.txt">kernel
+        docs</link>.
+      '';
+    };
+
   };
 
   addressOptions = {
@@ -601,7 +972,7 @@ let
       addressConfig = mkOption {
         default = {};
         example = { Address = "192.168.0.100/24"; };
-        type = types.addCheck (types.attrsOf unitOption) checkAddress;
+        type = types.addCheck (types.attrsOf unitOption) check.network.sectionAddress;
         description = ''
           Each attribute in this set specifies an option in the
           <literal>[Address]</literal> section of the unit.  See
@@ -617,7 +988,7 @@ let
       routingPolicyRuleConfig = mkOption {
         default = { };
         example = { routingPolicyRuleConfig = { Table = 10; IncomingInterface = "eth1"; Family = "both"; } ;};
-        type = types.addCheck (types.attrsOf unitOption) checkRoutingPolicyRule;
+        type = types.addCheck (types.attrsOf unitOption) check.network.sectionRoutingPolicyRule;
         description = ''
           Each attribute in this set specifies an option in the
           <literal>[RoutingPolicyRule]</literal> section of the unit.  See
@@ -633,7 +1004,7 @@ let
       routeConfig = mkOption {
         default = {};
         example = { Gateway = "192.168.0.1"; };
-        type = types.addCheck (types.attrsOf unitOption) checkRoute;
+        type = types.addCheck (types.attrsOf unitOption) check.network.sectionRoute;
         description = ''
           Each attribute in this set specifies an option in the
           <literal>[Route]</literal> section of the unit.  See
@@ -644,28 +1015,12 @@ let
     };
   };
 
-  wireguardPeerOptions = {
-    options = {
-      wireguardPeerConfig = mkOption {
-        default = {};
-        example = { };
-        type = types.addCheck (types.attrsOf unitOption) checkWireGuardPeer;
-        description = ''
-          Each attribute in this set specifies an option in the
-          <literal>[WireGuardPeer]</literal> section of the unit.  See
-          <citerefentry><refentrytitle>systemd.network</refentrytitle>
-          <manvolnum>5</manvolnum></citerefentry> for details.
-        '';
-      };
-    };
-  };
-
   ipv6PrefixOptions = {
     options = {
       ipv6PrefixConfig = mkOption {
         default = {};
         example = { Prefix = "fd00::/64"; };
-        type = types.addCheck (types.attrsOf unitOption) checkIpv6Prefix;
+        type = types.addCheck (types.attrsOf unitOption) check.network.sectionIPv6Prefix;
         description = ''
           Each attribute in this set specifies an option in the
           <literal>[IPv6Prefix]</literal> section of the unit.  See
@@ -676,13 +1031,24 @@ let
     };
   };
 
-
   networkOptions = commonNetworkOptions // {
 
+    linkConfig = mkOption {
+      default = {};
+      example = { Unmanaged = true; };
+      type = types.addCheck (types.attrsOf unitOption) check.network.sectionLink;
+      description = ''
+        Each attribute in this set specifies an option in the
+        <literal>[Link]</literal> section of the unit.  See
+        <citerefentry><refentrytitle>systemd.network</refentrytitle>
+        <manvolnum>5</manvolnum></citerefentry> for details.
+      '';
+    };
+
     networkConfig = mkOption {
       default = {};
       example = { Description = "My Network"; };
-      type = types.addCheck (types.attrsOf unitOption) checkNetwork;
+      type = types.addCheck (types.attrsOf unitOption) check.network.sectionNetwork;
       description = ''
         Each attribute in this set specifies an option in the
         <literal>[Network]</literal> section of the unit.  See
@@ -701,7 +1067,7 @@ let
     dhcpV4Config = mkOption {
       default = {};
       example = { UseDNS = true; UseRoutes = true; };
-      type = types.addCheck (types.attrsOf unitOption) checkDhcpV4;
+      type = types.addCheck (types.attrsOf unitOption) check.network.sectionDHCPv4;
       description = ''
         Each attribute in this set specifies an option in the
         <literal>[DHCPv4]</literal> section of the unit.  See
@@ -713,7 +1079,7 @@ let
     dhcpV6Config = mkOption {
       default = {};
       example = { UseDNS = true; UseRoutes = true; };
-      type = types.addCheck (types.attrsOf unitOption) checkDhcpV6;
+      type = types.addCheck (types.attrsOf unitOption) check.network.sectionDHCPv6;
       description = ''
         Each attribute in this set specifies an option in the
         <literal>[DHCPv6]</literal> section of the unit.  See
@@ -722,48 +1088,36 @@ let
       '';
     };
 
-    ipv6PrefixDelegationConfig = mkOption {
+    dhcpServerConfig = mkOption {
       default = {};
-      example = { EmitDNS = true; Managed = true; OtherInformation = true; };
-      type = types.addCheck (types.attrsOf unitOption) checkIpv6PrefixDelegation;
+      example = { PoolOffset = 50; EmitDNS = false; };
+      type = types.addCheck (types.attrsOf unitOption) check.network.sectionDHCPServer;
       description = ''
         Each attribute in this set specifies an option in the
-        <literal>[IPv6PrefixDelegation]</literal> section of the unit.  See
-        <citerefentry><refentrytitle>systemd.network</refentrytitle>
-        <manvolnum>5</manvolnum></citerefentry> for details.
-      '';
-    };
-
-    ipv6Prefixes = mkOption {
-      default = [];
-      example = { AddressAutoconfiguration = true; OnLink = true; };
-      type = with types; listOf (submodule ipv6PrefixOptions);
-      description = ''
-        A list of ipv6Prefix sections to be added to the unit.  See
+        <literal>[DHCPServer]</literal> section of the unit.  See
         <citerefentry><refentrytitle>systemd.network</refentrytitle>
         <manvolnum>5</manvolnum></citerefentry> for details.
       '';
     };
 
-    dhcpServerConfig = mkOption {
+    ipv6PrefixDelegationConfig = mkOption {
       default = {};
-      example = { PoolOffset = 50; EmitDNS = false; };
-      type = types.addCheck (types.attrsOf unitOption) checkDhcpServer;
+      example = { EmitDNS = true; Managed = true; OtherInformation = true; };
+      type = types.addCheck (types.attrsOf unitOption) check.network.sectionIPv6PrefixDelegation;
       description = ''
         Each attribute in this set specifies an option in the
-        <literal>[DHCPServer]</literal> section of the unit.  See
+        <literal>[IPv6PrefixDelegation]</literal> section of the unit.  See
         <citerefentry><refentrytitle>systemd.network</refentrytitle>
         <manvolnum>5</manvolnum></citerefentry> for details.
       '';
     };
 
-    linkConfig = mkOption {
-      default = {};
-      example = { Unmanaged = true; };
-      type = types.addCheck (types.attrsOf unitOption) checkNetworkLink;
+    ipv6Prefixes = mkOption {
+      default = [];
+      example = { AddressAutoconfiguration = true; OnLink = true; };
+      type = with types; listOf (submodule ipv6PrefixOptions);
       description = ''
-        Each attribute in this set specifies an option in the
-        <literal>[Link]</literal> section of the unit.  See
+        A list of ipv6Prefix sections to be added to the unit.  See
         <citerefentry><refentrytitle>systemd.network</refentrytitle>
         <manvolnum>5</manvolnum></citerefentry> for details.
       '';
@@ -958,160 +1312,162 @@ let
     };
   };
 
-  commonMatchText = def: optionalString (def.matchConfig != {}) ''
+  commonMatchText = def: optionalString (def.matchConfig != { }) ''
     [Match]
     ${attrsToSection def.matchConfig}
   '';
 
   linkToUnit = name: def:
     { inherit (def) enable;
-      text = commonMatchText def +
-        ''
+      text = commonMatchText def
+        + ''
           [Link]
           ${attrsToSection def.linkConfig}
-
-          ${def.extraConfig}
-        '';
+        ''
+        + def.extraConfig;
     };
 
   netdevToUnit = name: def:
     { inherit (def) enable;
-      text = commonMatchText def +
-        ''
+      text = commonMatchText def
+        + ''
           [NetDev]
           ${attrsToSection def.netdevConfig}
-
-          ${optionalString (def.vlanConfig != { }) ''
-            [VLAN]
-            ${attrsToSection def.vlanConfig}
-
-          ''}
-          ${optionalString (def.macvlanConfig != { }) ''
-            [MACVLAN]
-            ${attrsToSection def.macvlanConfig}
-
-          ''}
-          ${optionalString (def.vxlanConfig != { }) ''
-            [VXLAN]
-            ${attrsToSection def.vxlanConfig}
-
-          ''}
-          ${optionalString (def.tunnelConfig != { }) ''
-            [Tunnel]
-            ${attrsToSection def.tunnelConfig}
-
-          ''}
-          ${optionalString (def.peerConfig != { }) ''
-            [Peer]
-            ${attrsToSection def.peerConfig}
-
-          ''}
-          ${optionalString (def.tunConfig != { }) ''
-            [Tun]
-            ${attrsToSection def.tunConfig}
-
-          ''}
-          ${optionalString (def.tapConfig != { }) ''
-            [Tap]
-            ${attrsToSection def.tapConfig}
-
-          ''}
-          ${optionalString (def.bondConfig != { }) ''
-            [Bond]
-            ${attrsToSection def.bondConfig}
-
-          ''}
-          ${optionalString (def.xfrmConfig != { }) ''
-            [Xfrm]
-            ${attrsToSection def.xfrmConfig}
-
-          ''}
-          ${optionalString (def.vrfConfig != { }) ''
-            [VRF]
-            ${attrsToSection def.vrfConfig}
-
-          ''}
-          ${optionalString (def.wireguardConfig != { }) ''
-            [WireGuard]
-            ${attrsToSection def.wireguardConfig}
-
-          ''}
-          ${flip concatMapStrings def.wireguardPeers (x: ''
-            [WireGuardPeer]
-            ${attrsToSection x.wireguardPeerConfig}
-
-          '')}
-          ${def.extraConfig}
-        '';
+        ''
+        + optionalString (def.vlanConfig != { }) ''
+          [VLAN]
+          ${attrsToSection def.vlanConfig}
+        ''
+        + optionalString (def.macvlanConfig != { }) ''
+          [MACVLAN]
+          ${attrsToSection def.macvlanConfig}
+        ''
+        + optionalString (def.vxlanConfig != { }) ''
+          [VXLAN]
+          ${attrsToSection def.vxlanConfig}
+        ''
+        + optionalString (def.tunnelConfig != { }) ''
+          [Tunnel]
+          ${attrsToSection def.tunnelConfig}
+        ''
+        + optionalString (def.peerConfig != { }) ''
+          [Peer]
+          ${attrsToSection def.peerConfig}
+        ''
+        + optionalString (def.tunConfig != { }) ''
+          [Tun]
+          ${attrsToSection def.tunConfig}
+        ''
+        + optionalString (def.tapConfig != { }) ''
+          [Tap]
+          ${attrsToSection def.tapConfig}
+        ''
+        + optionalString (def.wireguardConfig != { }) ''
+          [WireGuard]
+          ${attrsToSection def.wireguardConfig}
+        ''
+        + flip concatMapStrings def.wireguardPeers (x: ''
+          [WireGuardPeer]
+          ${attrsToSection x.wireguardPeerConfig}
+        '')
+        + optionalString (def.bondConfig != { }) ''
+          [Bond]
+          ${attrsToSection def.bondConfig}
+        ''
+        + optionalString (def.xfrmConfig != { }) ''
+          [Xfrm]
+          ${attrsToSection def.xfrmConfig}
+        ''
+        + optionalString (def.vrfConfig != { }) ''
+          [VRF]
+          ${attrsToSection def.vrfConfig}
+        ''
+        + def.extraConfig;
     };
 
   networkToUnit = name: def:
     { inherit (def) enable;
-      text = commonMatchText def +
+      text = commonMatchText def
+        + optionalString (def.linkConfig != { }) ''
+          [Link]
+          ${attrsToSection def.linkConfig}
         ''
-          ${optionalString (def.linkConfig != { }) ''
-            [Link]
-            ${attrsToSection def.linkConfig}
-
-          ''}
-
+        + ''
           [Network]
-          ${attrsToSection def.networkConfig}
+        ''
+        + attrsToSection def.networkConfig
+        + optionalString (def.address != [ ]) ''
           ${concatStringsSep "\n" (map (s: "Address=${s}") def.address)}
+        ''
+        + optionalString (def.gateway != [ ]) ''
           ${concatStringsSep "\n" (map (s: "Gateway=${s}") def.gateway)}
+        ''
+        + optionalString (def.dns != [ ]) ''
           ${concatStringsSep "\n" (map (s: "DNS=${s}") def.dns)}
+        ''
+        + optionalString (def.ntp != [ ]) ''
           ${concatStringsSep "\n" (map (s: "NTP=${s}") def.ntp)}
+        ''
+        + optionalString (def.bridge != [ ]) ''
           ${concatStringsSep "\n" (map (s: "Bridge=${s}") def.bridge)}
+        ''
+        + optionalString (def.bond != [ ]) ''
           ${concatStringsSep "\n" (map (s: "Bond=${s}") def.bond)}
+        ''
+        + optionalString (def.vrf != [ ]) ''
           ${concatStringsSep "\n" (map (s: "VRF=${s}") def.vrf)}
+        ''
+        + optionalString (def.vlan != [ ]) ''
           ${concatStringsSep "\n" (map (s: "VLAN=${s}") def.vlan)}
+        ''
+        + optionalString (def.macvlan != [ ]) ''
           ${concatStringsSep "\n" (map (s: "MACVLAN=${s}") def.macvlan)}
+        ''
+        + optionalString (def.vxlan != [ ]) ''
           ${concatStringsSep "\n" (map (s: "VXLAN=${s}") def.vxlan)}
+        ''
+        + optionalString (def.tunnel != [ ]) ''
           ${concatStringsSep "\n" (map (s: "Tunnel=${s}") def.tunnel)}
+        ''
+        + optionalString (def.xfrm != [ ]) ''
           ${concatStringsSep "\n" (map (s: "Xfrm=${s}") def.xfrm)}
+        ''
+        + ''
 
-          ${optionalString (def.dhcpV4Config != { }) ''
-            [DHCPv4]
-            ${attrsToSection def.dhcpV4Config}
-
-          ''}
-          ${optionalString (def.dhcpV6Config != {}) ''
-            [DHCPv6]
-            ${attrsToSection def.dhcpV6Config}
-
-          ''}
-          ${optionalString (def.ipv6PrefixDelegationConfig != {}) ''
-            [IPv6PrefixDelegation]
-            ${attrsToSection def.ipv6PrefixDelegationConfig}
-
-          ''}
-          ${flip concatMapStrings def.ipv6Prefixes (x: ''
-            [IPv6Prefix]
-            ${attrsToSection x.ipv6PrefixConfig}
-
-          '')}
-          ${optionalString (def.dhcpServerConfig != { }) ''
-            [DHCPServer]
-            ${attrsToSection def.dhcpServerConfig}
-
-          ''}
-          ${flip concatMapStrings def.addresses (x: ''
-            [Address]
-            ${attrsToSection x.addressConfig}
-
-          '')}
-          ${flip concatMapStrings def.routes (x: ''
-            [Route]
-            ${attrsToSection x.routeConfig}
-
-          '')}
-          ${flip concatMapStrings def.routingPolicyRules (x: ''
-            [RoutingPolicyRule]
-            ${attrsToSection x.routingPolicyRuleConfig}
-
-          '')}
-          ${def.extraConfig}
-        '';
+        ''
+        + flip concatMapStrings def.addresses (x: ''
+          [Address]
+          ${attrsToSection x.addressConfig}
+        '')
+        + flip concatMapStrings def.routingPolicyRules (x: ''
+          [RoutingPolicyRule]
+          ${attrsToSection x.routingPolicyRuleConfig}
+        '')
+        + flip concatMapStrings def.routes (x: ''
+          [Route]
+          ${attrsToSection x.routeConfig}
+        '')
+        + optionalString (def.dhcpV4Config != { }) ''
+          [DHCPv4]
+          ${attrsToSection def.dhcpV4Config}
+        ''
+        + optionalString (def.dhcpV6Config != { }) ''
+          [DHCPv6]
+          ${attrsToSection def.dhcpV6Config}
+        ''
+        + optionalString (def.dhcpServerConfig != { }) ''
+          [DHCPServer]
+          ${attrsToSection def.dhcpServerConfig}
+        ''
+        + optionalString (def.ipv6PrefixDelegationConfig != { }) ''
+          [IPv6PrefixDelegation]
+          ${attrsToSection def.ipv6PrefixDelegationConfig}
+        ''
+        + flip concatMapStrings def.ipv6Prefixes (x: ''
+          [IPv6Prefix]
+          ${attrsToSection x.ipv6PrefixConfig}
+        '')
+        + def.extraConfig;
     };
 
   unitFiles = listToAttrs (map (name: {
diff --git a/nixos/modules/system/boot/plymouth.nix b/nixos/modules/system/boot/plymouth.nix
index 23fce22366d8e..55e5b07ed615d 100644
--- a/nixos/modules/system/boot/plymouth.nix
+++ b/nixos/modules/system/boot/plymouth.nix
@@ -102,6 +102,8 @@ in
     systemd.services.plymouth-poweroff.wantedBy = [ "poweroff.target" ];
     systemd.services.plymouth-reboot.wantedBy = [ "reboot.target" ];
     systemd.services.plymouth-read-write.wantedBy = [ "sysinit.target" ];
+    systemd.services.systemd-ask-password-plymouth.wantedBy = ["multi-user.target"];
+    systemd.paths.systemd-ask-password-plymouth.wantedBy = ["multi-user.target"];
 
     boot.initrd.extraUtilsCommands = ''
       copy_bin_and_libs ${pkgs.plymouth}/bin/plymouthd
@@ -146,6 +148,7 @@ in
     # We use `mkAfter` to ensure that LUKS password prompt would be shown earlier than the splash screen.
     boot.initrd.preLVMCommands = mkAfter ''
       mkdir -p /etc/plymouth
+      mkdir -p /run/plymouth
       ln -s ${configFile} /etc/plymouth/plymouthd.conf
       ln -s $extraUtils/share/plymouth/plymouthd.defaults /etc/plymouth/plymouthd.defaults
       ln -s $extraUtils/share/plymouth/logo.png /etc/plymouth/logo.png
diff --git a/nixos/modules/system/boot/stage-1-init.sh b/nixos/modules/system/boot/stage-1-init.sh
index 607aec87f01e5..0c1be71cf5326 100644
--- a/nixos/modules/system/boot/stage-1-init.sh
+++ b/nixos/modules/system/boot/stage-1-init.sh
@@ -144,6 +144,14 @@ for o in $(cat /proc/cmdline); do
             set -- $(IFS==; echo $o)
             stage2Init=$2
             ;;
+        boot.persistence=*)
+            set -- $(IFS==; echo $o)
+            persistence=$2
+            ;;
+        boot.persistence.opt=*)
+            set -- $(IFS==; echo $o)
+            persistence_opt=$2
+            ;;
         boot.trace|debugtrace)
             # Show each command.
             set -x
@@ -370,12 +378,14 @@ mountFS() {
 
     mkdir -p "/mnt-root$mountPoint"
 
-    # For CIFS mounts, retry a few times before giving up.
+    # For ZFS and CIFS mounts, retry a few times before giving up.
+    # We do this for ZFS as a workaround for issue NixOS/nixpkgs#25383.
     local n=0
     while true; do
         mount "/mnt-root$mountPoint" && break
-        if [ "$fsType" != cifs -o "$n" -ge 10 ]; then fail; break; fi
+        if [ \( "$fsType" != cifs -a "$fsType" != zfs \) -o "$n" -ge 10 ]; then fail; break; fi
         echo "retrying..."
+        sleep 1
         n=$((n + 1))
     done
 
@@ -534,6 +544,14 @@ while read -u 3 mountPoint; do
       continue
     fi
 
+    if [ "$mountPoint" = / ] && [ "$device" = tmpfs ] && [ ! -z "$persistence" ]; then
+        echo persistence...
+        waitDevice "$persistence"
+        echo enabling persistence...
+        mountFS "$persistence" "$mountPoint" "$persistence_opt" "auto"
+        continue
+    fi
+
     mountFS "$device" "$mountPoint" "$options" "$fsType"
 done
 
diff --git a/nixos/modules/system/boot/stage-1.nix b/nixos/modules/system/boot/stage-1.nix
index d551466f52eb3..a04660fb56e7d 100644
--- a/nixos/modules/system/boot/stage-1.nix
+++ b/nixos/modules/system/boot/stage-1.nix
@@ -111,8 +111,8 @@ let
       copy_bin_and_libs ${pkgs.utillinux}/sbin/blkid
 
       # Copy dmsetup and lvm.
-      copy_bin_and_libs ${pkgs.lvm2}/sbin/dmsetup
-      copy_bin_and_libs ${pkgs.lvm2}/sbin/lvm
+      copy_bin_and_libs ${getBin pkgs.lvm2}/bin/dmsetup
+      copy_bin_and_libs ${getBin pkgs.lvm2}/bin/lvm
 
       # Add RAID mdadm tool.
       copy_bin_and_libs ${pkgs.mdadm}/sbin/mdadm
@@ -235,7 +235,7 @@ let
             --replace cdrom_id ${extraUtils}/bin/cdrom_id \
             --replace ${pkgs.coreutils}/bin/basename ${extraUtils}/bin/basename \
             --replace ${pkgs.utillinux}/bin/blkid ${extraUtils}/bin/blkid \
-            --replace ${pkgs.lvm2}/sbin ${extraUtils}/bin \
+            --replace ${getBin pkgs.lvm2}/bin ${extraUtils}/bin \
             --replace ${pkgs.mdadm}/sbin ${extraUtils}/sbin \
             --replace ${pkgs.bash}/bin/sh ${extraUtils}/bin/sh \
             --replace ${udev} ${extraUtils}
@@ -374,7 +374,8 @@ let
           ) config.boot.initrd.secrets)
          }
 
-        (cd "$tmp" && find . | cpio -H newc -o) | gzip >>"$1"
+        (cd "$tmp" && find . -print0 | sort -z | cpio -o -H newc -R +0:+0 --reproducible --null) | \
+          ${config.boot.initrd.compressor} >> "$1"
       '';
 
 in
@@ -559,10 +560,12 @@ in
           default = false;
           type = types.bool;
           description = ''
-            If set, this file system will be mounted in the initial
-            ramdisk.  By default, this applies to the root file system
-            and to the file system containing
-            <filename>/nix/store</filename>.
+            If set, this file system will be mounted in the initial ramdisk.
+            Note that the file system will always be mounted in the initial
+            ramdisk if its mount point is one of the following:
+            ${concatStringsSep ", " (
+              forEach utils.pathsNeededForBoot (i: "<filename>${i}</filename>")
+            )}.
           '';
         };
       });
diff --git a/nixos/modules/system/boot/stage-2-init.sh b/nixos/modules/system/boot/stage-2-init.sh
index d1de7920df987..936077b9df1eb 100644
--- a/nixos/modules/system/boot/stage-2-init.sh
+++ b/nixos/modules/system/boot/stage-2-init.sh
@@ -169,4 +169,4 @@ exec {logOutFd}>&- {logErrFd}>&-
 echo "starting systemd..."
 PATH=/run/current-system/systemd/lib/systemd:@fsPackagesPath@ \
     LOCALE_ARCHIVE=/run/current-system/sw/lib/locale/locale-archive \
-    exec systemd
+    exec @systemdExecutable@
diff --git a/nixos/modules/system/boot/stage-2.nix b/nixos/modules/system/boot/stage-2.nix
index 6b0b472273017..dd6d83ee00947 100644
--- a/nixos/modules/system/boot/stage-2.nix
+++ b/nixos/modules/system/boot/stage-2.nix
@@ -10,6 +10,7 @@ let
     src = ./stage-2-init.sh;
     shellDebug = "${pkgs.bashInteractive}/bin/bash";
     shell = "${pkgs.bash}/bin/bash";
+    inherit (config.boot) systemdExecutable;
     isExecutable = true;
     inherit (config.nix) readOnlyStore;
     inherit useHostResolvConf;
@@ -72,6 +73,15 @@ in
         '';
       };
 
+      systemdExecutable = mkOption {
+        default = "systemd";
+        type = types.str;
+        description = ''
+          The program to execute to start systemd. Typically
+          <literal>systemd</literal>, which will find systemd in the
+          PATH.
+        '';
+      };
     };
 
   };
diff --git a/nixos/modules/system/boot/systemd-nspawn.nix b/nixos/modules/system/boot/systemd-nspawn.nix
index 06ea5ee49f727..b450d77429b21 100644
--- a/nixos/modules/system/boot/systemd-nspawn.nix
+++ b/nixos/modules/system/boot/systemd-nspawn.nix
@@ -113,9 +113,9 @@ in {
   config =
     let
       units = mapAttrs' (n: v: let nspawnFile = "${n}.nspawn"; in nameValuePair nspawnFile (instanceToUnit nspawnFile v)) cfg;
-    in 
+    in
       mkMerge [
-        (mkIf (cfg != {}) { 
+        (mkIf (cfg != {}) {
           environment.etc."systemd/nspawn".source = mkIf (cfg != {}) (generateUnits' false "nspawn" units [] []);
         })
         {
@@ -123,7 +123,7 @@ in {
 
           # Workaround for https://github.com/NixOS/nixpkgs/pull/67232#issuecomment-531315437 and https://github.com/systemd/systemd/issues/13622
           # Once systemd fixes this upstream, we can re-enable -U
-          systemd.services."systemd-nspawn@".serviceConfig.ExecStart = [ 
+          systemd.services."systemd-nspawn@".serviceConfig.ExecStart = [
             ""  # deliberately empty. signals systemd to override the ExecStart
             # Only difference between upstream is that we do not pass the -U flag
             "${config.systemd.package}/bin/systemd-nspawn --quiet --keep-unit --boot --link-journal=try-guest --network-veth --settings=override --machine=%i"
diff --git a/nixos/modules/system/boot/systemd-unit-options.nix b/nixos/modules/system/boot/systemd-unit-options.nix
index c6dbb96951ae0..ac6fed440a266 100644
--- a/nixos/modules/system/boot/systemd-unit-options.nix
+++ b/nixos/modules/system/boot/systemd-unit-options.nix
@@ -379,6 +379,16 @@ in rec {
       '';
     };
 
+    listenDatagrams = mkOption {
+      default = [];
+      type = types.listOf types.str;
+      example = [ "0.0.0.0:993" "/run/my-socket" ];
+      description = ''
+        For each item in this list, a <literal>ListenDatagram</literal>
+        option in the <literal>[Socket]</literal> section will be created.
+      '';
+    };
+
     socketConfig = mkOption {
       default = {};
       example = { ListenStream = "/run/my-socket"; };
diff --git a/nixos/modules/system/boot/systemd.nix b/nixos/modules/system/boot/systemd.nix
index a8e51fc09014d..96304ff6cecb1 100644
--- a/nixos/modules/system/boot/systemd.nix
+++ b/nixos/modules/system/boot/systemd.nix
@@ -73,7 +73,7 @@ let
       "systemd-journald.service"
       "systemd-journal-flush.service"
       "systemd-journal-catalog-update.service"
-      "systemd-journald-audit.socket"
+      ] ++ (optional (!config.boot.isContainer) "systemd-journald-audit.socket") ++ [
       "systemd-journald-dev-log.socket"
       "syslog.socket"
 
@@ -81,10 +81,6 @@ let
       "systemd-coredump.socket"
       "systemd-coredump@.service"
 
-      # SysV init compatibility.
-      "systemd-initctl.socket"
-      "systemd-initctl.service"
-
       # Kernel module loading.
       "systemd-modules-load.service"
       "kmod-static-nodes.service"
@@ -101,7 +97,7 @@ let
       "dev-hugepages.mount"
       "dev-mqueue.mount"
       "sys-fs-fuse-connections.mount"
-      "sys-kernel-config.mount"
+      ] ++ (optional (!config.boot.isContainer) "sys-kernel-config.mount") ++ [
       "sys-kernel-debug.mount"
 
       # Maintaining state across reboots.
@@ -354,6 +350,7 @@ let
           [Socket]
           ${attrsToSection def.socketConfig}
           ${concatStringsSep "\n" (map (s: "ListenStream=${s}") def.listenStreams)}
+          ${concatStringsSep "\n" (map (s: "ListenDatagram=${s}") def.listenDatagrams)}
         '';
     };
 
@@ -749,6 +746,25 @@ in
       '';
     };
 
+    systemd.tmpfiles.packages = mkOption {
+      type = types.listOf types.package;
+      default = [];
+      example = literalExample "[ pkgs.lvm2 ]";
+      apply = map getLib;
+      description = ''
+        List of packages containing <command>systemd-tmpfiles</command> rules.
+
+        All files ending in .conf found in
+        <filename><replaceable>pkg</replaceable>/lib/tmpfiles.d</filename>
+        will be included.
+        If this folder does not exist or does not contain any files an error will be returned instead.
+
+        If a <filename>lib</filename> output is available, rules are searched there and only there.
+        If there is no <filename>lib</filename> output it will fall back to <filename>out</filename>
+        and if that does not exist either, the default output will be used.
+      '';
+    };
+
     systemd.user.units = mkOption {
       description = "Definition of systemd per-user units.";
       default = {};
@@ -818,6 +834,49 @@ in
       '';
     };
 
+    systemd.watchdog.device = mkOption {
+      type = types.nullOr types.path;
+      default = null;
+      example = "/dev/watchdog";
+      description = ''
+        The path to a hardware watchdog device which will be managed by systemd.
+        If not specified, systemd will default to /dev/watchdog.
+      '';
+    };
+
+    systemd.watchdog.runtimeTime = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      example = "30s";
+      description = ''
+        The amount of time which can elapse before a watchdog hardware device
+        will automatically reboot the system. Valid time units include "ms",
+        "s", "min", "h", "d", and "w".
+      '';
+    };
+
+    systemd.watchdog.rebootTime = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      example = "10m";
+      description = ''
+        The amount of time which can elapse after a reboot has been triggered
+        before a watchdog hardware device will automatically reboot the system.
+        Valid time units include "ms", "s", "min", "h", "d", and "w".
+      '';
+    };
+
+    systemd.watchdog.kexecTime = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      example = "10m";
+      description = ''
+        The amount of time which can elapse when kexec is being executed before
+        a watchdog hardware device will automatically reboot the system. This
+        option should only be enabled if reloadTime is also enabled. Valid
+        time units include "ms", "s", "min", "h", "d", and "w".
+      '';
+    };
   };
 
 
@@ -889,6 +948,19 @@ in
           DefaultIPAccounting=yes
         ''}
         DefaultLimitCORE=infinity
+        ${optionalString (config.systemd.watchdog.device != null) ''
+          WatchdogDevice=${config.systemd.watchdog.device}
+        ''}
+        ${optionalString (config.systemd.watchdog.runtimeTime != null) ''
+          RuntimeWatchdogSec=${config.systemd.watchdog.runtimeTime}
+        ''}
+        ${optionalString (config.systemd.watchdog.rebootTime != null) ''
+          RebootWatchdogSec=${config.systemd.watchdog.rebootTime}
+        ''}
+        ${optionalString (config.systemd.watchdog.kexecTime != null) ''
+          KExecWatchdogSec=${config.systemd.watchdog.kexecTime}
+        ''}
+
         ${config.systemd.extraConfig}
       '';
 
@@ -936,24 +1008,18 @@ in
       "sysctl.d/50-coredump.conf".source = "${systemd}/example/sysctl.d/50-coredump.conf";
       "sysctl.d/50-default.conf".source = "${systemd}/example/sysctl.d/50-default.conf";
 
-      "tmpfiles.d/00-nixos.conf".text = ''
-        # This file is created automatically and should not be modified.
-        # Please change the option ‘systemd.tmpfiles.rules’ instead.
-
-        ${concatStringsSep "\n" cfg.tmpfiles.rules}
-      '';
-
-      "tmpfiles.d/home.conf".source = "${systemd}/example/tmpfiles.d/home.conf";
-      "tmpfiles.d/journal-nocow.conf".source = "${systemd}/example/tmpfiles.d/journal-nocow.conf";
-      "tmpfiles.d/portables.conf".source = "${systemd}/example/tmpfiles.d/portables.conf";
-      "tmpfiles.d/static-nodes-permissions.conf".source = "${systemd}/example/tmpfiles.d/static-nodes-permissions.conf";
-      "tmpfiles.d/systemd.conf".source = "${systemd}/example/tmpfiles.d/systemd.conf";
-      "tmpfiles.d/systemd-nologin.conf".source = "${systemd}/example/tmpfiles.d/systemd-nologin.conf";
-      "tmpfiles.d/systemd-nspawn.conf".source = "${systemd}/example/tmpfiles.d/systemd-nspawn.conf";
-      "tmpfiles.d/systemd-tmp.conf".source = "${systemd}/example/tmpfiles.d/systemd-tmp.conf";
-      "tmpfiles.d/tmp.conf".source = "${systemd}/example/tmpfiles.d/tmp.conf";
-      "tmpfiles.d/var.conf".source = "${systemd}/example/tmpfiles.d/var.conf";
-      "tmpfiles.d/x11.conf".source = "${systemd}/example/tmpfiles.d/x11.conf";
+      "tmpfiles.d".source = pkgs.symlinkJoin {
+        name = "tmpfiles.d";
+        paths = map (p: p + "/lib/tmpfiles.d") cfg.tmpfiles.packages;
+        postBuild = ''
+          for i in $(cat $pathsPath); do
+            (test -d "$i" && test $(ls "$i"/*.conf | wc -l) -ge 1) || (
+              echo "ERROR: The path '$i' from systemd.tmpfiles.packages contains no *.conf files."
+              exit 1
+            )
+          done
+        '';
+      };
 
       "systemd/system-generators" = { source = hooks "generators" cfg.generators; };
       "systemd/system-shutdown" = { source = hooks "shutdown" cfg.shutdown; };
@@ -974,6 +1040,36 @@ in
         unitConfig.X-StopOnReconfiguration = true;
       };
 
+    systemd.tmpfiles.packages = [
+      # Default tmpfiles rules provided by systemd
+      (pkgs.runCommand "systemd-default-tmpfiles" {} ''
+        mkdir -p $out/lib/tmpfiles.d
+        cd $out/lib/tmpfiles.d
+
+        ln -s "${systemd}/example/tmpfiles.d/home.conf"
+        ln -s "${systemd}/example/tmpfiles.d/journal-nocow.conf"
+        ln -s "${systemd}/example/tmpfiles.d/static-nodes-permissions.conf"
+        ln -s "${systemd}/example/tmpfiles.d/systemd.conf"
+        ln -s "${systemd}/example/tmpfiles.d/systemd-nologin.conf"
+        ln -s "${systemd}/example/tmpfiles.d/systemd-nspawn.conf"
+        ln -s "${systemd}/example/tmpfiles.d/systemd-tmp.conf"
+        ln -s "${systemd}/example/tmpfiles.d/tmp.conf"
+        ln -s "${systemd}/example/tmpfiles.d/var.conf"
+        ln -s "${systemd}/example/tmpfiles.d/x11.conf"
+      '')
+      # User-specified tmpfiles rules
+      (pkgs.writeTextFile {
+        name = "nixos-tmpfiles.d";
+        destination = "/lib/tmpfiles.d/00-nixos.conf";
+        text = ''
+          # This file is created automatically and should not be modified.
+          # Please change the option ‘systemd.tmpfiles.rules’ instead.
+
+          ${concatStringsSep "\n" cfg.tmpfiles.rules}
+        '';
+      })
+    ];
+
     systemd.units =
          mapAttrs' (n: v: nameValuePair "${n}.path"    (pathToUnit    n v)) cfg.paths
       // mapAttrs' (n: v: nameValuePair "${n}.service" (serviceToUnit n v)) cfg.services
diff --git a/nixos/modules/system/boot/tmp.nix b/nixos/modules/system/boot/tmp.nix
index 5bf5e2eb2ec58..26eb172210e73 100644
--- a/nixos/modules/system/boot/tmp.nix
+++ b/nixos/modules/system/boot/tmp.nix
@@ -36,4 +36,4 @@ with lib;
 
   };
 
-}
\ No newline at end of file
+}
diff --git a/nixos/modules/system/etc/make-etc.sh b/nixos/modules/system/etc/make-etc.sh
index 1ca4c3046f0e5..aabfb5e88a655 100644
--- a/nixos/modules/system/etc/make-etc.sh
+++ b/nixos/modules/system/etc/make-etc.sh
@@ -23,7 +23,7 @@ for ((i = 0; i < ${#targets_[@]}; i++)); do
         done
 
     else
-        
+
         mkdir -p $out/etc/$(dirname $target)
         if ! [ -e $out/etc/$target ]; then
             ln -s $source $out/etc/$target
@@ -34,13 +34,12 @@ for ((i = 0; i < ${#targets_[@]}; i++)); do
                 exit 1
             fi
         fi
-        
+
         if test "${modes_[$i]}" != symlink; then
             echo "${modes_[$i]}"  > $out/etc/$target.mode
             echo "${users_[$i]}"  > $out/etc/$target.uid
             echo "${groups_[$i]}" > $out/etc/$target.gid
         fi
-        
+
     fi
 done
-
diff --git a/nixos/modules/tasks/auto-upgrade.nix b/nixos/modules/tasks/auto-upgrade.nix
index e70004e643e10..69385e5f2fe01 100644
--- a/nixos/modules/tasks/auto-upgrade.nix
+++ b/nixos/modules/tasks/auto-upgrade.nix
@@ -2,9 +2,9 @@
 
 with lib;
 
-let cfg = config.system.autoUpgrade; in
+let cfg = config.system.autoUpgrade;
 
-{
+in {
 
   options = {
 
@@ -21,6 +21,16 @@ let cfg = config.system.autoUpgrade; in
         '';
       };
 
+      flake = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        example = "github:kloenk/nix";
+        description = ''
+          The Flake URI of the NixOS configuration to build.
+          Disables the option <option>system.autoUpgrade.channel</option>.
+        '';
+      };
+
       channel = mkOption {
         type = types.nullOr types.str;
         default = null;
@@ -35,10 +45,20 @@ let cfg = config.system.autoUpgrade; in
 
       flags = mkOption {
         type = types.listOf types.str;
-        default = [];
-        example = [ "-I" "stuff=/home/alice/nixos-stuff" "--option" "extra-binary-caches" "http://my-cache.example.org/" ];
+        default = [ ];
+        example = [
+          "-I"
+          "stuff=/home/alice/nixos-stuff"
+          "--option"
+          "extra-binary-caches"
+          "http://my-cache.example.org/"
+        ];
         description = ''
           Any additional flags passed to <command>nixos-rebuild</command>.
+
+          If you are using flakes and use a local repo you can add
+          <command>[ "--update-input" "nixpkgs" "--commit-lock-file" ]</command>
+          to update nixpkgs.
         '';
       };
 
@@ -82,11 +102,23 @@ let cfg = config.system.autoUpgrade; in
 
   config = lib.mkIf cfg.enable {
 
-    system.autoUpgrade.flags =
-      [ "--no-build-output" ]
-      ++ (if cfg.channel == null
-          then [ "--upgrade" ]
-          else [ "-I" "nixpkgs=${cfg.channel}/nixexprs.tar.xz" ]);
+    assertions = [{
+      assertion = !((cfg.channel != null) && (cfg.flake != null));
+      message = ''
+        The options 'system.autoUpgrade.channels' and 'system.autoUpgrade.flake' cannot both be set.
+      '';
+    }];
+
+    system.autoUpgrade.flags = [ "--no-build-output" ]
+      ++ (if cfg.flake == null then
+        (if cfg.channel == null then
+          [ "--upgrade" ]
+        else [
+          "-I"
+          "nixpkgs=${cfg.channel}/nixexprs.tar.xz"
+        ])
+      else
+        [ "--flake ${cfg.flake}" ]);
 
     systemd.services.nixos-upgrade = {
       description = "NixOS Upgrade";
@@ -96,33 +128,41 @@ let cfg = config.system.autoUpgrade; in
 
       serviceConfig.Type = "oneshot";
 
-      environment = config.nix.envVars //
-        { inherit (config.environment.sessionVariables) NIX_PATH;
-          HOME = "/root";
-        } // config.networking.proxy.envVars;
+      environment = config.nix.envVars // {
+        inherit (config.environment.sessionVariables) NIX_PATH;
+        HOME = "/root";
+      } // config.networking.proxy.envVars;
 
-      path = with pkgs; [ coreutils gnutar xz.bin gzip gitMinimal config.nix.package.out ];
+      path = with pkgs; [
+        coreutils
+        gnutar
+        xz.bin
+        gzip
+        gitMinimal
+        config.nix.package.out
+      ];
 
       script = let
-          nixos-rebuild = "${config.system.build.nixos-rebuild}/bin/nixos-rebuild";
-        in
-        if cfg.allowReboot then ''
-            ${nixos-rebuild} boot ${toString cfg.flags}
-            booted="$(readlink /run/booted-system/{initrd,kernel,kernel-modules})"
-            built="$(readlink /nix/var/nix/profiles/system/{initrd,kernel,kernel-modules})"
-            if [ "$booted" = "$built" ]; then
-              ${nixos-rebuild} switch ${toString cfg.flags}
-            else
-              /run/current-system/sw/bin/shutdown -r +1
-            fi
-          '' else ''
-            ${nixos-rebuild} switch ${toString cfg.flags}
-        '';
+        nixos-rebuild =
+          "${config.system.build.nixos-rebuild}/bin/nixos-rebuild";
+      in if cfg.allowReboot then ''
+        ${nixos-rebuild} boot ${toString cfg.flags}
+        booted="$(readlink /run/booted-system/{initrd,kernel,kernel-modules})"
+        built="$(readlink /nix/var/nix/profiles/system/{initrd,kernel,kernel-modules})"
+        if [ "$booted" = "$built" ]; then
+          ${nixos-rebuild} switch ${toString cfg.flags}
+        else
+          /run/current-system/sw/bin/shutdown -r +1
+        fi
+      '' else ''
+        ${nixos-rebuild} switch ${toString cfg.flags}
+      '';
 
       startAt = cfg.dates;
     };
 
-    systemd.timers.nixos-upgrade.timerConfig.RandomizedDelaySec = cfg.randomizedDelaySec;
+    systemd.timers.nixos-upgrade.timerConfig.RandomizedDelaySec =
+      cfg.randomizedDelaySec;
 
   };
 
diff --git a/nixos/modules/tasks/bcache.nix b/nixos/modules/tasks/bcache.nix
index 8bab91c721fdc..41fb7664f3d17 100644
--- a/nixos/modules/tasks/bcache.nix
+++ b/nixos/modules/tasks/bcache.nix
@@ -8,6 +8,6 @@
 
   boot.initrd.extraUdevRulesCommands = ''
     cp -v ${pkgs.bcache-tools}/lib/udev/rules.d/*.rules $out/
-  ''; 
+  '';
 
 }
diff --git a/nixos/modules/tasks/encrypted-devices.nix b/nixos/modules/tasks/encrypted-devices.nix
index bc0933f16fec4..9c3f2d8fccb23 100644
--- a/nixos/modules/tasks/encrypted-devices.nix
+++ b/nixos/modules/tasks/encrypted-devices.nix
@@ -37,7 +37,14 @@ let
         default = null;
         example = "/mnt-root/root/.swapkey";
         type = types.nullOr types.str;
-        description = "File system location of keyfile. This unlocks the drive after the root has been mounted to <literal>/mnt-root</literal>.";
+        description = ''
+          Path to a keyfile used to unlock the backing encrypted
+          device. At the time this keyfile is accessed, the
+          <literal>neededForBoot</literal> filesystems (see
+          <literal>fileSystems.&lt;name?&gt;.neededForBoot</literal>)
+          will have been mounted under <literal>/mnt-root</literal>,
+          so the keyfile path should usually start with "/mnt-root/".
+        '';
       };
     };
   };
@@ -65,12 +72,16 @@ in
     boot.initrd = {
       luks = {
         devices =
-          builtins.listToAttrs (map (dev: { name = dev.encrypted.label; value = { device = dev.encrypted.blkDev; }; }) keylessEncDevs);
+          builtins.listToAttrs (map (dev: {
+            name = dev.encrypted.label;
+            value = { device = dev.encrypted.blkDev; };
+          }) keylessEncDevs);
         forceLuksSupportInInitrd = true;
       };
       postMountCommands =
-        concatMapStrings (dev: "cryptsetup luksOpen --key-file ${dev.encrypted.keyFile} ${dev.encrypted.blkDev} ${dev.encrypted.label};\n") keyedEncDevs;
+        concatMapStrings (dev:
+          "cryptsetup luksOpen --key-file ${dev.encrypted.keyFile} ${dev.encrypted.blkDev} ${dev.encrypted.label};\n"
+        ) keyedEncDevs;
     };
   };
 }
-
diff --git a/nixos/modules/tasks/filesystems/zfs.nix b/nixos/modules/tasks/filesystems/zfs.nix
index 71eed4d6f1a40..9ca7c6fb3431b 100644
--- a/nixos/modules/tasks/filesystems/zfs.nix
+++ b/nixos/modules/tasks/filesystems/zfs.nix
@@ -191,15 +191,14 @@ in
       };
 
       requestEncryptionCredentials = mkOption {
-        type = types.bool;
+        type = types.either types.bool (types.listOf types.str);
         default = true;
+        example = [ "tank" "data" ];
         description = ''
-          Request encryption keys or passwords for all encrypted datasets on import.
-          For root pools the encryption key can be supplied via both an
-          interactive prompt (keylocation=prompt) and from a file
-          (keylocation=file://). Note that for data pools the encryption key can
-          be only loaded from a file and not via interactive prompt since the
-          import is processed in a background systemd service.
+          If true on import encryption keys or passwords for all encrypted datasets
+          are requested. To only decrypt selected datasets supply a list of dataset
+          names instead. For root pools the encryption key can be supplied via both
+          an interactive prompt (keylocation=prompt) and from a file (keylocation=file://).
         '';
       };
 
@@ -421,9 +420,13 @@ in
               fi
               poolImported "${pool}" || poolImport "${pool}"  # Try one last time, e.g. to import a degraded pool.
             fi
-            ${lib.optionalString cfgZfs.requestEncryptionCredentials ''
-              zfs load-key -a
-            ''}
+            ${if isBool cfgZfs.requestEncryptionCredentials
+              then optionalString cfgZfs.requestEncryptionCredentials ''
+                zfs load-key -a
+              ''
+              else concatMapStrings (fs: ''
+                zfs load-key ${fs}
+              '') cfgZfs.requestEncryptionCredentials}
         '') rootPools));
       };
 
@@ -490,7 +493,11 @@ in
             description = "Import ZFS pool \"${pool}\"";
             # we need systemd-udev-settle until https://github.com/zfsonlinux/zfs/pull/4943 is merged
             requires = [ "systemd-udev-settle.service" ];
-            after = [ "systemd-udev-settle.service" "systemd-modules-load.service" ];
+            after = [
+              "systemd-udev-settle.service"
+              "systemd-modules-load.service"
+              "systemd-ask-password-console.service"
+            ];
             wantedBy = (getPoolMounts pool) ++ [ "local-fs.target" ];
             before = (getPoolMounts pool) ++ [ "local-fs.target" ];
             unitConfig = {
@@ -515,7 +522,27 @@ in
               done
               poolImported "${pool}" || poolImport "${pool}"  # Try one last time, e.g. to import a degraded pool.
               if poolImported "${pool}"; then
-                ${optionalString cfgZfs.requestEncryptionCredentials "\"${packages.zfsUser}/sbin/zfs\" load-key -r \"${pool}\""}
+                ${optionalString (if isBool cfgZfs.requestEncryptionCredentials
+                                  then cfgZfs.requestEncryptionCredentials
+                                  else cfgZfs.requestEncryptionCredentials != []) ''
+                  ${packages.zfsUser}/sbin/zfs list -rHo name,keylocation ${pool} | while IFS=$'\t' read ds kl; do
+                    (${optionalString (!isBool cfgZfs.requestEncryptionCredentials) ''
+                         if ! echo '${concatStringsSep "\n" cfgZfs.requestEncryptionCredentials}' | grep -qFx "$ds"; then
+                           continue
+                         fi
+                       ''}
+                    case "$kl" in
+                      none )
+                        ;;
+                      prompt )
+                        ${config.systemd.package}/bin/systemd-ask-password "Enter key for $ds:" | ${packages.zfsUser}/sbin/zfs load-key "$ds"
+                        ;;
+                      * )
+                        ${packages.zfsUser}/sbin/zfs load-key "$ds"
+                        ;;
+                    esac) < /dev/null # To protect while read ds kl in case anything reads stdin
+                  done
+                ''}
                 echo "Successfully imported ${pool}"
               else
                 exit 1
diff --git a/nixos/modules/tasks/lvm.nix b/nixos/modules/tasks/lvm.nix
index d56a8a2f63a8d..2c3cc4c5467dc 100644
--- a/nixos/modules/tasks/lvm.nix
+++ b/nixos/modules/tasks/lvm.nix
@@ -1,17 +1,70 @@
 { config, lib, pkgs, ... }:
 
 with lib;
+let
+  cfg = config.services.lvm;
+in {
+  options.services.lvm = {
+    package = mkOption {
+      type = types.package;
+      default = if cfg.dmeventd.enable then pkgs.lvm2_dmeventd else pkgs.lvm2;
+      internal = true;
+      defaultText = "pkgs.lvm2";
+      description = ''
+        This option allows you to override the LVM package that's used on the system
+        (udev rules, tmpfiles, systemd services).
+        Defaults to pkgs.lvm2, or pkgs.lvm2_dmeventd if dmeventd is enabled.
+      '';
+    };
+    dmeventd.enable = mkEnableOption "the LVM dmevent daemon";
+    boot.thin.enable = mkEnableOption "support for booting from ThinLVs";
+  };
 
-{
-
-  ###### implementation
+  config = mkMerge [
+    (mkIf (!config.boot.isContainer) {
+      systemd.tmpfiles.packages = [ cfg.package.out ];
+      environment.systemPackages = [ cfg.package ];
+      systemd.packages = [ cfg.package ];
 
-  config = mkIf (!config.boot.isContainer) {
+      # TODO: update once https://github.com/NixOS/nixpkgs/pull/93006 was merged
+      services.udev.packages = [ cfg.package.out ];
+    })
+    (mkIf cfg.dmeventd.enable {
+      systemd.sockets."dm-event".wantedBy = [ "sockets.target" ];
+      systemd.services."lvm2-monitor".wantedBy = [ "sysinit.target" ];
 
-    environment.systemPackages = [ pkgs.lvm2 ];
+      environment.etc."lvm/lvm.conf".text = ''
+        dmeventd/executable = "${cfg.package}/bin/dmeventd"
+      '';
+    })
+    (mkIf cfg.boot.thin.enable {
+      boot.initrd = {
+        kernelModules = [ "dm-snapshot" "dm-thin-pool" ];
 
-    services.udev.packages = [ pkgs.lvm2 ];
+        extraUtilsCommands = ''
+          copy_bin_and_libs ${pkgs.thin-provisioning-tools}/bin/pdata_tools
+          copy_bin_and_libs ${pkgs.thin-provisioning-tools}/bin/thin_check
+        '';
+      };
 
-  };
+      environment.etc."lvm/lvm.conf".text = ''
+        global/thin_check_executable = "${pkgs.thin-provisioning-tools}/bin/thin_check"
+      '';
+    })
+    (mkIf (cfg.dmeventd.enable || cfg.boot.thin.enable) {
+      boot.initrd.preLVMCommands = ''
+          mkdir -p /etc/lvm
+          cat << EOF >> /etc/lvm/lvm.conf
+          ${optionalString cfg.boot.thin.enable ''
+            global/thin_check_executable = "$(command -v thin_check)"
+          ''}
+          ${optionalString cfg.dmeventd.enable ''
+            dmeventd/executable = "$(command -v false)"
+            activation/monitoring = 0
+          ''}
+          EOF
+      '';
+    })
+  ];
 
 }
diff --git a/nixos/modules/tasks/network-interfaces-scripted.nix b/nixos/modules/tasks/network-interfaces-scripted.nix
index 2e87197176b68..9ba6ccfbe7167 100644
--- a/nixos/modules/tasks/network-interfaces-scripted.nix
+++ b/nixos/modules/tasks/network-interfaces-scripted.nix
@@ -253,8 +253,8 @@ let
 
         createTunDevice = i: nameValuePair "${i.name}-netdev"
           { description = "Virtual Network Interface ${i.name}";
-            bindsTo = [ "dev-net-tun.device" ];
-            after = [ "dev-net-tun.device" "network-pre.target" ];
+            bindsTo = optional (!config.boot.isContainer) "dev-net-tun.device";
+            after = optional (!config.boot.isContainer) "dev-net-tun.device" ++ [ "network-pre.target" ];
             wantedBy = [ "network-setup.service" (subsystemDevice i.name) ];
             partOf = [ "network-setup.service" ];
             before = [ "network-setup.service" ];
diff --git a/nixos/modules/tasks/network-interfaces.nix b/nixos/modules/tasks/network-interfaces.nix
index 78d6696694997..565868724ad53 100644
--- a/nixos/modules/tasks/network-interfaces.nix
+++ b/nixos/modules/tasks/network-interfaces.nix
@@ -408,6 +408,9 @@ in
         (this derives it from the machine-id that systemd generates) or
 
         <literal>head -c4 /dev/urandom | od -A none -t x4</literal>
+
+        The primary use case is to ensure when using ZFS that a pool isn't imported
+        accidentally on a wrong machine.
       '';
     };
 
diff --git a/nixos/modules/testing/test-instrumentation.nix b/nixos/modules/testing/test-instrumentation.nix
index 1baeab53b0c2d..30ffb12cbadee 100644
--- a/nixos/modules/testing/test-instrumentation.nix
+++ b/nixos/modules/testing/test-instrumentation.nix
@@ -1,22 +1,13 @@
 # This module allows the test driver to connect to the virtual machine
 # via a root shell attached to port 514.
 
-{ config, lib, pkgs, ... }:
+{ options, config, lib, pkgs, ... }:
 
 with lib;
 with import ../../lib/qemu-flags.nix { inherit pkgs; };
 
 {
 
-  # This option is a dummy that if used in conjunction with
-  # modules/virtualisation/qemu-vm.nix gets merged with the same option defined
-  # there and only is declared here because some modules use
-  # test-instrumentation.nix but not qemu-vm.nix.
-  #
-  # One particular example are the boot tests where we want instrumentation
-  # within the images but not other stuff like setting up 9p filesystems.
-  options.virtualisation.qemu = { };
-
   config = {
 
     systemd.services.backdoor =
@@ -55,7 +46,12 @@ 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 ];
+    # NOTE: optionalAttrs
+    #       test-instrumentation.nix appears to be used without qemu-vm.nix, so
+    #       we avoid defining consoles if not possible.
+    # TODO: refactor such that test-instrumentation can import qemu-vm
+    #       or declare virtualisation.qemu.console option in a module that's always imported
+    virtualisation = lib.optionalAttrs (options ? virtualisation.qemu.consoles) { qemu.consoles = [ qemuSerialDevice ]; };
 
     boot.initrd.preDeviceCommands =
       ''
diff --git a/nixos/modules/virtualisation/azure-image.nix b/nixos/modules/virtualisation/azure-image.nix
index 21fd58e5c9024..60fed3222ef3a 100644
--- a/nixos/modules/virtualisation/azure-image.nix
+++ b/nixos/modules/virtualisation/azure-image.nix
@@ -6,7 +6,7 @@ let
 in
 {
   imports = [ ./azure-common.nix ];
-  
+
   options = {
     virtualisation.azureImage.diskSize = mkOption {
       type = with types; int;
diff --git a/nixos/modules/virtualisation/containers.nix b/nixos/modules/virtualisation/containers.nix
index 7d184575640b6..3a6767d84a9bd 100644
--- a/nixos/modules/virtualisation/containers.nix
+++ b/nixos/modules/virtualisation/containers.nix
@@ -23,6 +23,15 @@ in
     maintainers = [] ++ lib.teams.podman.members;
   };
 
+
+  imports = [
+    (
+      lib.mkRemovedOptionModule
+      [ "virtualisation" "containers" "users" ]
+      "All users with `isNormalUser = true` set now get appropriate subuid/subgid mappings."
+    )
+  ];
+
   options.virtualisation.containers = {
 
     enable =
@@ -34,6 +43,25 @@ in
         '';
       };
 
+    containersConf = mkOption {
+      default = {};
+      description = "containers.conf configuration";
+      type = types.submodule {
+        options = {
+
+          extraConfig = mkOption {
+            type = types.lines;
+            default = "";
+            description = ''
+              Extra configuration that should be put in the containers.conf
+              configuration file
+            '';
+
+          };
+        };
+      };
+    };
+
     registries = {
       search = mkOption {
         type = types.listOf types.str;
@@ -80,43 +108,20 @@ in
       '';
     };
 
-    users = mkOption {
-      default = [];
-      type = types.listOf types.str;
-      description = ''
-        List of users to set up subuid/subgid mappings for.
-        This is a requirement for running rootless containers.
-      '';
-    };
-
   };
 
   config = lib.mkIf cfg.enable {
 
+    environment.etc."containers/containers.conf".text = ''
+      [network]
+      cni_plugin_dirs = ["${pkgs.cni-plugins}/bin/"]
+
+    '' + cfg.containersConf.extraConfig;
+
     environment.etc."containers/registries.conf".source = toTOML "registries.conf" {
       registries = lib.mapAttrs (n: v: { registries = v; }) cfg.registries;
     };
 
-    users.extraUsers = builtins.listToAttrs (
-      (
-        builtins.foldl' (
-          acc: user: {
-            values = acc.values ++ [
-              {
-                name = user;
-                value = {
-                  subUidRanges = [ { startUid = acc.offset; count = 65536; } ];
-                  subGidRanges = [ { startGid = acc.offset; count = 65536; } ];
-                };
-              }
-            ];
-            offset = acc.offset + 65536;
-          }
-        )
-        { values = []; offset = 100000; } (lib.unique cfg.users)
-      ).values
-    );
-
     environment.etc."containers/policy.json".source =
       if cfg.policy != {} then pkgs.writeText "policy.json" (builtins.toJSON cfg.policy)
       else copyFile "${pkgs.skopeo.src}/default-policy.json";
diff --git a/nixos/modules/virtualisation/cri-o.nix b/nixos/modules/virtualisation/cri-o.nix
index f267c97b17889..9c818eee73b67 100644
--- a/nixos/modules/virtualisation/cri-o.nix
+++ b/nixos/modules/virtualisation/cri-o.nix
@@ -85,7 +85,7 @@ in
 
     environment.etc."crictl.yaml".source = copyFile "${pkgs.cri-o-unwrapped.src}/crictl.yaml";
 
-    environment.etc."crio/crio.conf".text = ''
+    environment.etc."crio/crio.conf.d/00-default.conf".text = ''
       [crio]
       storage_driver = "${cfg.storageDriver}"
 
@@ -100,6 +100,7 @@ in
       cgroup_manager = "systemd"
       log_level = "${cfg.logLevel}"
       manage_ns_lifecycle = true
+      pinns_path = "${cfg.package}/bin/pinns"
 
       ${optionalString (cfg.runtime != null) ''
       default_runtime = "${cfg.runtime}"
@@ -109,6 +110,7 @@ in
     '';
 
     environment.etc."cni/net.d/10-crio-bridge.conf".source = copyFile "${pkgs.cri-o-unwrapped.src}/contrib/cni/10-crio-bridge.conf";
+    environment.etc."cni/net.d/99-loopback.conf".source = copyFile "${pkgs.cri-o-unwrapped.src}/contrib/cni/99-loopback.conf";
 
     # Enable common /etc/containers configuration
     virtualisation.containers.enable = true;
diff --git a/nixos/modules/virtualisation/docker.nix b/nixos/modules/virtualisation/docker.nix
index 7d196a46276ac..d87ada35a0ae4 100644
--- a/nixos/modules/virtualisation/docker.nix
+++ b/nixos/modules/virtualisation/docker.nix
@@ -149,6 +149,7 @@ in
   ###### implementation
 
   config = mkIf cfg.enable (mkMerge [{
+      boot.kernelModules = [ "bridge" "veth" ];
       environment.systemPackages = [ cfg.package ]
         ++ optional cfg.enableNvidia pkgs.nvidia-docker;
       users.groups.docker.gid = config.ids.gids.docker;
diff --git a/nixos/modules/virtualisation/libvirtd.nix b/nixos/modules/virtualisation/libvirtd.nix
index 43b5fcfa8fae5..1d6a9457dde41 100644
--- a/nixos/modules/virtualisation/libvirtd.nix
+++ b/nixos/modules/virtualisation/libvirtd.nix
@@ -265,8 +265,8 @@ in {
       restartIfChanged = false;
     };
 
-    systemd.sockets.libvirtd    .wantedBy = [ "sockets.target" ];
-    systemd.sockets.libvirtd-tcp.wantedBy = [ "sockets.target" ];
+    # https://libvirt.org/daemons.html#monolithic-systemd-integration
+    systemd.sockets.libvirtd.wantedBy = [ "sockets.target" ];
 
     security.polkit.extraConfig = ''
       polkit.addRule(function(action, subject) {
diff --git a/nixos/modules/virtualisation/podman.nix b/nixos/modules/virtualisation/podman.nix
index 652850bf5006b..e0e2f04e24c18 100644
--- a/nixos/modules/virtualisation/podman.nix
+++ b/nixos/modules/virtualisation/podman.nix
@@ -28,6 +28,10 @@ let
 
 in
 {
+  imports = [
+    (lib.mkRenamedOptionModule [ "virtualisation" "podman" "libpod" ] [ "virtualisation" "containers" "containersConf" ])
+  ];
+
   meta = {
     maintainers = lib.teams.podman.members;
   };
@@ -67,25 +71,6 @@ in
       '';
     };
 
-    libpod = mkOption {
-      default = {};
-      description = "Libpod configuration";
-      type = types.submodule {
-        options = {
-
-          extraConfig = mkOption {
-            type = types.lines;
-            default = "";
-            description = ''
-              Extra configuration that should be put in the libpod.conf
-              configuration file
-            '';
-
-          };
-        };
-      };
-    };
-
     package = lib.mkOption {
       type = types.package;
       default = podmanPackage;
@@ -103,11 +88,6 @@ in
     environment.systemPackages = [ cfg.package ]
       ++ lib.optional cfg.dockerCompat dockerCompat;
 
-    environment.etc."containers/libpod.conf".text = ''
-      cni_plugin_dir = ["${pkgs.cni-plugins}/bin/"]
-
-    '' + cfg.libpod.extraConfig;
-
     environment.etc."cni/net.d/87-podman-bridge.conflist".source = copyFile "${pkgs.podman-unwrapped.src}/cni/87-podman-bridge.conflist";
 
     # Enable common /etc/containers configuration
diff --git a/nixos/modules/virtualisation/qemu-vm.nix b/nixos/modules/virtualisation/qemu-vm.nix
index be06d6feb11f3..a650dd72c2a48 100644
--- a/nixos/modules/virtualisation/qemu-vm.nix
+++ b/nixos/modules/virtualisation/qemu-vm.nix
@@ -16,11 +16,6 @@ let
 
   qemu = config.system.build.qemu or pkgs.qemu_test;
 
-  vmName =
-    if config.networking.hostName == ""
-    then "noname"
-    else config.networking.hostName;
-
   cfg = config.virtualisation;
 
   consoles = lib.concatMapStringsSep " " (c: "console=${c}") cfg.qemu.consoles;
@@ -46,6 +41,13 @@ let
         description = "Extra options passed to device flag.";
       };
 
+      name = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description =
+          "A name for the drive. Must be unique in the drives list. Not passed to qemu.";
+      };
+
     };
 
   };
@@ -74,6 +76,32 @@ let
 
   drivesCmdLine = drives: concatStringsSep " " (imap1 driveCmdline drives);
 
+
+  # Creates a device name from a 1-based a numerical index, e.g.
+  # * `driveDeviceName 1` -> `/dev/vda`
+  # * `driveDeviceName 2` -> `/dev/vdb`
+  driveDeviceName = idx:
+    let letter = elemAt lowerChars (idx - 1);
+    in if cfg.qemu.diskInterface == "scsi" then
+      "/dev/sd${letter}"
+    else
+      "/dev/vd${letter}";
+
+  lookupDriveDeviceName = driveName: driveList:
+    (findSingle (drive: drive.name == driveName)
+      (throw "Drive ${driveName} not found")
+      (throw "Multiple drives named ${driveName}") driveList).device;
+
+  addDeviceNames =
+    imap1 (idx: drive: drive // { device = driveDeviceName idx; });
+
+  efiPrefix =
+    if (pkgs.stdenv.isi686 || pkgs.stdenv.isx86_64) then "${pkgs.OVMF.fd}/FV/OVMF"
+    else if pkgs.stdenv.isAarch64 then "${pkgs.OVMF.fd}/FV/AAVMF"
+    else throw "No EFI firmware available for platform";
+  efiFirmware = "${efiPrefix}_CODE.fd";
+  efiVarsDefault = "${efiPrefix}_VARS.fd";
+
   # Shell script to start the VM.
   startVM =
     ''
@@ -99,10 +127,14 @@ let
         # A writable boot disk can be booted from automatically.
         ${qemu}/bin/qemu-img create -f qcow2 -b ${bootDisk}/disk.img $TMPDIR/disk.img || exit 1
 
+        NIX_EFI_VARS=$(readlink -f ''${NIX_EFI_VARS:-${cfg.efiVars}})
+
         ${if cfg.useEFIBoot then ''
-          # VM needs a writable flash BIOS.
-          cp ${bootDisk}/bios.bin $TMPDIR || exit 1
-          chmod 0644 $TMPDIR/bios.bin || exit 1
+          # VM needs writable EFI vars
+          if ! test -e "$NIX_EFI_VARS"; then
+            cp ${bootDisk}/efi-vars.fd "$NIX_EFI_VARS" || exit 1
+            chmod 0644 "$NIX_EFI_VARS" || exit 1
+          fi
         '' else ''
         ''}
       '' else ''
@@ -119,7 +151,7 @@ let
 
       # Start QEMU.
       exec ${qemuBinary qemu} \
-          -name ${vmName} \
+          -name ${config.system.name} \
           -m ${toString config.virtualisation.memorySize} \
           -smp ${toString config.virtualisation.cores} \
           -device virtio-rng-pci \
@@ -139,6 +171,8 @@ let
 
   # Generate a hard disk image containing a /boot partition and GRUB
   # in the MBR.  Used when the `useBootLoader' option is set.
+  # Uses `runInLinuxVM` to create the image in a throwaway VM.
+  # See note [Disk layout with `useBootLoader`].
   # FIXME: use nixos/lib/make-disk-image.nix.
   bootDisk =
     pkgs.vmTools.runInLinuxVM (
@@ -147,21 +181,22 @@ let
             ''
               mkdir $out
               diskImage=$out/disk.img
-              bootFlash=$out/bios.bin
-              ${qemu}/bin/qemu-img create -f qcow2 $diskImage "40M"
+              ${qemu}/bin/qemu-img create -f qcow2 $diskImage "60M"
               ${if cfg.useEFIBoot then ''
-                cp ${pkgs.OVMF-CSM.fd}/FV/OVMF.fd $bootFlash
-                chmod 0644 $bootFlash
+                efiVars=$out/efi-vars.fd
+                cp ${efiVarsDefault} $efiVars
+                chmod 0644 $efiVars
               '' else ''
               ''}
             '';
           buildInputs = [ pkgs.utillinux ];
-          QEMU_OPTS = if cfg.useEFIBoot
-                      then "-pflash $out/bios.bin -nographic -serial pty"
-                      else "-nographic -serial pty";
+          QEMU_OPTS = "-nographic -serial stdio -monitor none"
+                      + lib.optionalString cfg.useEFIBoot (
+                        " -drive if=pflash,format=raw,unit=0,readonly=on,file=${efiFirmware}"
+                      + " -drive if=pflash,format=raw,unit=1,file=$efiVars");
         }
         ''
-          # Create a /boot EFI partition with 40M and arbitrary but fixed GUIDs for reproducibility
+          # Create a /boot EFI partition with 60M and arbitrary but fixed GUIDs for reproducibility
           ${pkgs.gptfdisk}/bin/sgdisk \
             --set-alignment=1 --new=1:34:2047 --change-name=1:BIOSBootPartition --typecode=1:ef02 \
             --set-alignment=512 --largest-new=2 --change-name=2:EFISystem --typecode=2:ef00 \
@@ -172,6 +207,19 @@ let
             --partition-guid=2:970C694F-AFD0-4B99-B750-CDB7A329AB6F \
             --hybrid 2 \
             --recompute-chs /dev/vda
+
+          ${optionalString (config.boot.loader.grub.device != "/dev/vda")
+            # In this throwaway VM, we only have the /dev/vda disk, but the
+            # actual VM described by `config` (used by `switch-to-configuration`
+            # below) may set `boot.loader.grub.device` to a different device
+            # that's nonexistent in the throwaway VM.
+            # Create a symlink for that device, so that the `grub-install`
+            # by `switch-to-configuration` will hit /dev/vda anyway.
+            ''
+              ln -s /dev/vda ${config.boot.loader.grub.device}
+            ''
+          }
+
           ${pkgs.dosfstools}/bin/mkfs.fat -F16 /dev/vda2
           export MTOOLS_SKIP_CHECK=1
           ${pkgs.mtools}/bin/mlabel -i /dev/vda2 ::boot
@@ -185,6 +233,10 @@ let
           mkdir /boot
           mount /dev/vda2 /boot
 
+          ${optionalString config.boot.loader.efi.canTouchEfiVariables ''
+            mount -t efivarfs efivarfs /sys/firmware/efi/efivars
+          ''}
+
           # This is needed for GRUB 0.97, which doesn't know about virtio devices.
           mkdir /boot/grub
           echo '(hd0) /dev/vda' > /boot/grub/device.map
@@ -237,7 +289,7 @@ in
 
     virtualisation.diskImage =
       mkOption {
-        default = "./${vmName}.qcow2";
+        default = "./${config.system.name}.qcow2";
         description =
           ''
             Path to the disk image containing the root filesystem.
@@ -396,6 +448,7 @@ in
         mkOption {
           type = types.listOf (types.submodule driveOpts);
           description = "Drives passed to qemu.";
+          apply = addDeviceNames;
         };
 
       diskInterface =
@@ -441,6 +494,16 @@ in
           '';
       };
 
+    virtualisation.efiVars =
+      mkOption {
+        default = "./${config.system.name}-efi-vars.fd";
+        description =
+          ''
+            Path to nvram image containing UEFI variables.  The will be created
+            on startup if it does not exist.
+          '';
+      };
+
     virtualisation.bios =
       mkOption {
         default = null;
@@ -457,7 +520,27 @@ in
 
   config = {
 
-    boot.loader.grub.device = mkVMOverride cfg.bootDevice;
+    # Note [Disk layout with `useBootLoader`]
+    #
+    # If `useBootLoader = true`, we configure 2 drives:
+    # `/dev/?da` for the root disk, and `/dev/?db` for the boot disk
+    # which has the `/boot` partition and the boot loader.
+    # Concretely:
+    #
+    # * The second drive's image `disk.img` is created in `bootDisk = ...`
+    #   using a throwaway VM. Note that there the disk is always `/dev/vda`,
+    #   even though in the final VM it will be at `/dev/*b`.
+    # * The disks are attached in `virtualisation.qemu.drives`.
+    #   Their order makes them appear as devices `a`, `b`, etc.
+    # * `fileSystems."/boot"` is adjusted to be on device `b`.
+
+    # If `useBootLoader`, GRUB goes to the second disk, see
+    # note [Disk layout with `useBootLoader`].
+    boot.loader.grub.device = mkVMOverride (
+      if cfg.useBootLoader
+        then driveDeviceName 2 # second disk
+        else cfg.bootDevice
+    );
 
     boot.initrd.extraUtilsCommands =
       ''
@@ -512,8 +595,7 @@ in
       optional cfg.writableStore "overlay"
       ++ optional (cfg.qemu.diskInterface == "scsi") "sym53c8xx";
 
-    virtualisation.bootDevice =
-      mkDefault (if cfg.qemu.diskInterface == "scsi" then "/dev/sda" else "/dev/vda");
+    virtualisation.bootDevice = mkDefault (driveDeviceName 1);
 
     virtualisation.pathsInNixDB = [ config.system.build.toplevel ];
 
@@ -531,7 +613,8 @@ in
         ''-append "$(cat ${config.system.build.toplevel}/kernel-params) init=${config.system.build.toplevel}/init regInfo=${regInfo}/registration ${consoles} $QEMU_KERNEL_PARAMS"''
       ])
       (mkIf cfg.useEFIBoot [
-        "-pflash $TMPDIR/bios.bin"
+        "-drive if=pflash,format=raw,unit=0,readonly,file=${efiFirmware}"
+        "-drive if=pflash,format=raw,unit=1,file=$NIX_EFI_VARS"
       ])
       (mkIf (cfg.bios != null) [
         "-bios ${cfg.bios}/bios.bin"
@@ -542,25 +625,22 @@ in
     ];
 
     virtualisation.qemu.drives = mkMerge [
+      [{
+        name = "root";
+        file = "$NIX_DISK_IMAGE";
+        driveExtraOpts.cache = "writeback";
+        driveExtraOpts.werror = "report";
+      }]
       (mkIf cfg.useBootLoader [
+        # The order of this list determines the device names, see
+        # note [Disk layout with `useBootLoader`].
         {
-          file = "$NIX_DISK_IMAGE";
-          driveExtraOpts.cache = "writeback";
-          driveExtraOpts.werror = "report";
-        }
-        {
+          name = "boot";
           file = "$TMPDIR/disk.img";
           driveExtraOpts.media = "disk";
           deviceExtraOpts.bootindex = "1";
         }
       ])
-      (mkIf (!cfg.useBootLoader) [
-        {
-          file = "$NIX_DISK_IMAGE";
-          driveExtraOpts.cache = "writeback";
-          driveExtraOpts.werror = "report";
-        }
-      ])
       (imap0 (idx: _: {
         file = "$(pwd)/empty${toString idx}.qcow2";
         driveExtraOpts.werror = "report";
@@ -608,9 +688,9 @@ in
           };
       } // optionalAttrs cfg.useBootLoader
       { "/boot" =
-          { device = "/dev/vdb2";
+          # see note [Disk layout with `useBootLoader`]
+          { device = "${lookupDriveDeviceName "boot" cfg.qemu.drives}2"; # 2 for e.g. `vdb2`, as created in `bootDisk`
             fsType = "vfat";
-            options = [ "ro" ];
             noCheck = true; # fsck fails on a r/o filesystem
           };
       });
@@ -627,7 +707,7 @@ in
       ''
         mkdir -p $out/bin
         ln -s ${config.system.build.toplevel} $out/system
-        ln -s ${pkgs.writeScript "run-nixos-vm" startVM} $out/bin/run-${vmName}-vm
+        ln -s ${pkgs.writeScript "run-nixos-vm" startVM} $out/bin/run-${config.system.name}-vm
       '';
 
     # When building a regular system configuration, override whatever
diff --git a/nixos/modules/virtualisation/railcar.nix b/nixos/modules/virtualisation/railcar.nix
index 12da1c75fc38c..3f188fc68e55f 100644
--- a/nixos/modules/virtualisation/railcar.nix
+++ b/nixos/modules/virtualisation/railcar.nix
@@ -29,9 +29,9 @@ let
         default = "none";
         description = ''
           The type of the filesystem to be mounted.
-          Linux: filesystem types supported by the kernel as listed in 
-          `/proc/filesystems` (e.g., "minix", "ext2", "ext3", "jfs", "xfs", 
-          "reiserfs", "msdos", "proc", "nfs", "iso9660"). For bind mounts 
+          Linux: filesystem types supported by the kernel as listed in
+          `/proc/filesystems` (e.g., "minix", "ext2", "ext3", "jfs", "xfs",
+          "reiserfs", "msdos", "proc", "nfs", "iso9660"). For bind mounts
           (when options include either bind or rbind), the type is a dummy,
           often "none" (not listed in /proc/filesystems).
         '';
@@ -45,9 +45,9 @@ let
         default = [ "bind" ];
         description = ''
           Mount options of the filesystem to be used.
-        
-          Support optoions are listed in the mount(8) man page. Note that 
-          both filesystem-independent and filesystem-specific options 
+
+          Support options are listed in the mount(8) man page. Note that
+          both filesystem-independent and filesystem-specific options
           are listed.
         '';
       };
diff --git a/nixos/modules/virtualisation/virtualbox-guest.nix b/nixos/modules/virtualisation/virtualbox-guest.nix
index 834b994e92d2a..486951983d303 100644
--- a/nixos/modules/virtualisation/virtualbox-guest.nix
+++ b/nixos/modules/virtualisation/virtualbox-guest.nix
@@ -68,7 +68,7 @@ in
         SUBSYSTEM=="misc", KERNEL=="vboxguest", TAG+="systemd"
       '';
   } (mkIf cfg.x11 {
-    services.xserver.videoDrivers = mkOverride 50 [ "virtualbox" "modesetting" ];
+    services.xserver.videoDrivers = mkOverride 50 [ "vmware" "virtualbox" "modesetting" ];
 
     services.xserver.config =
       ''
diff --git a/nixos/modules/virtualisation/virtualbox-image.nix b/nixos/modules/virtualisation/virtualbox-image.nix
index 788b4d9d9761d..fa580e8b42d6b 100644
--- a/nixos/modules/virtualisation/virtualbox-image.nix
+++ b/nixos/modules/virtualisation/virtualbox-image.nix
@@ -58,6 +58,35 @@ in {
           Run <literal>VBoxManage modifyvm --help</literal> to see more options.
         '';
      };
+      extraDisk = mkOption {
+        description = ''
+          Optional extra disk/hdd configuration.
+          The disk will be an 'ext4' partition on a separate VMDK file.
+        '';
+        default = null;
+        example = {
+          label = "storage";
+          mountPoint = "/home/demo/storage";
+          size = 100 * 1024;
+        };
+        type = types.nullOr (types.submodule {
+          options = {
+            size = mkOption {
+              type = types.int;
+              description = "Size in MiB";
+            };
+            label = mkOption {
+              type = types.str;
+              default = "vm-extra-storage";
+              description = "Label for the disk partition";
+            };
+            mountPoint = mkOption {
+              type = types.str;
+              description = "Path where to mount this disk.";
+            };
+          };
+        });
+      };
     };
   };
 
@@ -72,6 +101,7 @@ in {
         audiocontroller = "ac97";
         audio = "alsa";
         audioout = "on";
+        graphicscontroller = "vmsvga";
         rtcuseutc = "on";
         usb = "on";
         usbehci = "on";
@@ -95,6 +125,20 @@ in {
           echo "creating VirtualBox pass-through disk wrapper (no copying involved)..."
           VBoxManage internalcommands createrawvmdk -filename disk.vmdk -rawdisk $diskImage
 
+          ${optionalString (cfg.extraDisk != null) ''
+            echo "creating extra disk: data-disk.raw"
+            dataDiskImage=data-disk.raw
+            truncate -s ${toString cfg.extraDisk.size}M $dataDiskImage
+
+            parted --script $dataDiskImage -- \
+              mklabel msdos \
+              mkpart primary ext4 1MiB -1
+            eval $(partx $dataDiskImage -o START,SECTORS --nr 1 --pairs)
+            mkfs.ext4 -F -L ${cfg.extraDisk.label} $dataDiskImage -E offset=$(sectorsToBytes $START) $(sectorsToKilobytes $SECTORS)K
+            echo "creating extra disk: data-disk.vmdk"
+            VBoxManage internalcommands createrawvmdk -filename data-disk.vmdk -rawdisk $dataDiskImage
+          ''}
+
           echo "creating VirtualBox VM..."
           vmName="${cfg.vmName}";
           VBoxManage createvm --name "$vmName" --register \
@@ -105,6 +149,10 @@ in {
           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
+          ${optionalString (cfg.extraDisk != null) ''
+            VBoxManage storageattach "$vmName" --storagectl SATA --port 1 --device 0 --type hdd \
+            --medium data-disk.vmdk
+          ''}
 
           echo "exporting VirtualBox VM..."
           mkdir -p $out
@@ -118,11 +166,19 @@ in {
         '';
     };
 
-    fileSystems."/" = {
-      device = "/dev/disk/by-label/nixos";
-      autoResize = true;
-      fsType = "ext4";
-    };
+    fileSystems = {
+      "/" = {
+        device = "/dev/disk/by-label/nixos";
+        autoResize = true;
+        fsType = "ext4";
+      };
+    } // (lib.optionalAttrs (cfg.extraDisk != null) {
+      ${cfg.extraDisk.mountPoint} = {
+        device = "/dev/disk/by-label/" + cfg.extraDisk.label;
+        autoResize = true;
+        fsType = "ext4";
+      };
+    });
 
     boot.growPartition = true;
     boot.loader.grub.device = "/dev/sda";
diff --git a/nixos/release.nix b/nixos/release.nix
index cf16986b21360..1f5c158126955 100644
--- a/nixos/release.nix
+++ b/nixos/release.nix
@@ -310,6 +310,11 @@ in rec {
         services.xserver.desktopManager.gnome3.enable = true;
       });
 
+    pantheon = makeClosure ({ ... }:
+      { services.xserver.enable = true;
+        services.xserver.desktopManager.pantheon.enable = true;
+      });
+
     # Linux/Apache/PostgreSQL/PHP stack.
     lapp = makeClosure ({ pkgs, ... }:
       { services.httpd.enable = true;
diff --git a/nixos/tests/acme.nix b/nixos/tests/acme.nix
index fc41dc1eb5ff7..a81884737213e 100644
--- a/nixos/tests/acme.nix
+++ b/nixos/tests/acme.nix
@@ -48,10 +48,9 @@ in import ./make-test-python.nix ({ lib, ... }: {
       security.acme.certs."standalone.test" = {
         webroot = "/var/lib/acme/acme-challenges";
       };
-      systemd.targets."acme-finished-standalone.test" = {};
-      systemd.services."acme-standalone.test" = {
-        wants = [ "acme-finished-standalone.test.target" ];
-        before = [ "acme-finished-standalone.test.target" ];
+      systemd.targets."acme-finished-standalone.test" = {
+        after = [ "acme-standalone.test.service" ];
+        wantedBy = [ "acme-standalone.test.service" ];
       };
       services.nginx.enable = true;
       services.nginx.virtualHosts."standalone.test" = {
@@ -68,11 +67,9 @@ in import ./make-test-python.nix ({ lib, ... }: {
 
       # A target remains active. Use this to probe the fact that
       # a service fired eventhough it is not RemainAfterExit
-      systemd.targets."acme-finished-a.example.test" = {};
-      systemd.services."acme-a.example.test" = {
-        wants = [ "acme-finished-a.example.test.target" ];
-        before = [ "acme-finished-a.example.test.target" ];
-        after = [ "nginx.service" ];
+      systemd.targets."acme-finished-a.example.test" = {
+        after = [ "acme-a.example.test.service" ];
+        wantedBy = [ "acme-a.example.test.service" ];
       };
 
       services.nginx.enable = true;
@@ -89,11 +86,9 @@ in import ./make-test-python.nix ({ lib, ... }: {
       security.acme.server = "https://acme.test/dir";
 
       specialisation.second-cert.configuration = {pkgs, ...}: {
-        systemd.targets."acme-finished-b.example.test" = {};
-        systemd.services."acme-b.example.test" = {
-          wants = [ "acme-finished-b.example.test.target" ];
-          before = [ "acme-finished-b.example.test.target" ];
-          after = [ "nginx.service" ];
+        systemd.targets."acme-finished-b.example.test" = {
+          after = [ "acme-b.example.test.service" ];
+          wantedBy = [ "acme-b.example.test.service" ];
         };
         services.nginx.virtualHosts."b.example.test" = {
           enableACME = true;
@@ -104,6 +99,7 @@ in import ./make-test-python.nix ({ lib, ... }: {
           '';
         };
       };
+
       specialisation.dns-01.configuration = {pkgs, config, nodes, lib, ...}: {
         security.acme.certs."example.test" = {
           domain = "*.example.test";
@@ -115,10 +111,12 @@ in import ./make-test-python.nix ({ lib, ... }: {
           user = config.services.nginx.user;
           group = config.services.nginx.group;
         };
-        systemd.targets."acme-finished-example.test" = {};
+        systemd.targets."acme-finished-example.test" = {
+          after = [ "acme-example.test.service" ];
+          wantedBy = [ "acme-example.test.service" ];
+        };
         systemd.services."acme-example.test" = {
-          wants = [ "acme-finished-example.test.target" ];
-          before = [ "acme-finished-example.test.target" "nginx.service" ];
+          before = [ "nginx.service" ];
           wantedBy = [ "nginx.service" ];
         };
         services.nginx.virtualHosts."c.example.test" = {
@@ -132,6 +130,26 @@ in import ./make-test-python.nix ({ lib, ... }: {
           '';
         };
       };
+
+      # When nginx depends on a service that is slow to start up, requesting used to fail
+      # certificates fail.  Reproducer for https://github.com/NixOS/nixpkgs/issues/81842
+      specialisation.slow-startup.configuration = { pkgs, config, nodes, lib, ...}: {
+        systemd.services.my-slow-service = {
+          wantedBy = [ "multi-user.target" "nginx.service" ];
+          before = [ "nginx.service" ];
+          preStart = "sleep 5";
+          script = "${pkgs.python3}/bin/python -m http.server";
+        };
+        systemd.targets."acme-finished-d.example.com" = {
+          after = [ "acme-d.example.com.service" ];
+          wantedBy = [ "acme-d.example.com.service" ];
+        };
+        services.nginx.virtualHosts."d.example.com" = {
+          forceSSL = true;
+          enableACME = true;
+          locations."/".proxyPass = "http://localhost:8000";
+        };
+      };
     };
 
     client = {nodes, lib, ...}: {
@@ -207,5 +225,15 @@ in import ./make-test-python.nix ({ lib, ... }: {
           client.succeed(
               "curl --cacert /tmp/ca.crt https://c.example.test/ | grep -qF 'hello world'"
           )
+
+      with subtest("Can request certificate of nginx when startup is delayed"):
+          webserver.succeed(
+              "${switchToNewServer}"
+          )
+          webserver.succeed(
+              "/run/current-system/specialisation/slow-startup/bin/switch-to-configuration test"
+          )
+          webserver.wait_for_unit("acme-finished-d.example.com.target")
+          client.succeed("curl --cacert /tmp/ca.crt https://d.example.com/")
     '';
 })
diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix
index 7056d414e9e91..0ce5f89b27c75 100644
--- a/nixos/tests/all-tests.nix
+++ b/nixos/tests/all-tests.nix
@@ -32,7 +32,10 @@ in
   beanstalkd = handleTest ./beanstalkd.nix {};
   bees = handleTest ./bees.nix {};
   bind = handleTest ./bind.nix {};
+  bitcoind = handleTest ./bitcoind.nix {};
   bittorrent = handleTest ./bittorrent.nix {};
+  bitwarden = handleTest ./bitwarden.nix {};
+  blockbook-frontend = handleTest ./blockbook-frontend.nix {};
   buildkite-agents = handleTest ./buildkite-agents.nix {};
   boot = handleTestOn ["x86_64-linux"] ./boot.nix {}; # syslinux is unsupported on aarch64
   boot-stage1 = handleTest ./boot-stage1.nix {};
@@ -65,6 +68,7 @@ in
   containers-portforward = handleTest ./containers-portforward.nix {};
   containers-restart_networking = handleTest ./containers-restart_networking.nix {};
   containers-tmpfs = handleTest ./containers-tmpfs.nix {};
+  convos = handleTest ./convos.nix {};
   corerad = handleTest ./corerad.nix {};
   couchdb = handleTest ./couchdb.nix {};
   deluge = handleTest ./deluge.nix {};
@@ -98,6 +102,7 @@ in
   ferm = handleTest ./ferm.nix {};
   firefox = handleTest ./firefox.nix {};
   firefox-esr = handleTest ./firefox.nix { esr = true; };
+  firejail = handleTest ./firejail.nix {};
   firewall = handleTest ./firewall.nix {};
   fish = handleTest ./fish.nix {};
   flannel = handleTestOn ["x86_64-linux"] ./flannel.nix {};
@@ -124,6 +129,7 @@ in
   grafana = handleTest ./grafana.nix {};
   graphite = handleTest ./graphite.nix {};
   graylog = handleTest ./graylog.nix {};
+  grub = handleTest ./grub.nix {};
   gvisor = handleTest ./gvisor.nix {};
   hadoop.hdfs = handleTestOn [ "x86_64-linux" ] ./hadoop/hdfs.nix {};
   hadoop.yarn = handleTestOn [ "x86_64-linux" ] ./hadoop/yarn.nix {};
@@ -148,6 +154,7 @@ in
   incron = handleTest ./incron.nix {};
   influxdb = handleTest ./influxdb.nix {};
   initrd-network-ssh = handleTest ./initrd-network-ssh {};
+  initrd-network-openvpn = handleTest ./initrd-network-openvpn {};
   initrdNetwork = handleTest ./initrd-network.nix {};
   installer = handleTest ./installer.nix {};
   iodine = handleTest ./iodine.nix {};
@@ -157,6 +164,7 @@ in
   jellyfin = handleTest ./jellyfin.nix {};
   jenkins = handleTest ./jenkins.nix {};
   jirafeau = handleTest ./jirafeau.nix {};
+  jitsi-meet = handleTest ./jitsi-meet.nix {};
   k3s = handleTest ./k3s.nix {};
   kafka = handleTest ./kafka.nix {};
   keepalived = handleTest ./keepalived.nix {};
@@ -188,12 +196,10 @@ in
   mailcatcher = handleTest ./mailcatcher.nix {};
   mariadb-galera-mariabackup = handleTest ./mysql/mariadb-galera-mariabackup.nix {};
   mariadb-galera-rsync = handleTest ./mysql/mariadb-galera-rsync.nix {};
-  mathics = handleTest ./mathics.nix {};
   matomo = handleTest ./matomo.nix {};
   matrix-synapse = handleTest ./matrix-synapse.nix {};
   mediawiki = handleTest ./mediawiki.nix {};
   memcached = handleTest ./memcached.nix {};
-  mesos = handleTest ./mesos.nix {};
   metabase = handleTest ./metabase.nix {};
   miniflux = handleTest ./miniflux.nix {};
   minio = handleTest ./minio.nix {};
@@ -217,6 +223,7 @@ in
   nat.firewall = handleTest ./nat.nix { withFirewall = true; };
   nat.firewall-conntrack = handleTest ./nat.nix { withFirewall = true; withConntrackHelpers = true; };
   nat.standalone = handleTest ./nat.nix { withFirewall = false; };
+  ncdns = handleTest ./ncdns.nix {};
   ndppd = handleTest ./ndppd.nix {};
   neo4j = handleTest ./neo4j.nix {};
   specialisation = handleTest ./specialisation.nix {};
@@ -261,9 +268,12 @@ in
   pgjwt = handleTest ./pgjwt.nix {};
   pgmanage = handleTest ./pgmanage.nix {};
   php = handleTest ./php {};
+  pinnwand = handleTest ./pinnwand.nix {};
   plasma5 = handleTest ./plasma5.nix {};
   plotinus = handleTest ./plotinus.nix {};
-  podman = handleTest ./podman.nix {};
+  podman = handleTestOn ["x86_64-linux"] ./podman.nix {};
+  postfix = handleTest ./postfix.nix {};
+  postfix-raise-smtpd-tls-security-level = handleTest ./postfix-raise-smtpd-tls-security-level.nix {};
   postgis = handleTest ./postgis.nix {};
   postgresql = handleTest ./postgresql.nix {};
   postgresql-wal-receiver = handleTest ./postgresql-wal-receiver.nix {};
@@ -277,6 +287,7 @@ in
   prosody = handleTest ./xmpp/prosody.nix {};
   prosodyMysql = handleTest ./xmpp/prosody-mysql.nix {};
   proxy = handleTest ./proxy.nix {};
+  pt2-clone = handleTest ./pt2-clone.nix {};
   qboot = handleTestOn ["x86_64-linux" "i686-linux"] ./qboot.nix {};
   quagga = handleTest ./quagga.nix {};
   quorum = handleTest ./quorum.nix {};
@@ -296,27 +307,31 @@ in
   sanoid = handleTest ./sanoid.nix {};
   sddm = handleTest ./sddm.nix {};
   service-runner = handleTest ./service-runner.nix {};
+  shattered-pixel-dungeon = handleTest ./shattered-pixel-dungeon.nix {};
   shiori = handleTest ./shiori.nix {};
   signal-desktop = handleTest ./signal-desktop.nix {};
   simple = handleTest ./simple.nix {};
   slurm = handleTest ./slurm.nix {};
   smokeping = handleTest ./smokeping.nix {};
+  snapcast = handleTest ./snapcast.nix {};
   snapper = handleTest ./snapper.nix {};
   sogo = handleTest ./sogo.nix {};
   solr = handleTest ./solr.nix {};
   spacecookie = handleTest ./spacecookie.nix {};
   spike = handleTest ./spike.nix {};
   sonarr = handleTest ./sonarr.nix {};
+  sslh = handleTest ./sslh.nix {};
   strongswan-swanctl = handleTest ./strongswan-swanctl.nix {};
   sudo = handleTest ./sudo.nix {};
   switchTest = handleTest ./switch-test.nix {};
   sympa = handleTest ./sympa.nix {};
+  syncthing = handleTest ./syncthing.nix {};
   syncthing-init = handleTest ./syncthing-init.nix {};
   syncthing-relay = handleTest ./syncthing-relay.nix {};
   systemd = handleTest ./systemd.nix {};
   systemd-analyze = handleTest ./systemd-analyze.nix {};
   systemd-binfmt = handleTestOn ["x86_64-linux"] ./systemd-binfmt.nix {};
-  systemd-boot = handleTestOn ["x86_64-linux"] ./systemd-boot.nix {};
+  systemd-boot = handleTest ./systemd-boot.nix {};
   systemd-confinement = handleTest ./systemd-confinement.nix {};
   systemd-timesyncd = handleTest ./systemd-timesyncd.nix {};
   systemd-networkd-vrf = handleTest ./systemd-networkd-vrf.nix {};
@@ -358,6 +373,7 @@ in
   yabar = handleTest ./yabar.nix {};
   yggdrasil = handleTest ./yggdrasil.nix {};
   zfs = handleTest ./zfs.nix {};
+  zigbee2mqtt = handleTest ./zigbee2mqtt.nix {};
   zoneminder = handleTest ./zoneminder.nix {};
   zookeeper = handleTest ./zookeeper.nix {};
   zsh-history = handleTest ./zsh-history.nix {};
diff --git a/nixos/tests/bcachefs.nix b/nixos/tests/bcachefs.nix
index 0541e5803225a..3f116d7df92aa 100644
--- a/nixos/tests/bcachefs.nix
+++ b/nixos/tests/bcachefs.nix
@@ -13,7 +13,7 @@ import ./make-test-python.nix ({ pkgs, ... }: {
     machine.succeed("modprobe bcachefs")
     machine.succeed("bcachefs version")
     machine.succeed("ls /dev")
-    
+
     machine.succeed(
         "mkdir /tmp/mnt",
         "udevadm settle",
diff --git a/nixos/tests/bitcoind.nix b/nixos/tests/bitcoind.nix
new file mode 100644
index 0000000000000..09f3e4a6ec078
--- /dev/null
+++ b/nixos/tests/bitcoind.nix
@@ -0,0 +1,46 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "bitcoind";
+  meta = with pkgs.stdenv.lib; {
+    maintainers = with maintainers; [ _1000101 ];
+  };
+
+  machine = { ... }: {
+    services.bitcoind."mainnet" = {
+      enable = true;
+      rpc = {
+        port = 8332;
+        users.rpc.passwordHMAC = "acc2374e5f9ba9e62a5204d3686616cf$53abdba5e67a9005be6a27ca03a93ce09e58854bc2b871523a0d239a72968033";
+        users.rpc2.passwordHMAC = "1495e4a3ad108187576c68f7f9b5ddc5$accce0881c74aa01bb8960ff3bdbd39f607fd33178147679e055a4ac35f53225";
+      };
+    };
+    services.bitcoind."testnet" = {
+      enable = true;
+      configFile = "/test.blank";
+      testnet = true;
+      rpc = {
+        port = 18332;
+      };
+      extraCmdlineOptions = [ "-rpcuser=rpc" "-rpcpassword=rpc" "-rpcauth=rpc2:1495e4a3ad108187576c68f7f9b5ddc5$accce0881c74aa01bb8960ff3bdbd39f607fd33178147679e055a4ac35f53225" ];
+    };
+  };
+
+  testScript = ''
+    start_all()
+
+    machine.wait_for_unit("bitcoind-mainnet.service")
+    machine.wait_for_unit("bitcoind-testnet.service")
+
+    machine.wait_until_succeeds(
+        'curl --user rpc:rpc --data-binary \'{"jsonrpc": "1.0", "id":"curltest", "method": "getblockchaininfo", "params": [] }\' -H \'content-type: text/plain;\' localhost:8332 |  grep \'"chain":"main"\' '
+    )
+    machine.wait_until_succeeds(
+        'curl --user rpc2:rpc2 --data-binary \'{"jsonrpc": "1.0", "id":"curltest", "method": "getblockchaininfo", "params": [] }\' -H \'content-type: text/plain;\' localhost:8332 |  grep \'"chain":"main"\' '
+    )
+    machine.wait_until_succeeds(
+        'curl --user rpc:rpc --data-binary \'{"jsonrpc": "1.0", "id":"curltest", "method": "getblockchaininfo", "params": [] }\' -H \'content-type: text/plain;\' localhost:18332 |  grep \'"chain":"test"\' '
+    )
+    machine.wait_until_succeeds(
+        'curl --user rpc2:rpc2 --data-binary \'{"jsonrpc": "1.0", "id":"curltest", "method": "getblockchaininfo", "params": [] }\' -H \'content-type: text/plain;\' localhost:18332 |  grep \'"chain":"test"\' '
+    )
+  '';
+})
diff --git a/nixos/tests/bittorrent.nix b/nixos/tests/bittorrent.nix
index 0a97d5556a269..c195b60cd5695 100644
--- a/nixos/tests/bittorrent.nix
+++ b/nixos/tests/bittorrent.nix
@@ -19,6 +19,7 @@ let
   externalClient2Address = "80.100.100.2";
   externalTrackerAddress = "80.100.100.3";
 
+  download-dir = "/var/lib/transmission/Downloads";
   transmissionConfig = { ... }: {
     environment.systemPackages = [ pkgs.transmission ];
     services.transmission = {
@@ -26,6 +27,7 @@ let
       settings = {
         dht-enabled = false;
         message-level = 3;
+        inherit download-dir;
       };
     };
   };
@@ -117,12 +119,12 @@ in
       router.wait_for_unit("miniupnpd")
 
       # Create the torrent.
-      tracker.succeed("mkdir /tmp/data")
+      tracker.succeed("mkdir ${download-dir}/data")
       tracker.succeed(
-          "cp ${file} /tmp/data/test.tar.bz2"
+          "cp ${file} ${download-dir}/data/test.tar.bz2"
       )
       tracker.succeed(
-          "transmission-create /tmp/data/test.tar.bz2 --private --tracker http://${externalTrackerAddress}:6969/announce --outfile /tmp/test.torrent"
+          "transmission-create ${download-dir}/data/test.tar.bz2 --private --tracker http://${externalTrackerAddress}:6969/announce --outfile /tmp/test.torrent"
       )
       tracker.succeed("chmod 644 /tmp/test.torrent")
 
@@ -133,18 +135,16 @@ in
 
       # Start the initial seeder.
       tracker.succeed(
-          "transmission-remote --add /tmp/test.torrent --no-portmap --no-dht --download-dir /tmp/data"
+          "transmission-remote --add /tmp/test.torrent --no-portmap --no-dht --download-dir ${download-dir}/data"
       )
 
       # Now we should be able to download from the client behind the NAT.
       tracker.wait_for_unit("httpd")
       client1.wait_for_unit("network-online.target")
+      client1.succeed("transmission-remote --add http://${externalTrackerAddress}/test.torrent >&2 &")
+      client1.wait_for_file("${download-dir}/test.tar.bz2")
       client1.succeed(
-          "transmission-remote --add http://${externalTrackerAddress}/test.torrent --download-dir /tmp >&2 &"
-      )
-      client1.wait_for_file("/tmp/test.tar.bz2")
-      client1.succeed(
-          "cmp /tmp/test.tar.bz2 ${file}"
+          "cmp ${download-dir}/test.tar.bz2 ${file}"
       )
 
       # Bring down the initial seeder.
@@ -154,11 +154,11 @@ in
       # the first client created a NAT hole in the router.
       client2.wait_for_unit("network-online.target")
       client2.succeed(
-          "transmission-remote --add http://${externalTrackerAddress}/test.torrent --no-portmap --no-dht --download-dir /tmp >&2 &"
+          "transmission-remote --add http://${externalTrackerAddress}/test.torrent --no-portmap --no-dht >&2 &"
       )
-      client2.wait_for_file("/tmp/test.tar.bz2")
+      client2.wait_for_file("${download-dir}/test.tar.bz2")
       client2.succeed(
-          "cmp /tmp/test.tar.bz2 ${file}"
+          "cmp ${download-dir}/test.tar.bz2 ${file}"
       )
     '';
 })
diff --git a/nixos/tests/bitwarden.nix b/nixos/tests/bitwarden.nix
new file mode 100644
index 0000000000000..a47c77cec2137
--- /dev/null
+++ b/nixos/tests/bitwarden.nix
@@ -0,0 +1,188 @@
+{ system ? builtins.currentSystem
+, config ? { }
+, pkgs ? import ../.. { inherit system config; }
+}:
+
+# These tests will:
+#  * Set up a bitwarden-rs server
+#  * Have Firefox use the web vault to create an account, log in, and save a password to the valut
+#  * Have the bw cli log in and read that password from the vault
+#
+# Note that Firefox must be on the same machine as the server for WebCrypto APIs to be available (or HTTPS must be configured)
+#
+# The same tests should work without modification on the official bitwarden server, if we ever package that.
+
+with import ../lib/testing-python.nix { inherit system pkgs; };
+with pkgs.lib;
+let
+  backends = [ "sqlite" "mysql" "postgresql" ];
+
+  dbPassword = "please_dont_hack";
+
+  userEmail = "meow@example.com";
+  userPassword = "also_super_secret_ZJWpBKZi668QGt"; # Must be complex to avoid interstitial warning on the signup page
+
+  storedPassword = "seeeecret";
+
+  makeBitwardenTest = backend: makeTest {
+    name = "bitwarden_rs-${backend}";
+    meta = {
+      maintainers = with pkgs.stdenv.lib.maintainers; [ jjjollyjim ];
+    };
+
+    nodes = {
+      server = { pkgs, ... }:
+        let backendConfig = {
+          mysql = {
+            services.mysql = {
+              enable = true;
+              initialScript = pkgs.writeText "mysql-init.sql" ''
+                CREATE DATABASE bitwarden;
+                CREATE USER 'bitwardenuser'@'localhost' IDENTIFIED BY '${dbPassword}';
+                GRANT ALL ON `bitwarden`.* TO 'bitwardenuser'@'localhost';
+                FLUSH PRIVILEGES;
+              '';
+              package = pkgs.mysql;
+            };
+
+            services.bitwarden_rs.config.databaseUrl = "mysql://bitwardenuser:${dbPassword}@localhost/bitwarden";
+
+            systemd.services.bitwarden_rs.after = [ "mysql.service" ];
+          };
+
+          postgresql = {
+            services.postgresql = {
+              enable = true;
+              initialScript = pkgs.writeText "postgresql-init.sql" ''
+                CREATE DATABASE bitwarden;
+                CREATE USER bitwardenuser WITH PASSWORD '${dbPassword}';
+                GRANT ALL PRIVILEGES ON DATABASE bitwarden TO bitwardenuser;
+              '';
+            };
+
+            services.bitwarden_rs.config.databaseUrl = "postgresql://bitwardenuser:${dbPassword}@localhost/bitwarden";
+
+            systemd.services.bitwarden_rs.after = [ "postgresql.service" ];
+          };
+
+          sqlite = { };
+        };
+        in
+        mkMerge [
+          backendConfig.${backend}
+          {
+            services.bitwarden_rs = {
+              enable = true;
+              dbBackend = backend;
+              config.rocketPort = 80;
+            };
+
+            networking.firewall.allowedTCPPorts = [ 80 ];
+
+            environment.systemPackages =
+              let
+                testRunner = pkgs.writers.writePython3Bin "test-runner"
+                  {
+                    libraries = [ pkgs.python3Packages.selenium ];
+                  } ''
+                  from selenium.webdriver import Firefox
+                  from selenium.webdriver.firefox.options import Options
+                  from selenium.webdriver.support.ui import WebDriverWait
+                  from selenium.webdriver.support import expected_conditions as EC
+
+                  options = Options()
+                  options.add_argument('--headless')
+                  driver = Firefox(options=options)
+
+                  driver.implicitly_wait(20)
+                  driver.get('http://localhost/#/register')
+
+                  wait = WebDriverWait(driver, 10)
+
+                  wait.until(EC.title_contains("Create Account"))
+
+                  driver.find_element_by_css_selector('input#email').send_keys(
+                    '${userEmail}'
+                  )
+                  driver.find_element_by_css_selector('input#name').send_keys(
+                    'A Cat'
+                  )
+                  driver.find_element_by_css_selector('input#masterPassword').send_keys(
+                    '${userPassword}'
+                  )
+                  driver.find_element_by_css_selector('input#masterPasswordRetype').send_keys(
+                    '${userPassword}'
+                  )
+
+                  driver.find_element_by_xpath("//button[contains(., 'Submit')]").click()
+
+                  wait.until_not(EC.title_contains("Create Account"))
+
+                  driver.find_element_by_css_selector('input#masterPassword').send_keys(
+                    '${userPassword}'
+                  )
+                  driver.find_element_by_xpath("//button[contains(., 'Log In')]").click()
+
+                  wait.until(EC.title_contains("My Vault"))
+
+                  driver.find_element_by_xpath("//button[contains(., 'Add Item')]").click()
+
+                  driver.find_element_by_css_selector('input#name').send_keys(
+                    'secrets'
+                  )
+                  driver.find_element_by_css_selector('input#loginPassword').send_keys(
+                    '${storedPassword}'
+                  )
+
+                  driver.find_element_by_xpath("//button[contains(., 'Save')]").click()
+                '';
+              in
+              [ pkgs.firefox-unwrapped pkgs.geckodriver testRunner ];
+
+            virtualisation.memorySize = 768;
+          }
+        ];
+
+      client = { pkgs, ... }:
+        {
+          environment.systemPackages = [ pkgs.bitwarden-cli ];
+        };
+    };
+
+    testScript = ''
+      start_all()
+      server.wait_for_unit("bitwarden_rs.service")
+      server.wait_for_open_port(80)
+
+      with subtest("configure the cli"):
+          client.succeed("bw --nointeraction config server http://server")
+
+      with subtest("can't login to nonexistant account"):
+          client.fail(
+              "bw --nointeraction --raw login ${userEmail} ${userPassword}"
+          )
+
+      with subtest("use the web interface to sign up, log in, and save a password"):
+          server.succeed("PYTHONUNBUFFERED=1 test-runner | systemd-cat -t test-runner")
+
+      with subtest("log in with the cli"):
+          key = client.succeed(
+              "bw --nointeraction --raw login ${userEmail} ${userPassword}"
+          ).strip()
+
+      with subtest("sync with the cli"):
+          client.succeed(f"bw --nointeraction --raw --session {key} sync -f")
+
+      with subtest("get the password with the cli"):
+          password = client.succeed(
+              f"bw --nointeraction --raw --session {key} list items | ${pkgs.jq}/bin/jq -r .[].login.password"
+          )
+          assert password.strip() == "${storedPassword}"
+    '';
+  };
+in
+builtins.listToAttrs (
+  map
+    (backend: { name = backend; value = makeBitwardenTest backend; })
+    backends
+)
diff --git a/nixos/tests/blockbook-frontend.nix b/nixos/tests/blockbook-frontend.nix
new file mode 100644
index 0000000000000..742a02999e745
--- /dev/null
+++ b/nixos/tests/blockbook-frontend.nix
@@ -0,0 +1,28 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "blockbook-frontend";
+  meta = with pkgs.stdenv.lib; {
+    maintainers = with maintainers; [ _1000101 ];
+  };
+
+  machine = { ... }: {
+    services.blockbook-frontend."test" = {
+      enable = true;
+    };
+    services.bitcoind.mainnet = {
+      enable = true;
+      rpc = {
+        port = 8030;
+        users.rpc.passwordHMAC = "acc2374e5f9ba9e62a5204d3686616cf$53abdba5e67a9005be6a27ca03a93ce09e58854bc2b871523a0d239a72968033";
+      };
+    };
+  };
+
+  testScript = ''
+    start_all()
+    machine.wait_for_unit("blockbook-frontend-test.service")
+
+    machine.wait_for_open_port(9030)
+
+    machine.succeed("curl -sSfL http://localhost:9030 | grep 'Blockbook'")
+  '';
+})
diff --git a/nixos/tests/borgbackup.nix b/nixos/tests/borgbackup.nix
index d97471e293e83..bf37eb8607b28 100644
--- a/nixos/tests/borgbackup.nix
+++ b/nixos/tests/borgbackup.nix
@@ -43,7 +43,7 @@ in {
   nodes = {
     client = { ... }: {
       services.borgbackup.jobs = {
-        
+
         local = {
           paths = dataDir;
           repo = localRepo;
diff --git a/nixos/tests/ceph-multi-node.nix b/nixos/tests/ceph-multi-node.nix
index 22fe5cada4800..e26c6d5d670cf 100644
--- a/nixos/tests/ceph-multi-node.nix
+++ b/nixos/tests/ceph-multi-node.nix
@@ -183,15 +183,15 @@ let
     monA.wait_until_succeeds("ceph -s | grep 'HEALTH_OK'")
 
     monA.succeed(
-        "ceph osd pool create multi-node-test 128 128",
+        "ceph osd pool create multi-node-test 32 32",
         "ceph osd pool ls | grep 'multi-node-test'",
         "ceph osd pool rename multi-node-test multi-node-other-test",
         "ceph osd pool ls | grep 'multi-node-other-test'",
     )
-    monA.wait_until_succeeds("ceph -s | grep '1 pools, 128 pgs'")
+    monA.wait_until_succeeds("ceph -s | grep '2 pools, 33 pgs'")
     monA.succeed("ceph osd pool set multi-node-other-test size 2")
     monA.wait_until_succeeds("ceph -s | grep 'HEALTH_OK'")
-    monA.wait_until_succeeds("ceph -s | grep '128 active+clean'")
+    monA.wait_until_succeeds("ceph -s | grep '33 active+clean'")
     monA.fail(
         "ceph osd pool ls | grep 'multi-node-test'",
         "ceph osd pool delete multi-node-other-test multi-node-other-test --yes-i-really-really-mean-it",
diff --git a/nixos/tests/ceph-single-node.nix b/nixos/tests/ceph-single-node.nix
index 01c4b41384513..98528f6317bc0 100644
--- a/nixos/tests/ceph-single-node.nix
+++ b/nixos/tests/ceph-single-node.nix
@@ -143,12 +143,12 @@ let
     monA.wait_until_succeeds("ceph -s | grep 'HEALTH_OK'")
 
     monA.succeed(
-        "ceph osd pool create single-node-test 128 128",
+        "ceph osd pool create single-node-test 32 32",
         "ceph osd pool ls | grep 'single-node-test'",
         "ceph osd pool rename single-node-test single-node-other-test",
         "ceph osd pool ls | grep 'single-node-other-test'",
     )
-    monA.wait_until_succeeds("ceph -s | grep '1 pools, 128 pgs'")
+    monA.wait_until_succeeds("ceph -s | grep '2 pools, 33 pgs'")
     monA.succeed(
         "ceph osd getcrushmap -o crush",
         "crushtool -d crush -o decrushed",
@@ -158,7 +158,7 @@ let
         "ceph osd pool set single-node-other-test size 2",
     )
     monA.wait_until_succeeds("ceph -s | grep 'HEALTH_OK'")
-    monA.wait_until_succeeds("ceph -s | grep '128 active+clean'")
+    monA.wait_until_succeeds("ceph -s | grep '33 active+clean'")
     monA.fail(
         "ceph osd pool ls | grep 'multi-node-test'",
         "ceph osd pool delete single-node-other-test single-node-other-test --yes-i-really-really-mean-it",
diff --git a/nixos/tests/common/auto.nix b/nixos/tests/common/auto.nix
index 2c21a8d516737..da6b14e9f1608 100644
--- a/nixos/tests/common/auto.nix
+++ b/nixos/tests/common/auto.nix
@@ -41,8 +41,8 @@ in
 
   config = mkIf cfg.enable {
 
-    services.xserver.displayManager.lightdm = {
-      enable = true;
+    services.xserver.displayManager = {
+      lightdm.enable = true;
       autoLogin = {
         enable = true;
         user = cfg.user;
diff --git a/nixos/tests/consul.nix b/nixos/tests/consul.nix
index 6600dae4770b2..ee85f1d0b917a 100644
--- a/nixos/tests/consul.nix
+++ b/nixos/tests/consul.nix
@@ -55,30 +55,33 @@ let
 
   server = index: { pkgs, ... }:
     let
-      ip = builtins.elemAt allConsensusServerHosts index;
+      numConsensusServers = builtins.length allConsensusServerHosts;
+      thisConsensusServerHost = builtins.elemAt allConsensusServerHosts index;
+      ip = thisConsensusServerHost; # since we already use IPs to identify servers
     in
       {
         networking.interfaces.eth1.ipv4.addresses = pkgs.lib.mkOverride 0 [
-          { address = builtins.elemAt allConsensusServerHosts index; prefixLength = 16; }
+          { address = ip; prefixLength = 16; }
         ];
         networking.firewall = firewallSettings;
 
         services.consul =
-          let
-            thisConsensusServerHost = builtins.elemAt allConsensusServerHosts index;
-          in
           assert builtins.elem thisConsensusServerHost allConsensusServerHosts;
           {
             enable = true;
             inherit webUi;
             extraConfig = defaultExtraConfig // {
               server = true;
-              bootstrap_expect = builtins.length allConsensusServerHosts;
+              bootstrap_expect = numConsensusServers;
+              # Tell Consul that we never intend to drop below this many servers.
+              # Ensures to not permanently lose consensus after temporary loss.
+              # See https://github.com/hashicorp/consul/issues/8118#issuecomment-645330040
+              autopilot.min_quorum = numConsensusServers;
               retry_join =
                 # If there's only 1 node in the network, we allow self-join;
                 # otherwise, the node must not try to join itself, and join only the other servers.
                 # See https://github.com/hashicorp/consul/issues/2868
-                if builtins.length allConsensusServerHosts == 1
+                if numConsensusServers == 1
                   then allConsensusServerHosts
                   else builtins.filter (h: h != thisConsensusServerHost) allConsensusServerHosts;
               bind_addr = ip;
@@ -104,40 +107,123 @@ in {
     for m in machines:
         m.wait_for_unit("consul.service")
 
-    for m in machines:
-        m.wait_until_succeeds("[ $(consul members | grep -o alive | wc -l) == 5 ]")
+
+    def wait_for_healthy_servers():
+        # See https://github.com/hashicorp/consul/issues/8118#issuecomment-645330040
+        # for why the `Voter` column of `list-peers` has that info.
+        # TODO: The `grep true` relies on the fact that currently in
+        #       the output like
+        #           # consul operator raft list-peers
+        #           Node     ID   Address           State     Voter  RaftProtocol
+        #           server3  ...  192.168.1.3:8300  leader    true   3
+        #           server2  ...  192.168.1.2:8300  follower  true   3
+        #           server1  ...  192.168.1.1:8300  follower  false  3
+        #       `Voter`is the only boolean column.
+        #       Change this to the more reliable way to be defined by
+        #       https://github.com/hashicorp/consul/issues/8118
+        #       once that ticket is closed.
+        for m in machines:
+            m.wait_until_succeeds(
+                "[ $(consul operator raft list-peers | grep true | wc -l) == 3 ]"
+            )
+
+
+    def wait_for_all_machines_alive():
+        """
+        Note that Serf-"alive" does not mean "Raft"-healthy;
+        see `wait_for_healthy_servers()` for that instead.
+        """
+        for m in machines:
+            m.wait_until_succeeds("[ $(consul members | grep -o alive | wc -l) == 5 ]")
+
+
+    wait_for_healthy_servers()
+    # Also wait for clients to be alive.
+    wait_for_all_machines_alive()
 
     client1.succeed("consul kv put testkey 42")
     client2.succeed("[ $(consul kv get testkey) == 42 ]")
 
-    # Test that the cluster can tolearate failures of any single server:
-    for server in servers:
-        server.crash()
 
-        # For each client, wait until they have connection again
-        # using `kv get -recurse` before issuing commands.
-        client1.wait_until_succeeds("consul kv get -recurse")
-        client2.wait_until_succeeds("consul kv get -recurse")
+    def rolling_reboot_test(proper_rolling_procedure=True):
+        """
+        Tests that the cluster can tolearate failures of any single server,
+        following the recommended rolling upgrade procedure from
+        https://www.consul.io/docs/upgrading#standard-upgrades.
 
-        # Do some consul actions while one server is down.
-        client1.succeed("consul kv put testkey 43")
-        client2.succeed("[ $(consul kv get testkey) == 43 ]")
-        client2.succeed("consul kv delete testkey")
+        Optionally, `proper_rolling_procedure=False` can be given
+        to wait only for each server to be back `Healthy`, not `Stable`
+        in the Raft consensus, see Consul setting `ServerStabilizationTime` and
+        https://github.com/hashicorp/consul/issues/8118#issuecomment-645330040.
+        """
+
+        for server in servers:
+            server.crash()
+
+            # For each client, wait until they have connection again
+            # using `kv get -recurse` before issuing commands.
+            client1.wait_until_succeeds("consul kv get -recurse")
+            client2.wait_until_succeeds("consul kv get -recurse")
 
-        # Restart crashed machine.
-        server.start()
+            # Do some consul actions while one server is down.
+            client1.succeed("consul kv put testkey 43")
+            client2.succeed("[ $(consul kv get testkey) == 43 ]")
+            client2.succeed("consul kv delete testkey")
+
+            # Restart crashed machine.
+            server.start()
+
+            if proper_rolling_procedure:
+                # Wait for recovery.
+                wait_for_healthy_servers()
+            else:
+                # NOT proper rolling upgrade procedure, see above.
+                wait_for_all_machines_alive()
+
+            # Wait for client connections.
+            client1.wait_until_succeeds("consul kv get -recurse")
+            client2.wait_until_succeeds("consul kv get -recurse")
+
+            # Do some consul actions with server back up.
+            client1.succeed("consul kv put testkey 44")
+            client2.succeed("[ $(consul kv get testkey) == 44 ]")
+            client2.succeed("consul kv delete testkey")
+
+
+    def all_servers_crash_simultaneously_test():
+        """
+        Tests that the cluster will eventually come back after all
+        servers crash simultaneously.
+        """
+
+        for server in servers:
+            server.crash()
+
+        for server in servers:
+            server.start()
 
         # Wait for recovery.
-        for m in machines:
-            m.wait_until_succeeds("[ $(consul members | grep -o alive | wc -l) == 5 ]")
+        wait_for_healthy_servers()
 
         # Wait for client connections.
         client1.wait_until_succeeds("consul kv get -recurse")
         client2.wait_until_succeeds("consul kv get -recurse")
 
-        # Do some consul actions with server back up.
+        # Do some consul actions with servers back up.
         client1.succeed("consul kv put testkey 44")
         client2.succeed("[ $(consul kv get testkey) == 44 ]")
         client2.succeed("consul kv delete testkey")
+
+
+    # Run the tests.
+
+    print("rolling_reboot_test()")
+    rolling_reboot_test()
+
+    print("all_servers_crash_simultaneously_test()")
+    all_servers_crash_simultaneously_test()
+
+    print("rolling_reboot_test(proper_rolling_procedure=False)")
+    rolling_reboot_test(proper_rolling_procedure=False)
   '';
 })
diff --git a/nixos/tests/containers-portforward.nix b/nixos/tests/containers-portforward.nix
index fc90e151bd9ef..1e2c2c6c374f5 100644
--- a/nixos/tests/containers-portforward.nix
+++ b/nixos/tests/containers-portforward.nix
@@ -5,7 +5,7 @@ let
   hostPort = 10080;
   containerIp = "192.168.0.100";
   containerPort = 80;
-in 
+in
 
 import ./make-test-python.nix ({ pkgs, ...} : {
   name = "containers-portforward";
diff --git a/nixos/tests/convos.nix b/nixos/tests/convos.nix
new file mode 100644
index 0000000000000..b4ff1188fd8be
--- /dev/null
+++ b/nixos/tests/convos.nix
@@ -0,0 +1,30 @@
+import ./make-test-python.nix ({ lib, pkgs, ... }:
+
+with lib;
+let
+  port = 3333;
+in
+{
+  name = "convos";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ sgo ];
+  };
+
+  nodes = {
+    machine =
+      { pkgs, ... }:
+      {
+        services.convos = {
+          enable = true;
+          listenPort = port;
+        };
+      };
+  };
+
+  testScript = ''
+    machine.wait_for_unit("convos")
+    machine.wait_for_open_port("${toString port}")
+    machine.succeed("journalctl -u convos | grep -q 'Listening at.*${toString port}'")
+    machine.succeed("curl http://localhost:${toString port}/")
+  '';
+})
diff --git a/nixos/tests/corerad.nix b/nixos/tests/corerad.nix
index 72ab255b19160..37a1e90477a82 100644
--- a/nixos/tests/corerad.nix
+++ b/nixos/tests/corerad.nix
@@ -1,7 +1,7 @@
 import ./make-test-python.nix (
   {
     nodes = {
-      router = {config, pkgs, ...}: { 
+      router = {config, pkgs, ...}: {
         config = {
           # This machine simulates a router with IPv6 forwarding and a static IPv6 address.
           boot.kernel.sysctl = {
diff --git a/nixos/tests/docker-preloader.nix b/nixos/tests/docker-preloader.nix
index eeedec9a392ed..c3e8aced351a6 100644
--- a/nixos/tests/docker-preloader.nix
+++ b/nixos/tests/docker-preloader.nix
@@ -16,10 +16,10 @@ import ./make-test.nix ({ pkgs, ...} : {
           services.openssh.extraConfig = "PermitEmptyPasswords yes";
           users.extraUsers.root.password = "";
         };
-  };    
+  };
   testScript = ''
     startAll;
-    
+
     $docker->waitForUnit("sockets.target");
     $docker->succeed("docker run nix nix-store --version");
     $docker->succeed("docker run bash bash --version");
diff --git a/nixos/tests/docker-tools.nix b/nixos/tests/docker-tools.nix
index c48e5b0797628..2543801ae8bd2 100644
--- a/nixos/tests/docker-tools.nix
+++ b/nixos/tests/docker-tools.nix
@@ -30,8 +30,45 @@ import ./make-test-python.nix ({ pkgs, ... }: {
         )
 
     docker.succeed("docker run --rm ${examples.bash.imageName} bash --version")
+    # Check imageTag attribute matches image
+    docker.succeed("docker images --format '{{.Tag}}' | grep -F '${examples.bash.imageTag}'")
     docker.succeed("docker rmi ${examples.bash.imageName}")
 
+    # The remaining combinations
+    with subtest("Ensure imageTag attribute matches image"):
+        docker.succeed(
+            "docker load --input='${examples.bashNoTag}'"
+        )
+        docker.succeed(
+            "docker images --format '{{.Tag}}' | grep -F '${examples.bashNoTag.imageTag}'"
+        )
+        docker.succeed("docker rmi ${examples.bashNoTag.imageName}:${examples.bashNoTag.imageTag}")
+
+        docker.succeed(
+            "docker load --input='${examples.bashNoTagLayered}'"
+        )
+        docker.succeed(
+            "docker images --format '{{.Tag}}' | grep -F '${examples.bashNoTagLayered.imageTag}'"
+        )
+        docker.succeed("docker rmi ${examples.bashNoTagLayered.imageName}:${examples.bashNoTagLayered.imageTag}")
+
+        docker.succeed(
+            "${examples.bashNoTagStreamLayered} | docker load"
+        )
+        docker.succeed(
+            "docker images --format '{{.Tag}}' | grep -F '${examples.bashNoTagStreamLayered.imageTag}'"
+        )
+        docker.succeed(
+            "docker rmi ${examples.bashNoTagStreamLayered.imageName}:${examples.bashNoTagStreamLayered.imageTag}"
+        )
+
+        docker.succeed(
+            "docker load --input='${examples.nixLayered}'"
+        )
+        docker.succeed("docker images --format '{{.Tag}}' | grep -F '${examples.nixLayered.imageTag}'")
+        docker.succeed("docker rmi ${examples.nixLayered.imageName}")
+
+
     with subtest(
         "Check if the nix store is correctly initialized by listing "
         "dependencies of the installed Nix binary"
@@ -42,6 +79,16 @@ import ./make-test-python.nix ({ pkgs, ... }: {
             "docker rmi ${examples.nix.imageName}",
         )
 
+    with subtest(
+        "Ensure (layered) nix store has correct permissions "
+        "and that the container starts when its process does not have uid 0"
+    ):
+        docker.succeed(
+            "docker load --input='${examples.bashLayeredWithUser}'",
+            "docker run -u somebody --rm ${examples.bashLayeredWithUser.imageName} ${pkgs.bash}/bin/bash -c 'test 555 == $(stat --format=%a /nix) && test 555 == $(stat --format=%a /nix/store)'",
+            "docker rmi ${examples.bashLayeredWithUser.imageName}",
+        )
+
     with subtest("The nix binary symlinks are intact"):
         docker.succeed(
             "docker load --input='${examples.nix}'",
@@ -90,13 +137,22 @@ import ./make-test-python.nix ({ pkgs, ... }: {
 
     with subtest("Ensure Docker images can use an unstable date"):
         docker.succeed(
-            "docker load --input='${examples.bash}'"
+            "docker load --input='${examples.unstableDate}'"
         )
         assert unix_time_second1 not in docker.succeed(
             "docker inspect ${examples.unstableDate.imageName} "
             + "| ${pkgs.jq}/bin/jq -r .[].Created"
         )
 
+    with subtest("Ensure Layered Docker images can use an unstable date"):
+        docker.succeed(
+            "docker load --input='${examples.unstableDateLayered}'"
+        )
+        assert unix_time_second1 not in docker.succeed(
+            "docker inspect ${examples.unstableDateLayered.imageName} "
+            + "| ${pkgs.jq}/bin/jq -r .[].Created"
+        )
+
     with subtest("Ensure Layered Docker images work"):
         docker.succeed(
             "docker load --input='${examples.layered-image}'",
@@ -178,5 +234,12 @@ import ./make-test-python.nix ({ pkgs, ... }: {
         # This check may be loosened to allow an *empty* store rather than *no* store.
         docker.succeed("docker run --rm no-store-paths ls /")
         docker.fail("docker run --rm no-store-paths ls /nix/store")
+
+    with subtest("Ensure buildLayeredImage does not change store path contents."):
+        docker.succeed(
+            "docker load --input='${pkgs.dockerTools.examples.filesInStore}'",
+            "docker run --rm file-in-store nix-store --verify --check-contents",
+            "docker run --rm file-in-store |& grep 'some data'",
+        )
   '';
 })
diff --git a/nixos/tests/docker.nix b/nixos/tests/docker.nix
index 8fda7c1395ef3..a4a61468f33dc 100644
--- a/nixos/tests/docker.nix
+++ b/nixos/tests/docker.nix
@@ -43,7 +43,7 @@ import ./make-test-python.nix ({ pkgs, ...} : {
     docker.fail("sudo -u noprivs docker ps")
     docker.succeed("docker stop sleeping")
 
-    # Must match version twice to ensure client and server versions are correct
-    docker.succeed('[ $(docker version | grep ${pkgs.docker.version} | wc -l) = "2" ]')
+    # Must match version 4 times to ensure client and server git commits and versions are correct
+    docker.succeed('[ $(docker version | grep ${pkgs.docker.version} | wc -l) = "4" ]')
   '';
 })
diff --git a/nixos/tests/dokuwiki.nix b/nixos/tests/dokuwiki.nix
index 05271919effe8..58069366ca36e 100644
--- a/nixos/tests/dokuwiki.nix
+++ b/nixos/tests/dokuwiki.nix
@@ -32,24 +32,17 @@ let
 
 in {
   name = "dokuwiki";
-  meta.maintainers = with pkgs.lib.maintainers; [ "1000101" ];
-
+  meta = with pkgs.stdenv.lib; {
+    maintainers = with maintainers; [ _1000101 ];
+  };
   machine = { ... }: {
     services.dokuwiki."site1.local" = {
       aclUse = false;
       superUser = "admin";
-      nginx = {
-        forceSSL = false;
-        enableACME = false;
-      };
     };
     services.dokuwiki."site2.local" = {
-      aclUse = true;
+      usersFile = "/var/lib/dokuwiki/site2.local/users.auth.php";
       superUser = "admin";
-      nginx = {
-        forceSSL = false;
-        enableACME = false;
-      };
       templates = [ template-bootstrap3 ];
       plugins = [ plugin-icalevents ];
     };
@@ -69,6 +62,15 @@ in {
     machine.wait_for_open_port(80)
 
     machine.succeed("curl -sSfL http://site1.local/ | grep 'DokuWiki'")
+    machine.fail("curl -sSfL 'http://site1.local/doku.php?do=login' | grep 'Login'")
+
     machine.succeed("curl -sSfL http://site2.local/ | grep 'DokuWiki'")
+    machine.succeed("curl -sSfL 'http://site2.local/doku.php?do=login' | grep 'Login'")
+
+    machine.succeed(
+        "echo 'admin:$2y$10$ijdBQMzSVV20SrKtCna8gue36vnsbVm2wItAXvdm876sshI4uwy6S:Admin:admin@example.test:user' >> /var/lib/dokuwiki/site2.local/users.auth.php",
+        "curl -sSfL -d 'u=admin&p=password' --cookie-jar cjar 'http://site2.local/doku.php?do=login'",
+        "curl -sSfL --cookie cjar --cookie-jar cjar 'http://site2.local/doku.php?do=login' | grep 'Logged in as: <bdi>Admin</bdi>'",
+    )
   '';
 })
diff --git a/nixos/tests/firejail.nix b/nixos/tests/firejail.nix
new file mode 100644
index 0000000000000..a723cb01664f3
--- /dev/null
+++ b/nixos/tests/firejail.nix
@@ -0,0 +1,82 @@
+import ./make-test-python.nix ({ pkgs, ...} : {
+  name = "firejail";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ sgo ];
+  };
+
+  nodes.machine = { ... }: {
+    imports = [ ./common/user-account.nix ];
+
+    programs.firejail = {
+      enable = true;
+      wrappedBinaries = {
+        bash-jailed  = "${pkgs.bash}/bin/bash";
+      };
+    };
+
+    systemd.services.setupFirejailTest = {
+      wantedBy = [ "multi-user.target" ];
+      before = [ "multi-user.target" ];
+
+      environment = {
+        HOME = "/home/alice";
+      };
+
+      unitConfig = {
+        type = "oneshot";
+        RemainAfterExit = true;
+        user = "alice";
+      };
+
+      script = ''
+        cd $HOME
+
+        mkdir .password-store && echo s3cret > .password-store/secret
+        mkdir my-secrets && echo s3cret > my-secrets/secret
+
+        echo publ1c > public
+
+        mkdir -p .config/firejail
+        echo 'blacklist ''${HOME}/my-secrets' > .config/firejail/globals.local
+      '';
+    };
+  };
+
+  testScript = ''
+    start_all()
+    machine.wait_for_unit("multi-user.target")
+
+    # Test path acl with wrapper
+    machine.succeed("sudo -u alice bash-jailed -c 'cat ~/public' | grep -q publ1c")
+    machine.fail(
+        "sudo -u alice bash-jailed -c 'cat ~/.password-store/secret' | grep -q s3cret"
+    )
+    machine.fail("sudo -u alice bash-jailed -c 'cat ~/my-secrets/secret' | grep -q s3cret")
+
+
+    # Test path acl with firejail executable
+    machine.succeed("sudo -u alice firejail -- bash -c 'cat ~/public' | grep -q publ1c")
+    machine.fail(
+        "sudo -u alice firejail -- bash -c 'cat ~/.password-store/secret' | grep -q s3cret"
+    )
+    machine.fail(
+        "sudo -u alice firejail -- bash -c 'cat ~/my-secrets/secret' | grep -q s3cret"
+    )
+
+    # Disabling profiles
+    machine.succeed(
+        "sudo -u alice bash -c 'firejail --noprofile -- cat ~/.password-store/secret' | grep -q s3cret"
+    )
+
+    # CVE-2020-17367
+    machine.fail(
+        "sudo -u alice firejail --private-tmp id --output=/tmp/vuln1 && cat /tmp/vuln1"
+    )
+
+    # CVE-2020-17368
+    machine.fail(
+        "sudo -u alice firejail --private-tmp --output=/tmp/foo 'bash -c $(id>/tmp/vuln2;echo id)' && cat /tmp/vuln2"
+    )
+  '';
+})
+
diff --git a/nixos/tests/gnome3-xorg.nix b/nixos/tests/gnome3-xorg.nix
index b59badcd5de40..0d05c12384f32 100644
--- a/nixos/tests/gnome3-xorg.nix
+++ b/nixos/tests/gnome3-xorg.nix
@@ -12,8 +12,9 @@ import ./make-test-python.nix ({ pkgs, lib, ...} : {
 
       services.xserver.enable = true;
 
-      services.xserver.displayManager.gdm = {
-        enable = true;
+      services.xserver.displayManager = {
+        gdm.enable = true;
+        gdm.debug = true;
         autoLogin = {
           enable = true;
           user = user.name;
@@ -21,6 +22,7 @@ import ./make-test-python.nix ({ pkgs, lib, ...} : {
       };
 
       services.xserver.desktopManager.gnome3.enable = true;
+      services.xserver.desktopManager.gnome3.debug = true;
       services.xserver.displayManager.defaultSession = "gnome-xorg";
 
       virtualisation.memorySize = 1024;
diff --git a/nixos/tests/gnome3.nix b/nixos/tests/gnome3.nix
index 17e72c5f65103..b3d7aff8bd715 100644
--- a/nixos/tests/gnome3.nix
+++ b/nixos/tests/gnome3.nix
@@ -11,8 +11,9 @@ import ./make-test-python.nix ({ pkgs, lib, ...} : {
 
       services.xserver.enable = true;
 
-      services.xserver.displayManager.gdm = {
-        enable = true;
+      services.xserver.displayManager = {
+        gdm.enable = true;
+        gdm.debug = true;
         autoLogin = {
           enable = true;
           user = "alice";
@@ -20,6 +21,7 @@ import ./make-test-python.nix ({ pkgs, lib, ...} : {
       };
 
       services.xserver.desktopManager.gnome3.enable = true;
+      services.xserver.desktopManager.gnome3.debug = true;
 
       virtualisation.memorySize = 1024;
     };
diff --git a/nixos/tests/graphite.nix b/nixos/tests/graphite.nix
index 71776a94cbd64..137be2d89c8b3 100644
--- a/nixos/tests/graphite.nix
+++ b/nixos/tests/graphite.nix
@@ -1,11 +1,6 @@
 import ./make-test-python.nix ({ pkgs, ... } :
 {
   name = "graphite";
-  meta = {
-    # Fails on dependency `python-2.7-Twisted`'s test suite
-    # complaining `ImportError: No module named zope.interface`.
-    broken = true;
-  };
   nodes = {
     one =
       { ... }: {
@@ -21,7 +16,7 @@ import ./make-test-python.nix ({ pkgs, ... } :
           api = {
             enable = true;
             port = 8082;
-            finders = [ pkgs.python3Packages.influxgraph ];
+            finders = [ ];
           };
           carbon.enableCache = true;
           seyren.enable = false;  # Implicitely requires openssl-1.0.2u which is marked insecure
@@ -41,10 +36,14 @@ import ./make-test-python.nix ({ pkgs, ... } :
     # even if they're still in preStart (which takes quite long for graphiteWeb).
     # Wait for ports to open so we're sure the services are up and listening.
     one.wait_for_open_port(8080)
+    one.wait_for_open_port(8082)
     one.wait_for_open_port(2003)
     one.succeed('echo "foo 1 `date +%s`" | nc -N localhost 2003')
     one.wait_until_succeeds(
         "curl 'http://localhost:8080/metrics/find/?query=foo&format=treejson' --silent | grep foo >&2"
     )
+    one.wait_until_succeeds(
+        "curl 'http://localhost:8082/metrics/find/?query=foo&format=treejson' --silent | grep foo >&2"
+    )
   '';
 })
diff --git a/nixos/tests/grub.nix b/nixos/tests/grub.nix
new file mode 100644
index 0000000000000..84bfc90955b54
--- /dev/null
+++ b/nixos/tests/grub.nix
@@ -0,0 +1,60 @@
+import ./make-test-python.nix ({ lib, ... }: {
+  name = "grub";
+
+  meta = with lib.maintainers; {
+    maintainers = [ rnhmjoj ];
+  };
+
+  machine = { ... }: {
+    virtualisation.useBootLoader = true;
+
+    boot.loader.timeout = null;
+    boot.loader.grub = {
+      enable = true;
+      users.alice.password = "supersecret";
+
+      # OCR is not accurate enough
+      extraConfig = "serial; terminal_output serial";
+    };
+  };
+
+  testScript = ''
+    def grub_login_as(user, password):
+        """
+        Enters user and password to log into GRUB
+        """
+        machine.wait_for_console_text("Enter username:")
+        machine.send_chars(user + "\n")
+        machine.wait_for_console_text("Enter password:")
+        machine.send_chars(password + "\n")
+
+
+    def grub_select_all_configurations():
+        """
+        Selects "All configurations" from the GRUB menu
+        to trigger a login request.
+        """
+        machine.send_monitor_command("sendkey down")
+        machine.send_monitor_command("sendkey ret")
+
+
+    machine.start()
+
+    # wait for grub screen
+    machine.wait_for_console_text("GNU GRUB")
+
+    grub_select_all_configurations()
+    with subtest("Invalid credentials are rejected"):
+        grub_login_as("wronguser", "wrongsecret")
+        machine.wait_for_console_text("error: access denied.")
+
+    grub_select_all_configurations()
+    with subtest("Valid credentials are accepted"):
+        grub_login_as("alice", "supersecret")
+        machine.send_chars("\n")  # press enter to boot
+        machine.wait_for_console_text("Linux version")
+
+    with subtest("Machine boots correctly"):
+        machine.wait_for_unit("multi-user.target")
+  '';
+})
diff --git a/nixos/tests/home-assistant.nix b/nixos/tests/home-assistant.nix
index 0d1fcedcd857e..a93a28d877a31 100644
--- a/nixos/tests/home-assistant.nix
+++ b/nixos/tests/home-assistant.nix
@@ -2,6 +2,7 @@ import ./make-test-python.nix ({ pkgs, ... }:
 
 let
   configDir = "/var/lib/foobar";
+  mqttUsername = "homeassistant";
   mqttPassword = "secret";
 in {
   name = "home-assistant";
@@ -11,6 +12,15 @@ in {
 
   nodes.hass = { pkgs, ... }: {
     environment.systemPackages = with pkgs; [ mosquitto ];
+    services.mosquitto = {
+      enable = true;
+      users = {
+        "${mqttUsername}" = {
+          acl = [ "pattern readwrite #" ];
+          password = mqttPassword;
+        };
+      };
+    };
     services.home-assistant = {
       inherit configDir;
       enable = true;
@@ -23,8 +33,11 @@ in {
           elevation = 0;
         };
         frontend = {};
-        # uses embedded mqtt broker
-        mqtt.password = mqttPassword;
+        mqtt = {
+          broker = "127.0.0.1";
+          username = mqttUsername;
+          password = mqttPassword;
+        };
         binary_sensor = [{
           platform = "mqtt";
           state_topic = "home-assistant/test";
@@ -64,10 +77,10 @@ in {
     with subtest("Toggle a binary sensor using MQTT"):
         # wait for broker to become available
         hass.wait_until_succeeds(
-            "mosquitto_sub -V mqttv311 -t home-assistant/test -u homeassistant -P '${mqttPassword}' -W 1 -t '*'"
+            "mosquitto_sub -V mqttv311 -t home-assistant/test -u ${mqttUsername} -P '${mqttPassword}' -W 1 -t '*'"
         )
         hass.succeed(
-            "mosquitto_pub -V mqttv311 -t home-assistant/test -u homeassistant -P '${mqttPassword}' -m let_there_be_light"
+            "mosquitto_pub -V mqttv311 -t home-assistant/test -u ${mqttUsername} -P '${mqttPassword}' -m let_there_be_light"
         )
     with subtest("Print log to ease debugging"):
         output_log = hass.succeed("cat ${configDir}/home-assistant.log")
diff --git a/nixos/tests/hydra/common.nix b/nixos/tests/hydra/common.nix
index f612717dc9686..312c52e889a94 100644
--- a/nixos/tests/hydra/common.nix
+++ b/nixos/tests/hydra/common.nix
@@ -37,6 +37,7 @@
     };
     services.postfix.enable = true;
     nix = {
+      distributedBuilds = true;
       buildMachines = [{
         hostName = "localhost";
         systems = [ system ];
diff --git a/nixos/tests/ihatemoney.nix b/nixos/tests/ihatemoney.nix
index 7df0ea0b691fc..0451a4505808b 100644
--- a/nixos/tests/ihatemoney.nix
+++ b/nixos/tests/ihatemoney.nix
@@ -1,5 +1,11 @@
+{ system ? builtins.currentSystem,
+  config ? {},
+  pkgs ? import ../.. { inherit system config; }
+}:
+
 let
-  f = backend: import ./make-test-python.nix ({ pkgs, ... }: {
+  inherit (import ../lib/testing-python.nix { inherit system pkgs; }) makeTest;
+  f = backend: makeTest {
     name = "ihatemoney-${backend}";
     machine = { lib, ... }: {
       services.ihatemoney = {
@@ -24,9 +30,10 @@ let
     testScript = ''
       machine.wait_for_open_port(8000)
       machine.wait_for_unit("uwsgi.service")
+      machine.wait_until_succeeds("curl http://localhost:8000")
 
       assert '"yay"' in machine.succeed(
-          "curl -X POST http://localhost:8000/api/projects -d 'name=yay&id=yay&password=yay&contact_email=yay\@example.com'"
+          "curl -X POST http://localhost:8000/api/projects -d 'name=yay&id=yay&password=yay&contact_email=yay@example.com'"
       )
       owner, timestamp = machine.succeed(
           "stat --printf %U:%G___%Y /var/lib/ihatemoney/secret_key"
@@ -48,7 +55,7 @@ let
 
       assert "ihatemoney" in machine.succeed("curl http://localhost:8000")
     '';
-  });
+  };
 in {
   ihatemoney-sqlite = f "sqlite";
   ihatemoney-postgresql = f "postgresql";
diff --git a/nixos/tests/initrd-network-openvpn/default.nix b/nixos/tests/initrd-network-openvpn/default.nix
new file mode 100644
index 0000000000000..bb4c41e6d7095
--- /dev/null
+++ b/nixos/tests/initrd-network-openvpn/default.nix
@@ -0,0 +1,145 @@
+import ../make-test-python.nix ({ lib, ...}:
+
+{
+  name = "initrd-network-openvpn";
+
+  nodes =
+    let
+
+      # Inlining of the shared secret for the
+      # OpenVPN server and client
+      secretblock = ''
+        secret [inline]
+        <secret>
+        ${lib.readFile ./shared.key}
+        </secret>
+        '';
+
+    in
+    {
+
+      # Minimal test case to check a successful boot, even with invalid config
+      minimalboot =
+        { ... }:
+        {
+          boot.initrd.network = {
+            enable = true;
+            openvpn = {
+              enable = true;
+              configuration = "/dev/null";
+            };
+          };
+        };
+
+      # initrd VPN client
+      ovpnclient =
+        { ... }:
+        {
+          virtualisation.useBootLoader = true;
+          virtualisation.vlans = [ 1 ];
+
+          boot.initrd = {
+            # This command does not fork to keep the VM in the state where
+            # only the initramfs is loaded
+            preLVMCommands =
+            ''
+              /bin/nc -p 1234 -lke /bin/echo TESTVALUE
+            '';
+
+            network = {
+              enable = true;
+
+              # Work around udhcpc only getting a lease on eth0
+              postCommands = ''
+                /bin/ip addr add 192.168.1.2/24 dev eth1
+              '';
+
+              # Example configuration for OpenVPN
+              # This is the main reason for this test
+              openvpn = {
+                enable = true;
+                configuration = "${./initrd.ovpn}";
+              };
+            };
+          };
+        };
+
+      # VPN server and gateway for ovpnclient between vlan 1 and 2
+      ovpnserver =
+        { ... }:
+        {
+          virtualisation.vlans = [ 1 2 ];
+
+          # Enable NAT and forward port 12345 to port 1234
+          networking.nat = {
+            enable = true;
+            internalInterfaces = [ "tun0" ];
+            externalInterface = "eth2";
+            forwardPorts = [ { destination = "10.8.0.2:1234";
+                               sourcePort = 12345; } ];
+          };
+
+          # Trust tun0 and allow the VPN Server to be reached
+          networking.firewall = {
+            trustedInterfaces = [ "tun0" ];
+            allowedUDPPorts = [ 1194 ];
+          };
+
+          # Minimal OpenVPN server configuration
+          services.openvpn.servers.testserver =
+          {
+            config = ''
+              dev tun0
+              ifconfig 10.8.0.1 10.8.0.2
+              ${secretblock}
+            '';
+          };
+        };
+
+      # Client that resides in the "external" VLAN
+      testclient =
+        { ... }:
+        {
+          virtualisation.vlans = [ 2 ];
+        };
+  };
+
+
+  testScript =
+    ''
+      # Minimal test case, checks whether enabling (with invalid config) harms
+      # the boot process
+      with subtest("Check for successful boot with broken openvpn config"):
+          minimalboot.start()
+          # If we get to multi-user.target, we booted successfully
+          minimalboot.wait_for_unit("multi-user.target")
+          minimalboot.shutdown()
+
+      # Elaborated test case where the ovpnclient (where this module is used)
+      # can be reached by testclient only over ovpnserver.
+      # This is an indirect test for success.
+      with subtest("Check for connection from initrd VPN client, config as file"):
+          ovpnserver.start()
+          testclient.start()
+          ovpnclient.start()
+
+          # Wait until the OpenVPN Server is available
+          ovpnserver.wait_for_unit("openvpn-testserver.service")
+          ovpnserver.succeed("ping -c 1 10.8.0.1")
+
+          # Wait for the client to connect
+          ovpnserver.wait_until_succeeds("ping -c 1 10.8.0.2")
+
+          # Wait until the testclient has network
+          testclient.wait_for_unit("network.target")
+
+          # Check that ovpnclient is reachable over vlan 1
+          ovpnserver.succeed("nc -w 2 192.168.1.2 1234 | grep -q TESTVALUE")
+
+          # Check that ovpnclient is reachable over tun0
+          ovpnserver.succeed("nc -w 2 10.8.0.2 1234 | grep -q TESTVALUE")
+
+          # Check that ovpnclient is reachable from testclient over the gateway
+          testclient.succeed("nc -w 2 192.168.2.3 12345 | grep -q TESTVALUE")
+    '';
+})
diff --git a/nixos/tests/initrd-network-openvpn/initrd.ovpn b/nixos/tests/initrd-network-openvpn/initrd.ovpn
new file mode 100644
index 0000000000000..5926a48af00f4
--- /dev/null
+++ b/nixos/tests/initrd-network-openvpn/initrd.ovpn
@@ -0,0 +1,29 @@
+remote 192.168.1.3
+dev tun
+ifconfig 10.8.0.2 10.8.0.1
+# Only force VLAN 2 through the VPN
+route 192.168.2.0 255.255.255.0 10.8.0.1
+secret [inline]
+<secret>
+#
+# 2048 bit OpenVPN static key
+#
+-----BEGIN OpenVPN Static key V1-----
+553aabe853acdfe51cd6fcfea93dcbb0
+c8797deadd1187606b1ea8f2315eb5e6
+67c0d7e830f50df45686063b189d6c6b
+aab8bb3430cc78f7bb1f78628d5c3742
+0cef4f53a5acab2894905f4499f95d8e
+e69b7b6748b17016f89e19e91481a9fd
+bf8c10651f41a1d4fdf5f438925a6733
+13cec8f04701eb47b8f7ffc48bc3d7af
+65f07bce766015b87c3db4d668c655ff
+be5a69522a8e60ccb217f8521681b45d
+27c0b70bdfbfbb426c7646d80adf7482
+3ddac58b25cb1c1bb100de974478b4c6
+8b45a94261a2405e99810cb2b3abd49f
+21b3198ada87ff3c4e656a008e540a8d
+e7811584363597599cce2040a68ac00e
+f2125540e0f7f4adc37cb3f0d922eeb7
+-----END OpenVPN Static key V1-----
+</secret>
\ No newline at end of file
diff --git a/nixos/tests/initrd-network-openvpn/shared.key b/nixos/tests/initrd-network-openvpn/shared.key
new file mode 100644
index 0000000000000..248a91a3e3d5c
--- /dev/null
+++ b/nixos/tests/initrd-network-openvpn/shared.key
@@ -0,0 +1,21 @@
+#
+# 2048 bit OpenVPN static key
+#
+-----BEGIN OpenVPN Static key V1-----
+553aabe853acdfe51cd6fcfea93dcbb0
+c8797deadd1187606b1ea8f2315eb5e6
+67c0d7e830f50df45686063b189d6c6b
+aab8bb3430cc78f7bb1f78628d5c3742
+0cef4f53a5acab2894905f4499f95d8e
+e69b7b6748b17016f89e19e91481a9fd
+bf8c10651f41a1d4fdf5f438925a6733
+13cec8f04701eb47b8f7ffc48bc3d7af
+65f07bce766015b87c3db4d668c655ff
+be5a69522a8e60ccb217f8521681b45d
+27c0b70bdfbfbb426c7646d80adf7482
+3ddac58b25cb1c1bb100de974478b4c6
+8b45a94261a2405e99810cb2b3abd49f
+21b3198ada87ff3c4e656a008e540a8d
+e7811584363597599cce2040a68ac00e
+f2125540e0f7f4adc37cb3f0d922eeb7
+-----END OpenVPN Static key V1-----
diff --git a/nixos/tests/installed-tests/default.nix b/nixos/tests/installed-tests/default.nix
index b6bdfea227701..889a00d4b5686 100644
--- a/nixos/tests/installed-tests/default.nix
+++ b/nixos/tests/installed-tests/default.nix
@@ -50,6 +50,12 @@ let
 
             environment.systemPackages = with pkgs; [ gnome-desktop-testing ];
 
+            # The installed tests need to be added to the test VM’s closure.
+            # Otherwise, their dependencies might not actually be registered
+            # as valid paths in the VM’s Nix store database,
+            # and `nix-store --query` commands run as part of the tests
+            # (for example when building Flatpak runtimes) will fail.
+            environment.variables.TESTED_PACKAGE_INSTALLED_TESTS = "${tested.installedTests}/share";
           };
 
           testScript =
diff --git a/nixos/tests/installed-tests/flatpak.nix b/nixos/tests/installed-tests/flatpak.nix
index 091c993266296..8aeeaca90f614 100644
--- a/nixos/tests/installed-tests/flatpak.nix
+++ b/nixos/tests/installed-tests/flatpak.nix
@@ -5,14 +5,11 @@ makeInstalledTest {
   withX11 = true;
 
   testConfig = {
-    services.xserver.desktopManager.gnome3.enable = true; # TODO: figure out minimal environment where the tests work
-    # common/x11.nix enables the auto display manager (lightdm)
-    services.xserver.displayManager.gdm.enable = false;
-    services.gnome3.core-utilities.enable = false;
+    xdg.portal.enable = true;
     services.flatpak.enable = true;
-    environment.systemPackages = with pkgs; [ gnupg ostree python2 ];
+    environment.systemPackages = with pkgs; [ gnupg ostree python3 ];
     virtualisation.memorySize = 2047;
-    virtualisation.diskSize = 1024;
+    virtualisation.diskSize = 3072;
   };
 
   testRunnerFlags = "--timeout 3600";
diff --git a/nixos/tests/installed-tests/ibus.nix b/nixos/tests/installed-tests/ibus.nix
index af54b612b507b..a4bc2a7d7de0b 100644
--- a/nixos/tests/installed-tests/ibus.nix
+++ b/nixos/tests/installed-tests/ibus.nix
@@ -5,16 +5,12 @@ makeInstalledTest {
 
   testConfig = {
     i18n.inputMethod.enabled = "ibus";
+    systemd.user.services.ibus-daemon = {
+      serviceConfig.ExecStart = "${pkgs.ibus}/bin/ibus-daemon --xim --verbose";
+      wantedBy = [ "graphical-session.target" ];
+      partOf = [ "graphical-session.target" ];
+    };
   };
 
-  preTestScript = ''
-    # ibus has ibus-desktop-testing-runner but it tries to manage desktop session so we just spawn ibus-daemon ourselves
-    machine.succeed("ibus-daemon --daemonize --verbose")
-  '';
-
   withX11 = true;
-
-  # TODO: ibus-daemon is currently crashing or something
-  # maybe make ibus systemd service that auto-restarts?
-  meta.broken = true;
 }
diff --git a/nixos/tests/installed-tests/ostree.nix b/nixos/tests/installed-tests/ostree.nix
index eef7cace54cc1..90e09ad4ddf49 100644
--- a/nixos/tests/installed-tests/ostree.nix
+++ b/nixos/tests/installed-tests/ostree.nix
@@ -3,21 +3,10 @@
 makeInstalledTest {
   tested = pkgs.ostree;
 
-  # TODO: Wrap/patch the tests directly in the package
   testConfig = {
     environment.systemPackages = with pkgs; [
-      (python3.withPackages (p: with p; [ pyyaml ]))
       gnupg
       ostree
     ];
-
-    # for GJS tests
-    environment.variables.GI_TYPELIB_PATH = lib.makeSearchPath "lib/girepository-1.0" (with pkgs; [
-      gtk3
-      pango.out
-      ostree
-      gdk-pixbuf
-      atk
-    ]);
   };
 }
diff --git a/nixos/tests/installer.nix b/nixos/tests/installer.nix
index eef9abebf9f25..02b839fee3f56 100644
--- a/nixos/tests/installer.nix
+++ b/nixos/tests/installer.nix
@@ -64,7 +64,7 @@ let
   # a test script fragment `createPartitions', which must create
   # partitions and filesystems.
   testScriptFun = { bootLoader, createPartitions, grubVersion, grubDevice, grubUseEfi
-                  , grubIdentifier, preBootCommands, extraConfig
+                  , grubIdentifier, preBootCommands, postBootCommands, extraConfig
                   , testSpecialisationConfig
                   }:
     let iface = if grubVersion == 1 then "ide" else "virtio";
@@ -216,6 +216,7 @@ let
       machine = create_machine_named("boot-after-rebuild-switch")
       ${preBootCommands}
       machine.wait_for_unit("network.target")
+      ${postBootCommands}
       machine.shutdown()
 
       # Tests for validating clone configuration entries in grub menu
@@ -238,6 +239,7 @@ let
       with subtest("Set grub to boot the second configuration"):
           machine.succeed("grub-reboot 1")
 
+      ${postBootCommands}
       machine.shutdown()
 
       # Reboot Machine
@@ -252,12 +254,13 @@ let
       with subtest("We should find a file named /etc/gitconfig"):
           machine.succeed("test -e /etc/gitconfig")
 
+      ${postBootCommands}
       machine.shutdown()
     '';
 
 
   makeInstallerTest = name:
-    { createPartitions, preBootCommands ? "", extraConfig ? ""
+    { createPartitions, preBootCommands ? "", postBootCommands ? "", extraConfig ? ""
     , extraInstallerConfig ? {}
     , bootLoader ? "grub" # either "grub" or "systemd-boot"
     , grubVersion ? 2, grubDevice ? "/dev/vda", grubIdentifier ? "uuid", grubUseEfi ? false
@@ -335,7 +338,7 @@ let
       };
 
       testScript = testScriptFun {
-        inherit bootLoader createPartitions preBootCommands
+        inherit bootLoader createPartitions preBootCommands postBootCommands
                 grubVersion grubDevice grubIdentifier grubUseEfi extraConfig
                 testSpecialisationConfig;
       };
@@ -552,16 +555,26 @@ in {
           + " mkpart primary 2048M -1s"  # PV2
           + " set 2 lvm on",
           "udevadm settle",
+          "sleep 1",
           "pvcreate /dev/vda1 /dev/vda2",
+          "sleep 1",
           "vgcreate MyVolGroup /dev/vda1 /dev/vda2",
+          "sleep 1",
           "lvcreate --size 1G --name swap MyVolGroup",
-          "lvcreate --size 2G --name nixos MyVolGroup",
+          "sleep 1",
+          "lvcreate --size 3G --name nixos MyVolGroup",
+          "sleep 1",
           "mkswap -f /dev/MyVolGroup/swap -L swap",
           "swapon -L swap",
           "mkfs.xfs -L nixos /dev/MyVolGroup/nixos",
           "mount LABEL=nixos /mnt",
       )
     '';
+    postBootCommands = ''
+      assert "loaded active" in machine.succeed(
+          "systemctl list-units 'lvm2-pvscan@*' -ql --no-legend | tee /dev/stderr"
+      )
+    '';
   };
 
   # Boot off an encrypted root partition with the default LUKS header format
diff --git a/nixos/tests/jitsi-meet.nix b/nixos/tests/jitsi-meet.nix
new file mode 100644
index 0000000000000..42762dfdad8e0
--- /dev/null
+++ b/nixos/tests/jitsi-meet.nix
@@ -0,0 +1,55 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "jitsi-meet";
+  meta = with pkgs.stdenv.lib; {
+    maintainers = teams.jitsi.members;
+  };
+
+  nodes = {
+    client = { nodes, pkgs, ... }: {
+    };
+    server = { config, pkgs, ... }: {
+      services.jitsi-meet = {
+        enable = true;
+        hostName = "server";
+      };
+      services.jitsi-videobridge.openFirewall = true;
+
+      networking.firewall.allowedTCPPorts = [ 80 443 ];
+
+      services.nginx.virtualHosts.server = {
+        enableACME = true;
+        forceSSL = true;
+      };
+
+      security.acme.email = "me@example.org";
+      security.acme.acceptTerms = true;
+      security.acme.server = "https://example.com"; # self-signed only
+    };
+  };
+
+  testScript = ''
+    server.wait_for_unit("jitsi-videobridge2.service")
+    server.wait_for_unit("jicofo.service")
+    server.wait_for_unit("nginx.service")
+    server.wait_for_unit("prosody.service")
+
+    server.wait_until_succeeds(
+        "journalctl -b -u jitsi-videobridge2 -o cat | grep -q 'Performed a successful health check'"
+    )
+    server.wait_until_succeeds(
+        "journalctl -b -u jicofo -o cat | grep -q 'connected .JID: focus@auth.server'"
+    )
+    server.wait_until_succeeds(
+        "journalctl -b -u prosody -o cat | grep -q 'Authenticated as focus@auth.server'"
+    )
+    server.wait_until_succeeds(
+        "journalctl -b -u prosody -o cat | grep -q 'focus.server:component: External component successfully authenticated'"
+    )
+    server.wait_until_succeeds(
+        "journalctl -b -u prosody -o cat | grep -q 'Authenticated as jvb@auth.server'"
+    )
+
+    client.wait_for_unit("network.target")
+    assert "<title>Jitsi Meet</title>" in client.succeed("curl -sSfkL http://server/")
+  '';
+})
diff --git a/nixos/tests/kubernetes/base.nix b/nixos/tests/kubernetes/base.nix
index 86de9455e7374..8cfac10b6dc47 100644
--- a/nixos/tests/kubernetes/base.nix
+++ b/nixos/tests/kubernetes/base.nix
@@ -9,7 +9,6 @@ with pkgs.lib;
 let
   mkKubernetesBaseTest =
     { name, domain ? "my.zyx", test, machines
-    , pkgs ? import <nixpkgs> { inherit system; }
     , extraConfiguration ? null }:
     let
       masterName = head (filter (machineName: any (role: role == "master") machines.${machineName}.roles) (attrNames machines));
diff --git a/nixos/tests/leaps.nix b/nixos/tests/leaps.nix
index 65b475d734ec8..ac0c602d44504 100644
--- a/nixos/tests/leaps.nix
+++ b/nixos/tests/leaps.nix
@@ -7,7 +7,7 @@ import ./make-test-python.nix ({ pkgs,  ... }:
   };
 
   nodes =
-    { 
+    {
       client = { };
 
       server =
diff --git a/nixos/tests/lorri/default.nix b/nixos/tests/lorri/default.nix
index 198171082d880..c33c7503993da 100644
--- a/nixos/tests/lorri/default.nix
+++ b/nixos/tests/lorri/default.nix
@@ -18,7 +18,7 @@ import ../make-test-python.nix {
     machine.wait_until_succeeds("grep --fixed-strings 'ready' lorri.stdout")
 
     # Ping the daemon
-    machine.succeed("lorri internal__ping shell.nix")
+    machine.succeed("lorri internal ping shell.nix")
 
     # Wait for the daemon to finish the build
     machine.wait_until_succeeds("grep --fixed-strings 'Completed' lorri.stdout")
diff --git a/nixos/tests/mathics.nix b/nixos/tests/mathics.nix
deleted file mode 100644
index fcbeeb18a7271..0000000000000
--- a/nixos/tests/mathics.nix
+++ /dev/null
@@ -1,20 +0,0 @@
-import ./make-test.nix ({ pkgs, ... }: {
-  name = "mathics";
-  meta = with pkgs.stdenv.lib.maintainers; {
-    maintainers = [ benley ];
-  };
-
-  nodes = {
-    machine = { ... }: {
-      services.mathics.enable = true;
-      services.mathics.port = 8888;
-    };
-  };
-
-  testScript = ''
-    startAll;
-    $machine->waitForUnit("mathics.service");
-    $machine->waitForOpenPort(8888);
-    $machine->succeed("curl http://localhost:8888/");
-  '';
-})
diff --git a/nixos/tests/mesos.nix b/nixos/tests/mesos.nix
deleted file mode 100644
index 2e6dc0eda063c..0000000000000
--- a/nixos/tests/mesos.nix
+++ /dev/null
@@ -1,92 +0,0 @@
-import ./make-test.nix ({ pkgs, ...} : rec {
-  name = "mesos";
-  meta = with pkgs.stdenv.lib.maintainers; {
-    maintainers = [ offline kamilchm cstrahan ];
-  };
-
-  nodes = {
-    master = { ... }: {
-      networking.firewall.enable = false;
-      services.zookeeper.enable = true;
-      services.mesos.master = {
-          enable = true;
-          zk = "zk://master:2181/mesos";
-      };
-    };
-
-    slave = { ... }: {
-      networking.firewall.enable = false;
-      networking.nat.enable = true;
-      virtualisation.docker.enable = true;
-      services.mesos = {
-        slave = {
-          enable = true;
-          master = "master:5050";
-          dockerRegistry = registry;
-          executorEnvironmentVariables = {
-            PATH = "/run/current-system/sw/bin";
-          };
-        };
-      };
-    };
-  };
-
-  simpleDocker = pkgs.dockerTools.buildImage {
-    name = "echo";
-    tag = "latest";
-    contents = [ pkgs.stdenv.shellPackage pkgs.coreutils ];
-    config = {
-      Env = [
-        # When shell=true, mesos invokes "sh -c '<cmd>'", so make sure "sh" is
-        # on the PATH.
-        "PATH=${pkgs.stdenv.shellPackage}/bin:${pkgs.coreutils}/bin"
-      ];
-      Entrypoint = [ "echo" ];
-    };
-  };
-
-  registry = pkgs.runCommand "registry" { } ''
-    mkdir -p $out
-    cp ${simpleDocker} $out/echo:latest.tar
-  '';
-
-  testFramework = pkgs.pythonPackages.buildPythonPackage {
-    name = "mesos-tests";
-    propagatedBuildInputs = [ pkgs.mesos ];
-    catchConflicts = false;
-    src = ./mesos_test.py;
-    phases = [ "installPhase" "fixupPhase" ];
-    installPhase = ''
-      install -Dvm 0755 $src $out/bin/mesos_test.py
-
-      echo "done" > test.result
-      tar czf $out/test.tar.gz test.result
-    '';
-  };
-
-  testScript =
-    ''
-      startAll;
-      $master->waitForUnit("zookeeper.service");
-      $master->waitForUnit("mesos-master.service");
-      $slave->waitForUnit("docker.service");
-      $slave->waitForUnit("mesos-slave.service");
-      $master->waitForOpenPort(2181);
-      $master->waitForOpenPort(5050);
-      $slave->waitForOpenPort(5051);
-
-      # is slave registered?
-      $master->waitUntilSucceeds("curl -s --fail http://master:5050/master/slaves".
-                                 " | grep -q \"\\\"hostname\\\":\\\"slave\\\"\"");
-
-      # try to run docker image
-      $master->succeed("${pkgs.mesos}/bin/mesos-execute --master=master:5050".
-                       " --resources=\"cpus:0.1;mem:32\" --name=simple-docker".
-                       " --containerizer=mesos --docker_image=echo:latest".
-                       " --shell=true --command=\"echo done\" | grep -q TASK_FINISHED");
-
-      # simple command with .tar.gz uri
-      $master->succeed("${testFramework}/bin/mesos_test.py master ".
-                       "${testFramework}/test.tar.gz");
-    '';
-})
diff --git a/nixos/tests/mesos_test.py b/nixos/tests/mesos_test.py
deleted file mode 100644
index be8bb32e49a7e..0000000000000
--- a/nixos/tests/mesos_test.py
+++ /dev/null
@@ -1,72 +0,0 @@
-#!/usr/bin/env python
-import uuid
-import time
-import subprocess
-import os
-
-import sys
-
-from mesos.interface import Scheduler
-from mesos.native import MesosSchedulerDriver
-from mesos.interface import mesos_pb2
-
-def log(msg):
-    process = subprocess.Popen("systemd-cat", stdin=subprocess.PIPE)
-    (out,err) = process.communicate(msg)
-
-class NixosTestScheduler(Scheduler):
-    def __init__(self):
-        self.master_ip = sys.argv[1]
-        self.download_uri = sys.argv[2]
-
-    def resourceOffers(self, driver, offers):
-        log("XXX got resource offer")
-
-        offer = offers[0]
-        task = self.new_task(offer)
-        uri = task.command.uris.add()
-        uri.value = self.download_uri
-        task.command.value = "cat test.result"
-        driver.launchTasks(offer.id, [task])
-
-    def statusUpdate(self, driver, update):
-        log("XXX status update")
-        if update.state == mesos_pb2.TASK_FAILED:
-            log("XXX test task failed with message: " + update.message)
-            driver.stop()
-            sys.exit(1)
-        elif update.state == mesos_pb2.TASK_FINISHED:
-            driver.stop()
-            sys.exit(0)
-
-    def new_task(self, offer):
-        task = mesos_pb2.TaskInfo()
-        id = uuid.uuid4()
-        task.task_id.value = str(id)
-        task.slave_id.value = offer.slave_id.value
-        task.name = "task {}".format(str(id))
-
-        cpus = task.resources.add()
-        cpus.name = "cpus"
-        cpus.type = mesos_pb2.Value.SCALAR
-        cpus.scalar.value = 0.1
-
-        mem = task.resources.add()
-        mem.name = "mem"
-        mem.type = mesos_pb2.Value.SCALAR
-        mem.scalar.value = 32
-
-        return task
-
-if __name__ == '__main__':
-    log("XXX framework started")
-
-    framework = mesos_pb2.FrameworkInfo()
-    framework.user = "root"
-    framework.name = "nixos-test-framework"
-    driver = MesosSchedulerDriver(
-        NixosTestScheduler(),
-        framework,
-        sys.argv[1] + ":5050"
-    )
-    driver.run()
diff --git a/nixos/tests/misc.nix b/nixos/tests/misc.nix
index 17260ce640676..ae15055327343 100644
--- a/nixos/tests/misc.nix
+++ b/nixos/tests/misc.nix
@@ -20,12 +20,24 @@ import ./make-test-python.nix ({ pkgs, ...} : rec {
         { fsType = "tmpfs";
           options = [ "mode=1777" "noauto" ];
         };
+        # Tests https://discourse.nixos.org/t/how-to-make-a-derivations-executables-have-the-s-permission/8555
+        "/user-mount/point" = {
+          device = "/user-mount/source";
+          fsType = "none";
+          options = [ "bind" "rw" "user" "noauto" ];
+        };
+        "/user-mount/denied-point" = {
+          device = "/user-mount/denied-source";
+          fsType = "none";
+          options = [ "bind" "rw" "noauto" ];
+        };
       };
       systemd.automounts = singleton
         { wantedBy = [ "multi-user.target" ];
           where = "/tmp2";
         };
       users.users.sybil = { isNormalUser = true; group = "wheel"; };
+      users.users.alice = { isNormalUser = true; };
       security.sudo = { enable = true; wheelNeedsPassword = false; };
       boot.kernel.sysctl."vm.swappiness" = 1;
       boot.kernelParams = [ "vsyscall=emulate" ];
@@ -112,6 +124,26 @@ import ./make-test-python.nix ({ pkgs, ...} : rec {
           machine.succeed("touch /tmp2/x")
           machine.succeed("grep '/tmp2 tmpfs' /proc/mounts")
 
+      with subtest(
+          "Whether mounting by a user is possible with the `user` option in fstab (#95444)"
+      ):
+          machine.succeed("mkdir -p /user-mount/source")
+          machine.succeed("touch /user-mount/source/file")
+          machine.succeed("chmod -R a+Xr /user-mount/source")
+          machine.succeed("mkdir /user-mount/point")
+          machine.succeed("chown alice:users /user-mount/point")
+          machine.succeed("su - alice -c 'mount /user-mount/point'")
+          machine.succeed("su - alice -c 'ls /user-mount/point/file'")
+      with subtest(
+          "Whether mounting by a user is denied without the `user` option in  fstab"
+      ):
+          machine.succeed("mkdir -p /user-mount/denied-source")
+          machine.succeed("touch /user-mount/denied-source/file")
+          machine.succeed("chmod -R a+Xr /user-mount/denied-source")
+          machine.succeed("mkdir /user-mount/denied-point")
+          machine.succeed("chown alice:users /user-mount/denied-point")
+          machine.fail("su - alice -c 'mount /user-mount/denied-point'")
+
       with subtest("shell-vars"):
           machine.succeed('[ -n "$NIX_PATH" ]')
 
diff --git a/nixos/tests/molly-brown.nix b/nixos/tests/molly-brown.nix
new file mode 100644
index 0000000000000..09ce42726ca98
--- /dev/null
+++ b/nixos/tests/molly-brown.nix
@@ -0,0 +1,71 @@
+import ./make-test-python.nix ({ pkgs, ... }:
+
+  let testString = "NixOS Gemini test successful";
+  in {
+
+    name = "molly-brown";
+    meta = with pkgs.stdenv.lib.maintainers; { maintainers = [ ehmry ]; };
+
+    nodes = {
+
+      geminiServer = { config, pkgs, ... }:
+        let
+          inherit (config.networking) hostName;
+          cfg = config.services.molly-brown;
+        in {
+
+          environment.systemPackages = [
+            (pkgs.writeScriptBin "test-gemini" ''
+              #!${pkgs.python3}/bin/python
+
+              import socket
+              import ssl
+              import tempfile
+              import textwrap
+              import urllib.parse
+
+              url = "gemini://geminiServer/init.gmi"
+              parsed_url = urllib.parse.urlparse(url)
+
+              s = socket.create_connection((parsed_url.netloc, 1965))
+              context = ssl.SSLContext()
+              context.check_hostname = False
+              context.verify_mode = ssl.CERT_NONE
+              s = context.wrap_socket(s, server_hostname=parsed_url.netloc)
+              s.sendall((url + "\r\n").encode("UTF-8"))
+              fp = s.makefile("rb")
+              print(fp.readline().strip())
+              print(fp.readline().strip())
+              print(fp.readline().strip())
+            '')
+          ];
+
+          networking.firewall.allowedTCPPorts = [ cfg.settings.Port ];
+
+          services.molly-brown = {
+            enable = true;
+            docBase = "/tmp/docs";
+            certPath = "/tmp/cert.pem";
+            keyPath = "/tmp/key.pem";
+          };
+
+          systemd.services.molly-brown.preStart = ''
+            ${pkgs.openssl}/bin/openssl genrsa -out "/tmp/key.pem"
+            ${pkgs.openssl}/bin/openssl req -new \
+              -subj "/CN=${config.networking.hostName}" \
+              -key "/tmp/key.pem" -out /tmp/request.pem
+            ${pkgs.openssl}/bin/openssl x509 -req -days 3650 \
+              -in /tmp/request.pem -signkey "/tmp/key.pem" -out "/tmp/cert.pem"
+
+            mkdir -p "${cfg.settings.DocBase}"
+            echo "${testString}" > "${cfg.settings.DocBase}/test.gmi"
+          '';
+        };
+    };
+    testScript = ''
+      geminiServer.wait_for_unit("molly-brown")
+      geminiServer.wait_for_open_port(1965)
+      geminiServer.succeed("test-gemini")
+    '';
+
+  })
diff --git a/nixos/tests/mongodb.nix b/nixos/tests/mongodb.nix
index a637ec4bfc00f..1a71238830189 100644
--- a/nixos/tests/mongodb.nix
+++ b/nixos/tests/mongodb.nix
@@ -15,7 +15,7 @@ import ./make-test-python.nix ({ pkgs, ... }:
       node.wait_for_open_port(27017)
 
       assert "hello" in node.succeed(
-          "mongo ${testQuery}"
+          "${pkg}/bin/mongo ${testQuery}"
       )
 
       node.execute(
@@ -36,6 +36,7 @@ import ./make-test-python.nix ({ pkgs, ... }:
           mongodb-3_4
           mongodb-3_6
           mongodb-4_0
+          mongodb-4_2
         ];
       };
     };
@@ -44,8 +45,9 @@ import ./make-test-python.nix ({ pkgs, ... }:
       node.start()
     ''
       + runMongoDBTest pkgs.mongodb-3_4
-      + runMongoDBTest pkgs.mongodb-3_6 
+      + runMongoDBTest pkgs.mongodb-3_6
       + runMongoDBTest pkgs.mongodb-4_0
+      + runMongoDBTest pkgs.mongodb-4_2
       + ''
         node.shutdown()
       '';
diff --git a/nixos/tests/mysql/mysql.nix b/nixos/tests/mysql/mysql.nix
index 50e1c76e9fd0e..5437a2860437e 100644
--- a/nixos/tests/mysql/mysql.nix
+++ b/nixos/tests/mysql/mysql.nix
@@ -172,32 +172,32 @@ import ./../make-test-python.nix ({ pkgs, ...} : {
         "echo 'use testdb; select test_id from tests;' | sudo -u testuser mysql -u testuser -N | grep 42"
     )
 
-    # Check if TokuDB plugin works
+    # Check if RocksDB plugin works
     mariadb.succeed(
-        "echo 'use testdb; create table tokudb (test_id INT, PRIMARY KEY (test_id)) ENGINE = TokuDB;' | sudo -u testuser mysql -u testuser"
+        "echo 'use testdb; create table rocksdb (test_id INT, PRIMARY KEY (test_id)) ENGINE = RocksDB;' | sudo -u testuser mysql -u testuser"
     )
     mariadb.succeed(
-        "echo 'use testdb; insert into tokudb values (25);' | sudo -u testuser mysql -u testuser"
+        "echo 'use testdb; insert into rocksdb values (28);' | sudo -u testuser mysql -u testuser"
     )
     mariadb.succeed(
-        "echo 'use testdb; select test_id from tokudb;' | sudo -u testuser mysql -u testuser -N | grep 25"
+        "echo 'use testdb; select test_id from rocksdb;' | sudo -u testuser mysql -u testuser -N | grep 28"
     )
     mariadb.succeed(
-        "echo 'use testdb; drop table tokudb;' | sudo -u testuser mysql -u testuser"
+        "echo 'use testdb; drop table rocksdb;' | sudo -u testuser mysql -u testuser"
     )
-
-    # Check if RocksDB plugin works
+  '' + pkgs.stdenv.lib.optionalString pkgs.stdenv.isx86_64 ''
+    # Check if TokuDB plugin works
     mariadb.succeed(
-        "echo 'use testdb; create table rocksdb (test_id INT, PRIMARY KEY (test_id)) ENGINE = RocksDB;' | sudo -u testuser mysql -u testuser"
+        "echo 'use testdb; create table tokudb (test_id INT, PRIMARY KEY (test_id)) ENGINE = TokuDB;' | sudo -u testuser mysql -u testuser"
     )
     mariadb.succeed(
-        "echo 'use testdb; insert into rocksdb values (28);' | sudo -u testuser mysql -u testuser"
+        "echo 'use testdb; insert into tokudb values (25);' | sudo -u testuser mysql -u testuser"
     )
     mariadb.succeed(
-        "echo 'use testdb; select test_id from rocksdb;' | sudo -u testuser mysql -u testuser -N | grep 28"
+        "echo 'use testdb; select test_id from tokudb;' | sudo -u testuser mysql -u testuser -N | grep 25"
     )
     mariadb.succeed(
-        "echo 'use testdb; drop table rocksdb;' | sudo -u testuser mysql -u testuser"
+        "echo 'use testdb; drop table tokudb;' | sudo -u testuser mysql -u testuser"
     )
   '';
 })
diff --git a/nixos/tests/ncdns.nix b/nixos/tests/ncdns.nix
new file mode 100644
index 0000000000000..507e20fe7cc46
--- /dev/null
+++ b/nixos/tests/ncdns.nix
@@ -0,0 +1,77 @@
+import ./make-test-python.nix ({ pkgs, ... }:
+let
+  fakeReply = pkgs.writeText "namecoin-reply.json" ''
+  { "error": null,
+    "id": 1,
+    "result": {
+      "address": "T31q8ucJ4dI1xzhxQ5QispfECld5c7Xw",
+      "expired": false,
+      "expires_in": 2248,
+      "height": 438155,
+      "name": "d/test",
+      "txid": "db61c0b2540ba0c1a2c8cc92af703a37002e7566ecea4dbf8727c7191421edfb",
+      "value": "{\"ip\": \"1.2.3.4\", \"email\": \"root@test.bit\",\"info\": \"Fake record\"}",
+      "vout": 0
+    }
+  }
+  '';
+in
+
+{
+  name = "ncdns";
+
+  nodes.server = { ... }: {
+    networking.nameservers = [ "127.0.0.1" ];
+
+    services.namecoind.rpc = {
+      address = "127.0.0.1";
+      user = "namecoin";
+      password = "secret";
+      port = 8332;
+    };
+
+    # Fake namecoin RPC server because we can't
+    # run a full node in a test.
+    systemd.services.namecoind = {
+      wantedBy = [ "multi-user.target" ];
+      script = ''
+        while true; do
+          echo -e "HTTP/1.1 200 OK\n\n $(<${fakeReply})\n" \
+            | ${pkgs.netcat}/bin/nc -N -l 127.0.0.1 8332
+        done
+      '';
+    };
+
+    services.ncdns = {
+      enable = true;
+      dnssec.enable = true;
+    };
+
+    services.pdns-recursor = {
+      enable = true;
+      dns.allowFrom = [ "127.0.0.0/8" ];
+      settings.loglevel = 8;
+      resolveNamecoin = true;
+    };
+
+    environment.systemPackages = [ pkgs.dnsutils ];
+
+  };
+
+  testScript = ''
+    with subtest("DNSSEC keys have been generated"):
+        server.wait_for_unit("ncdns")
+        server.wait_for_file("/var/lib/ncdns/bit.key")
+        server.wait_for_file("/var/lib/ncdns/bit-zone.key")
+
+    with subtest("DNSKEY bit record is present"):
+        server.wait_for_unit("pdns-recursor")
+        server.wait_for_open_port("53")
+        server.succeed("host -t DNSKEY bit")
+
+    with subtest("can resolve a .bit name"):
+        server.wait_for_unit("namecoind")
+        server.wait_for_open_port("8332")
+        assert "1.2.3.4" in server.succeed("host -t A test.bit")
+  '';
+})
diff --git a/nixos/tests/networking.nix b/nixos/tests/networking.nix
index 3d8ab761a4464..83d4f6465b686 100644
--- a/nixos/tests/networking.nix
+++ b/nixos/tests/networking.nix
@@ -8,7 +8,9 @@ with import ../lib/testing-python.nix { inherit system pkgs; };
 with pkgs.lib;
 
 let
-  router = { config, pkgs, ... }:
+  qemu-flags = import ../lib/qemu-flags.nix { inherit pkgs; };
+
+  router = { config, pkgs, lib, ... }:
     with pkgs.lib;
     let
       vlanIfs = range 1 (length config.virtualisation.vlans);
@@ -30,17 +32,20 @@ let
       services.dhcpd4 = {
         enable = true;
         interfaces = map (n: "eth${toString n}") vlanIfs;
-        extraConfig = ''
-          authoritative;
-        '' + flip concatMapStrings vlanIfs (n: ''
+        extraConfig = flip concatMapStrings vlanIfs (n: ''
           subnet 192.168.${toString n}.0 netmask 255.255.255.0 {
             option routers 192.168.${toString n}.1;
-            # XXX: technically it's _not guaranteed_ that IP addresses will be
-            # issued from the first item in range onwards! We assume that in
-            # our tests however.
             range 192.168.${toString n}.2 192.168.${toString n}.254;
           }
-        '');
+        '')
+        ;
+        machines = flip map vlanIfs (vlan:
+          {
+            hostName = "client${toString vlan}";
+            ethernetAddress = qemu-flags.qemuNicMac vlan 1;
+            ipAddress = "192.168.${toString vlan}.2";
+          }
+        );
       };
       services.radvd = {
         enable = true;
diff --git a/nixos/tests/nextcloud/basic.nix b/nixos/tests/nextcloud/basic.nix
index a8fa0cae6f0f8..72fb020dca708 100644
--- a/nixos/tests/nextcloud/basic.nix
+++ b/nixos/tests/nextcloud/basic.nix
@@ -33,7 +33,6 @@ in {
 
       services.nextcloud = {
         enable = true;
-        nginx.enable = true;
         hostName = "nextcloud";
         config = {
           # Don't inherit adminuser since "root" is supposed to be the default
diff --git a/nixos/tests/nextcloud/with-mysql-and-memcached.nix b/nixos/tests/nextcloud/with-mysql-and-memcached.nix
index 8db630be893af..bec3815a3e14c 100644
--- a/nixos/tests/nextcloud/with-mysql-and-memcached.nix
+++ b/nixos/tests/nextcloud/with-mysql-and-memcached.nix
@@ -17,7 +17,6 @@ in {
       services.nextcloud = {
         enable = true;
         hostName = "nextcloud";
-        nginx.enable = true;
         https = true;
         caching = {
           apcu = true;
diff --git a/nixos/tests/nextcloud/with-postgresql-and-redis.nix b/nixos/tests/nextcloud/with-postgresql-and-redis.nix
index 95219cac9be8e..40a208115c32c 100644
--- a/nixos/tests/nextcloud/with-postgresql-and-redis.nix
+++ b/nixos/tests/nextcloud/with-postgresql-and-redis.nix
@@ -17,7 +17,6 @@ in {
       services.nextcloud = {
         enable = true;
         hostName = "nextcloud";
-        nginx.enable = true;
         caching = {
           apcu = false;
           redis = true;
diff --git a/nixos/tests/nfs/kerberos.nix b/nixos/tests/nfs/kerberos.nix
index 1f2d0d453ea0c..078f0b7814cea 100644
--- a/nixos/tests/nfs/kerberos.nix
+++ b/nixos/tests/nfs/kerberos.nix
@@ -3,7 +3,7 @@ import ../make-test-python.nix ({ pkgs, lib, ... }:
 with lib;
 
 let
-  krb5 = 
+  krb5 =
     { enable = true;
       domain_realm."nfs.test"   = "NFS.TEST";
       libdefaults.default_realm = "NFS.TEST";
@@ -31,7 +31,7 @@ in
 
 {
   name = "nfsv4-with-kerberos";
- 
+
   nodes = {
     client = { lib, ... }:
       { inherit krb5 users;
diff --git a/nixos/tests/pinnwand.nix b/nixos/tests/pinnwand.nix
new file mode 100644
index 0000000000000..2204e74b2c286
--- /dev/null
+++ b/nixos/tests/pinnwand.nix
@@ -0,0 +1,86 @@
+import ./make-test-python.nix ({ pkgs, ...}:
+let
+  pythonEnv = pkgs.python3.withPackages (py: with py; [ appdirs toml ]);
+
+  port = 8000;
+  baseUrl = "http://server:${toString port}";
+
+  configureSteck = pkgs.writeScript "configure.py" ''
+    #!${pythonEnv.interpreter}
+    import appdirs
+    import toml
+    import os
+
+    CONFIG = {
+      "base": "${baseUrl}/",
+      "confirm": False,
+      "magic": True,
+      "ignore": True
+    }
+
+    os.makedirs(appdirs.user_config_dir('steck'))
+    with open(os.path.join(appdirs.user_config_dir('steck'), 'steck.toml'), "w") as fd:
+        toml.dump(CONFIG, fd)
+    '';
+in
+{
+  name = "pinnwand";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers =[ hexa ];
+  };
+
+  nodes = {
+    server = { config, ... }:
+    {
+      networking.firewall.allowedTCPPorts = [
+        port
+      ];
+
+      services.pinnwand = {
+        enable = true;
+        port = port;
+      };
+    };
+
+    client = { pkgs, ... }:
+    {
+      environment.systemPackages = [ pkgs.steck ];
+    };
+  };
+
+  testScript = ''
+    start_all()
+
+    server.wait_for_unit("pinnwand.service")
+    client.wait_for_unit("network.target")
+
+    # create steck.toml config file
+    client.succeed("${configureSteck}")
+
+    # wait until the server running pinnwand is reachable
+    client.wait_until_succeeds("ping -c1 server")
+
+    # make sure pinnwand is listening
+    server.wait_until_succeeds("ss -lnp | grep ${toString port}")
+
+    # send the contents of /etc/machine-id
+    response = client.succeed("steck paste /etc/machine-id")
+
+    # parse the steck response
+    raw_url = None
+    removal_link = None
+    for line in response.split("\n"):
+        if line.startswith("View link:"):
+            raw_url = f"${baseUrl}/raw/{line.split('/')[-1]}"
+        if line.startswith("Removal link:"):
+            removal_link = line.split(":", 1)[1]
+
+    # check whether paste matches what we sent
+    client.succeed(f"curl {raw_url} > /tmp/machine-id")
+    client.succeed("diff /tmp/machine-id /etc/machine-id")
+
+    # remove paste and check that it's not available any more
+    client.succeed(f"curl {removal_link}")
+    client.fail(f"curl --fail {raw_url}")
+  '';
+})
diff --git a/nixos/tests/plasma5.nix b/nixos/tests/plasma5.nix
index 2eccfdf47f590..5a603f8cbfb5b 100644
--- a/nixos/tests/plasma5.nix
+++ b/nixos/tests/plasma5.nix
@@ -14,7 +14,7 @@ import ./make-test-python.nix ({ pkgs, ...} :
     services.xserver.displayManager.sddm.enable = true;
     services.xserver.displayManager.defaultSession = "plasma5";
     services.xserver.desktopManager.plasma5.enable = true;
-    services.xserver.displayManager.sddm.autoLogin = {
+    services.xserver.displayManager.autoLogin = {
       enable = true;
       user = "alice";
     };
diff --git a/nixos/tests/podman.nix b/nixos/tests/podman.nix
index 9134a68ff386c..cd8c2b4308c8d 100644
--- a/nixos/tests/podman.nix
+++ b/nixos/tests/podman.nix
@@ -12,9 +12,6 @@ import ./make-test-python.nix (
         { pkgs, ... }:
         {
           virtualisation.podman.enable = true;
-          virtualisation.containers.users = [
-            "alice"
-          ];
 
           users.users.alice = {
             isNormalUser = true;
diff --git a/nixos/tests/postfix-raise-smtpd-tls-security-level.nix b/nixos/tests/postfix-raise-smtpd-tls-security-level.nix
new file mode 100644
index 0000000000000..b3c2156122d20
--- /dev/null
+++ b/nixos/tests/postfix-raise-smtpd-tls-security-level.nix
@@ -0,0 +1,44 @@
+let
+  certs = import ./common/acme/server/snakeoil-certs.nix;
+in
+import ./make-test-python.nix {
+  name = "postfix";
+
+  machine = { pkgs, ... }: {
+    imports = [ common/user-account.nix ];
+    services.postfix = {
+      enable = true;
+      enableSubmissions = true;
+      submissionsOptions = {
+        smtpd_tls_security_level = "none";
+      };
+    };
+
+    environment.systemPackages = let
+      checkConfig = pkgs.writeScriptBin "check-config" ''
+        #!${pkgs.python3.interpreter}
+        import sys
+
+        state = 1
+        success = False
+
+        with open("/etc/postfix/master.cf") as masterCf:
+          for line in masterCf:
+            if state == 1 and line.startswith("submissions"):
+              state = 2
+            elif state == 2 and line.startswith(" ") and "smtpd_tls_security_level=encrypt" in line:
+              success = True
+            elif state == 2 and not line.startswith(" "):
+              state == 3
+        if not success:
+          sys.exit(1)
+      '';
+
+    in [ checkConfig ];
+  };
+
+  testScript = ''
+    machine.wait_for_unit("postfix.service")
+    machine.succeed("check-config")
+  '';
+}
diff --git a/nixos/tests/postfix.nix b/nixos/tests/postfix.nix
new file mode 100644
index 0000000000000..b0674ca3a0d29
--- /dev/null
+++ b/nixos/tests/postfix.nix
@@ -0,0 +1,76 @@
+let
+  certs = import ./common/acme/server/snakeoil-certs.nix;
+in
+import ./make-test-python.nix {
+  name = "postfix";
+
+  machine = { pkgs, ... }: {
+    imports = [ common/user-account.nix ];
+    services.postfix = {
+      enable = true;
+      enableSubmission = true;
+      enableSubmissions = true;
+      sslCACert = certs.ca.cert;
+      sslCert = certs."acme.test".cert;
+      sslKey = certs."acme.test".key;
+      submissionsOptions = {
+          smtpd_sasl_auth_enable = "yes";
+          smtpd_client_restrictions = "permit";
+          milter_macro_daemon_name = "ORIGINATING";
+      };
+    };
+
+    security.pki.certificateFiles = [
+      certs.ca.cert
+    ];
+
+    networking.extraHosts = ''
+      127.0.0.1 acme.test
+    '';
+
+    environment.systemPackages = let
+      sendTestMail = pkgs.writeScriptBin "send-testmail" ''
+        #!${pkgs.python3.interpreter}
+        import smtplib
+
+        with smtplib.SMTP('acme.test') as smtp:
+          smtp.sendmail('root@localhost', 'alice@localhost', 'Subject: Test\n\nTest data.')
+          smtp.quit()
+      '';
+
+      sendTestMailStarttls = pkgs.writeScriptBin "send-testmail-starttls" ''
+        #!${pkgs.python3.interpreter}
+        import smtplib
+        import ssl
+
+        ctx = ssl.create_default_context()
+
+        with smtplib.SMTP('acme.test') as smtp:
+          smtp.ehlo()
+          smtp.starttls(context=ctx)
+          smtp.ehlo()
+          smtp.sendmail('root@localhost', 'alice@localhost', 'Subject: Test STARTTLS\n\nTest data.')
+          smtp.quit()
+      '';
+
+      sendTestMailSmtps = pkgs.writeScriptBin "send-testmail-smtps" ''
+        #!${pkgs.python3.interpreter}
+        import smtplib
+        import ssl
+
+        ctx = ssl.create_default_context()
+
+        with smtplib.SMTP_SSL(host='acme.test', context=ctx) as smtp:
+          smtp.sendmail('root@localhost', 'alice@localhost', 'Subject: Test SMTPS\n\nTest data.')
+          smtp.quit()
+      '';
+    in [ sendTestMail sendTestMailStarttls sendTestMailSmtps ];
+  };
+
+  testScript = ''
+    machine.wait_for_unit("postfix.service")
+    machine.succeed("send-testmail")
+    machine.succeed("send-testmail-starttls")
+    machine.succeed("send-testmail-smtps")
+  '';
+}
diff --git a/nixos/tests/postgresql-wal-receiver.nix b/nixos/tests/postgresql-wal-receiver.nix
index 372dd9d8c1c11..c50746aa838e4 100644
--- a/nixos/tests/postgresql-wal-receiver.nix
+++ b/nixos/tests/postgresql-wal-receiver.nix
@@ -28,6 +28,10 @@ let
     meta.maintainers = with maintainers; [ pacien ];
 
     machine = { ... }: {
+      # Needed because this test uses a non-default 'services.postgresql.dataDir'.
+      systemd.tmpfiles.rules = [
+        "d /var/db/postgresql 0700 postgres postgres"
+      ];
       services.postgresql = {
         package = postgresqlPackage;
         enable = true;
diff --git a/nixos/tests/prometheus-exporters.nix b/nixos/tests/prometheus-exporters.nix
index 75c4ca12db24d..b912e3425e0ed 100644
--- a/nixos/tests/prometheus-exporters.nix
+++ b/nixos/tests/prometheus-exporters.nix
@@ -363,6 +363,31 @@ let
       '';
     };
 
+    modemmanager = {
+      exporterConfig = {
+        enable = true;
+        refreshRate = "10s";
+      };
+      metricProvider = {
+        # ModemManager is installed when NetworkManager is enabled. Ensure it is
+        # started and is wanted by NM and the exporter to start everything up
+        # in the right order.
+        networking.networkmanager.enable = true;
+        systemd.services.ModemManager = {
+          enable = true;
+          wantedBy = [ "NetworkManager.service" "prometheus-modemmanager-exporter.service" ];
+        };
+      };
+      exporterTest = ''
+        wait_for_unit("ModemManager.service")
+        wait_for_unit("prometheus-modemmanager-exporter.service")
+        wait_for_open_port(9539)
+        succeed(
+            "curl -sSf http://localhost:9539/metrics | grep -q 'modemmanager_info'"
+        )
+      '';
+    };
+
     nextcloud = {
       exporterConfig = {
         enable = true;
diff --git a/nixos/tests/prometheus.nix b/nixos/tests/prometheus.nix
index bce489168f9ff..af2aa66a55266 100644
--- a/nixos/tests/prometheus.nix
+++ b/nixos/tests/prometheus.nix
@@ -158,7 +158,10 @@ in import ./make-test-python.nix {
 
     s3 = { pkgs, ... } : {
       # Minio requires at least 1GiB of free disk space to run.
-      virtualisation.diskSize = 2 * 1024;
+      virtualisation = {
+        diskSize = 2 * 1024;
+        memorySize = 1024;
+      };
       networking.firewall.allowedTCPPorts = [ minioPort ];
 
       services.minio = {
@@ -235,7 +238,7 @@ in import ./make-test-python.nix {
     # Test if the Thanos bucket command is able to retrieve blocks from the S3 bucket
     # and check if the blocks have the correct labels:
     store.succeed(
-        "thanos bucket ls "
+        "thanos tools bucket ls "
         + "--objstore.config-file=${nodes.store.config.services.thanos.store.objstore.config-file} "
         + "--output=json | "
         + "jq .thanos.labels.some_label | "
diff --git a/nixos/tests/pt2-clone.nix b/nixos/tests/pt2-clone.nix
new file mode 100644
index 0000000000000..b502172e2ee5e
--- /dev/null
+++ b/nixos/tests/pt2-clone.nix
@@ -0,0 +1,35 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "pt2-clone";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ fgaz ];
+  };
+
+  machine = { config, pkgs, ... }: {
+    imports = [
+      ./common/x11.nix
+    ];
+
+    services.xserver.enable = true;
+    sound.enable = true;
+    environment.systemPackages = [ pkgs.pt2-clone ];
+  };
+
+  enableOCR = true;
+
+  testScript =
+    ''
+      machine.wait_for_x()
+      # Add a dummy sound card, or the program won't start
+      machine.execute("modprobe snd-dummy")
+
+      machine.execute("pt2-clone &")
+
+      machine.wait_for_window(r"ProTracker")
+      machine.sleep(5)
+      # One of the few words that actually get recognized
+      if "LENGTH" not in machine.get_screen_text():
+          raise Exception("Program did not start successfully")
+      machine.screenshot("screen")
+    '';
+})
+
diff --git a/nixos/tests/radicale.nix b/nixos/tests/radicale.nix
index c81e78a8f9943..1d3679c82a20c 100644
--- a/nixos/tests/radicale.nix
+++ b/nixos/tests/radicale.nix
@@ -14,9 +14,6 @@ let
 
         [storage]
         filesystem_folder = /tmp/collections
-
-        [logging]
-        debug = True
       '';
     };
     # WARNING: DON'T DO THIS IN PRODUCTION!
@@ -49,13 +46,18 @@ in
         services.radicale.extraArgs = [
           "--export-storage" "/tmp/collections-new"
         ];
+        system.stateVersion = "17.03";
       };
       radicale2_verify = lib.recursiveUpdate radicale2 {
-        services.radicale.extraArgs = [ "--verify-storage" ];
+        services.radicale.extraArgs = [ "--debug" "--verify-storage" ];
+        system.stateVersion = "17.09";
       };
       radicale2 = lib.recursiveUpdate (common args) {
         system.stateVersion = "17.09";
       };
+      radicale3 = lib.recursiveUpdate (common args) {
+        system.stateVersion = "20.09";
+      };
     };
 
     # This tests whether the web interface is accessible to an authenticated user
@@ -117,6 +119,22 @@ in
               retcode == 0 and "VCALENDAR" in output
           ), "Could not read calendar from Radicale 2"
 
-      radicale.succeed("curl --fail http://${user}:${password}@localhost:${port}/.web/")
+          radicale.succeed("curl --fail http://${user}:${password}@localhost:${port}/.web/")
+
+      with subtest("Check Radicale 3 functionality"):
+          radicale.succeed(
+              "${switchToConfig "radicale3"} >&2"
+          )
+          radicale.wait_for_unit("radicale.service")
+          radicale.wait_for_open_port(${port})
+
+          (retcode, output) = radicale.execute(
+              "curl --fail http://${user}:${password}@localhost:${port}/someuser/calendar.ics/"
+          )
+          assert (
+              retcode == 0 and "VCALENDAR" in output
+          ), "Could not read calendar from Radicale 3"
+
+          radicale.succeed("curl --fail http://${user}:${password}@localhost:${port}/.web/")
     '';
 })
diff --git a/nixos/tests/restic.nix b/nixos/tests/restic.nix
index 67bb7f1933d66..dad5bdfff27dc 100644
--- a/nixos/tests/restic.nix
+++ b/nixos/tests/restic.nix
@@ -4,33 +4,50 @@ import ./make-test-python.nix (
     let
       password = "some_password";
       repository = "/tmp/restic-backup";
-      passwordFile = pkgs.writeText "password" "correcthorsebatterystaple";
+      rcloneRepository = "rclone:local:/tmp/restic-rclone-backup";
+
+      passwordFile = "${pkgs.writeText "password" "correcthorsebatterystaple"}";
+      initialize = true;
+      paths = [ "/opt" ];
+      pruneOpts = [
+        "--keep-daily 2"
+        "--keep-weekly 1"
+        "--keep-monthly 1"
+        "--keep-yearly 99"
+      ];
     in
       {
         name = "restic";
 
         meta = with pkgs.stdenv.lib.maintainers; {
-          maintainers = [ bbigras ];
+          maintainers = [ bbigras i077 ];
         };
 
         nodes = {
           server =
-            { ... }:
+            { pkgs, ... }:
               {
                 services.restic.backups = {
                   remotebackup = {
-                    inherit repository;
-                    passwordFile = "${passwordFile}";
-                    initialize = true;
-                    paths = [ "/opt" ];
-                    pruneOpts = [
-                      "--keep-daily 2"
-                      "--keep-weekly 1"
-                      "--keep-monthly 1"
-                      "--keep-yearly 99"
-                    ];
+                    inherit repository passwordFile initialize paths pruneOpts;
+                  };
+                  rclonebackup = {
+                    repository = rcloneRepository;
+                    rcloneConfig = {
+                      type = "local";
+                      one_file_system = true;
+                    };
+
+                    # This gets overridden by rcloneConfig.type
+                    rcloneConfigFile = pkgs.writeText "rclone.conf" ''
+                      [local]
+                      type=ftp
+                    '';
+                    inherit passwordFile initialize paths pruneOpts;
                   };
                 };
+
+                environment.sessionVariables.RCLONE_CONFIG_LOCAL_TYPE = "local";
               };
         };
 
@@ -38,25 +55,35 @@ import ./make-test-python.nix (
           server.start()
           server.wait_for_unit("dbus.socket")
           server.fail(
-              "${pkgs.restic}/bin/restic -r ${repository} -p ${passwordFile} snapshots"
+              "${pkgs.restic}/bin/restic -r ${repository} -p ${passwordFile} snapshots",
+              "${pkgs.restic}/bin/restic -r ${rcloneRepository} -p ${passwordFile} snapshots",
           )
           server.succeed(
               "mkdir -p /opt",
               "touch /opt/some_file",
+              "mkdir -p /tmp/restic-rclone-backup",
               "timedatectl set-time '2016-12-13 13:45'",
               "systemctl start restic-backups-remotebackup.service",
+              "systemctl start restic-backups-rclonebackup.service",
               '${pkgs.restic}/bin/restic -r ${repository} -p ${passwordFile} snapshots -c | grep -e "^1 snapshot"',
+              '${pkgs.restic}/bin/restic -r ${rcloneRepository} -p ${passwordFile} snapshots -c | grep -e "^1 snapshot"',
               "timedatectl set-time '2017-12-13 13:45'",
               "systemctl start restic-backups-remotebackup.service",
+              "systemctl start restic-backups-rclonebackup.service",
               "timedatectl set-time '2018-12-13 13:45'",
               "systemctl start restic-backups-remotebackup.service",
+              "systemctl start restic-backups-rclonebackup.service",
               "timedatectl set-time '2018-12-14 13:45'",
               "systemctl start restic-backups-remotebackup.service",
+              "systemctl start restic-backups-rclonebackup.service",
               "timedatectl set-time '2018-12-15 13:45'",
               "systemctl start restic-backups-remotebackup.service",
+              "systemctl start restic-backups-rclonebackup.service",
               "timedatectl set-time '2018-12-16 13:45'",
               "systemctl start restic-backups-remotebackup.service",
+              "systemctl start restic-backups-rclonebackup.service",
               '${pkgs.restic}/bin/restic -r ${repository} -p ${passwordFile} snapshots -c | grep -e "^4 snapshot"',
+              '${pkgs.restic}/bin/restic -r ${rcloneRepository} -p ${passwordFile} snapshots -c | grep -e "^4 snapshot"',
           )
         '';
       }
diff --git a/nixos/tests/sddm.nix b/nixos/tests/sddm.nix
index a145705250f71..f9b961163c3c1 100644
--- a/nixos/tests/sddm.nix
+++ b/nixos/tests/sddm.nix
@@ -44,8 +44,8 @@ let
       machine = { ... }: {
         imports = [ ./common/user-account.nix ];
         services.xserver.enable = true;
-        services.xserver.displayManager.sddm = {
-          enable = true;
+        services.xserver.displayManager = {
+          sddm.enable = true;
           autoLogin = {
             enable = true;
             user = "alice";
diff --git a/nixos/tests/shattered-pixel-dungeon.nix b/nixos/tests/shattered-pixel-dungeon.nix
new file mode 100644
index 0000000000000..cf6ee8db80b22
--- /dev/null
+++ b/nixos/tests/shattered-pixel-dungeon.nix
@@ -0,0 +1,29 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "shattered-pixel-dungeon";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ fgaz ];
+  };
+
+  machine = { config, pkgs, ... }: {
+    imports = [
+      ./common/x11.nix
+    ];
+
+    services.xserver.enable = true;
+    environment.systemPackages = [ pkgs.shattered-pixel-dungeon ];
+  };
+
+  enableOCR = true;
+
+  testScript =
+    ''
+      machine.wait_for_x()
+      machine.execute("shattered-pixel-dungeon &")
+      machine.wait_for_window(r"Shattered Pixel Dungeon")
+      machine.sleep(5)
+      if "Enter" not in machine.get_screen_text():
+          raise Exception("Program did not start successfully")
+      machine.screenshot("screen")
+    '';
+})
+
diff --git a/nixos/tests/slurm.nix b/nixos/tests/slurm.nix
index d0e62d15437c0..a54c5d9db4826 100644
--- a/nixos/tests/slurm.nix
+++ b/nixos/tests/slurm.nix
@@ -1,16 +1,52 @@
-import ./make-test-python.nix ({ lib, ... }:
+import ./make-test-python.nix ({ lib, pkgs, ... }:
 let
-    mungekey = "mungeverryweakkeybuteasytointegratoinatest";
-
     slurmconfig = {
-      controlMachine = "control";
-      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
-      '';
+      services.slurm = {
+        controlMachine = "control";
+        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
+        '';
+      };
+      environment.systemPackages = [ mpitest ];
+      networking.firewall.enable = false;
+      systemd.tmpfiles.rules = [
+        "f /etc/munge/munge.key 0400 munge munge - mungeverryweakkeybuteasytointegratoinatest"
+      ];
     };
+
+    mpitest = let
+      mpitestC = pkgs.writeText "mpitest.c" ''
+        #include <stdio.h>
+        #include <stdlib.h>
+        #include <mpi.h>
+
+        int
+        main (int argc, char *argv[])
+        {
+          int rank, size, length;
+          char name[512];
+
+          MPI_Init (&argc, &argv);
+          MPI_Comm_rank (MPI_COMM_WORLD, &rank);
+          MPI_Comm_size (MPI_COMM_WORLD, &size);
+          MPI_Get_processor_name (name, &length);
+
+          if ( rank == 0 ) printf("size=%d\n", size);
+
+          printf ("%s: hello world from process %d of %d\n", name, rank, size);
+
+          MPI_Finalize ();
+
+          return EXIT_SUCCESS;
+        }
+      '';
+    in pkgs.runCommandNoCC "mpitest" {} ''
+      mkdir -p $out/bin
+      ${pkgs.openmpi}/bin/mpicc ${mpitestC} -o $out/bin/mpitest
+    '';
 in {
   name = "slurm";
 
@@ -21,37 +57,40 @@ in {
     computeNode =
       { ...}:
       {
+        imports = [ slurmconfig ];
         # TODO slurmd port and slurmctld port should be configurations and
         # automatically allowed by the  firewall.
-        networking.firewall.enable = false;
         services.slurm = {
           client.enable = true;
-        } // slurmconfig;
+        };
       };
     in {
 
     control =
       { ...}:
       {
-        networking.firewall.enable = false;
+        imports = [ slurmconfig ];
         services.slurm = {
           server.enable = true;
-        } // slurmconfig;
+        };
       };
 
     submit =
       { ...}:
       {
-        networking.firewall.enable = false;
+        imports = [ slurmconfig ];
         services.slurm = {
           enableStools = true;
-        } // slurmconfig;
+        };
       };
 
     dbd =
       { pkgs, ... } :
       {
         networking.firewall.enable = false;
+        systemd.tmpfiles.rules = [
+          "f /etc/munge/munge.key 0400 munge munge - mungeverryweakkeybuteasytointegratoinatest"
+        ];
         services.slurm.dbdserver = {
           enable = true;
           storagePass = "password123";
@@ -87,24 +126,7 @@ in {
   ''
   start_all()
 
-  # Set up authentification across the cluster
-  for node in [submit, control, dbd, node1, node2, node3]:
-
-      node.wait_for_unit("default.target")
-
-      node.succeed("mkdir /etc/munge")
-      node.succeed(
-          "echo '${mungekey}' > /etc/munge/munge.key"
-      )
-      node.succeed("chmod 0400 /etc/munge/munge.key")
-      node.succeed("chown munge:munge /etc/munge/munge.key")
-      node.succeed("systemctl restart munged")
-
-      node.wait_for_unit("munged")
-
-
-  # Restart the services since they have probably failed due to the munge init
-  # failure
+  # Make sure DBD is up after DB initialzation
   with subtest("can_start_slurmdbd"):
       dbd.succeed("systemctl restart slurmdbd")
       dbd.wait_for_unit("slurmdbd.service")
@@ -137,5 +159,8 @@ in {
           # find the srun job from above in the database
           control.succeed("sleep 5")
           control.succeed("sacct | grep hostname")
+
+  with subtest("run_PMIx_mpitest"):
+      submit.succeed("srun -N 3 --mpi=pmix mpitest | grep size=3")
   '';
 })
diff --git a/nixos/tests/snapcast.nix b/nixos/tests/snapcast.nix
new file mode 100644
index 0000000000000..92534f1028190
--- /dev/null
+++ b/nixos/tests/snapcast.nix
@@ -0,0 +1,58 @@
+import ./make-test-python.nix ({ pkgs, ...} :
+
+let
+  port = 10004;
+  tcpPort = 10005;
+  httpPort = 10080;
+in {
+  name = "snapcast";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ hexa ];
+  };
+
+  nodes = {
+    server = {
+      services.snapserver = {
+        enable = true;
+        port = port;
+        tcp.port = tcpPort;
+        http.port = httpPort;
+        streams = {
+          mpd = {
+            type = "pipe";
+            location = "/run/snapserver/mpd";
+          };
+          bluetooth = {
+            type = "pipe";
+            location = "/run/snapserver/bluetooth";
+          };
+        };
+      };
+    };
+  };
+
+  testScript = ''
+    import json
+
+    get_rpc_version = {"id": "1", "jsonrpc": "2.0", "method": "Server.GetRPCVersion"}
+
+    start_all()
+
+    server.wait_for_unit("snapserver.service")
+    server.wait_until_succeeds("ss -ntl | grep -q ${toString port}")
+    server.wait_until_succeeds("ss -ntl | grep -q ${toString tcpPort}")
+    server.wait_until_succeeds("ss -ntl | grep -q ${toString httpPort}")
+
+    with subtest("check that pipes are created"):
+        server.succeed("test -p /run/snapserver/mpd")
+        server.succeed("test -p /run/snapserver/bluetooth")
+
+    with subtest("test tcp json-rpc"):
+        server.succeed(f"echo '{json.dumps(get_rpc_version)}' | nc -w 1 localhost ${toString tcpPort}")
+
+    with subtest("test http json-rpc"):
+        server.succeed(
+            "curl --fail http://localhost:${toString httpPort}/jsonrpc -d '{json.dumps(get_rpc_version)}'"
+        )
+  '';
+})
diff --git a/nixos/tests/sslh.nix b/nixos/tests/sslh.nix
new file mode 100644
index 0000000000000..2a800aa52d0aa
--- /dev/null
+++ b/nixos/tests/sslh.nix
@@ -0,0 +1,83 @@
+import ./make-test-python.nix {
+  name = "sslh";
+
+  nodes = {
+    server = { pkgs, lib, ... }: {
+      networking.firewall.allowedTCPPorts = [ 443 ];
+      networking.interfaces.eth1.ipv6.addresses = [
+        {
+          address = "fe00:aa:bb:cc::2";
+          prefixLength = 64;
+        }
+      ];
+      # sslh is really slow when reverse dns does not work
+      networking.hosts = {
+        "fe00:aa:bb:cc::2" = [ "server" ];
+        "fe00:aa:bb:cc::1" = [ "client" ];
+      };
+      services.sslh = {
+        enable = true;
+        transparent = true;
+        appendConfig = ''
+          protocols:
+          (
+            { name: "ssh"; service: "ssh"; host: "localhost"; port: "22"; probe: "builtin"; },
+            { name: "http"; host: "localhost"; port: "80"; probe: "builtin"; },
+          );
+        '';
+      };
+      services.openssh.enable = true;
+      users.users.root.openssh.authorizedKeys.keyFiles = [ ./initrd-network-ssh/id_ed25519.pub ];
+      services.nginx = {
+        enable = true;
+        virtualHosts."localhost" = {
+          addSSL = false;
+          default = true;
+          root = pkgs.runCommand "testdir" {} ''
+            mkdir "$out"
+            echo hello world > "$out/index.html"
+          '';
+        };
+      };
+    };
+    client = { ... }: {
+      networking.interfaces.eth1.ipv6.addresses = [
+        {
+          address = "fe00:aa:bb:cc::1";
+          prefixLength = 64;
+        }
+      ];
+      networking.hosts."fe00:aa:bb:cc::2" = [ "server" ];
+      environment.etc.sshKey = {
+        source = ./initrd-network-ssh/id_ed25519; # dont use this anywhere else
+        mode = "0600";
+      };
+    };
+  };
+
+  testScript = ''
+    start_all()
+
+    server.wait_for_unit("sslh.service")
+    server.wait_for_unit("nginx.service")
+    server.wait_for_unit("sshd.service")
+    server.wait_for_open_port(80)
+    server.wait_for_open_port(443)
+    server.wait_for_open_port(22)
+
+    for arg in ["-6", "-4"]:
+        client.wait_until_succeeds(f"ping {arg} -c1 server")
+
+        # check that ssh through sslh works
+        client.succeed(
+            f"ssh {arg} -p 443 -i /etc/sshKey -o StrictHostKeyChecking=accept-new server 'echo $SSH_CONNECTION > /tmp/foo{arg}'"
+        )
+
+        # check that 1/ the above ssh command had an effect 2/ transparent proxying really works
+        ip = "fe00:aa:bb:cc::1" if arg == "-6" else "192.168.1."
+        server.succeed(f"grep '{ip}' /tmp/foo{arg}")
+
+        # check that http through sslh works
+        assert client.succeed(f"curl {arg} http://server:443").strip() == "hello world"
+  '';
+}
diff --git a/nixos/tests/sudo.nix b/nixos/tests/sudo.nix
index 5bbec3d572691..8c38f1b47ef0c 100644
--- a/nixos/tests/sudo.nix
+++ b/nixos/tests/sudo.nix
@@ -74,7 +74,7 @@ in
         with subtest("test5 user should not be able to run commands under root"):
             machine.fail("sudo -u test5 sudo -n -u root true")
 
-        with subtest("test5 user should be able to keep his environment"):
+        with subtest("test5 user should be able to keep their environment"):
             machine.succeed("sudo -u test5 sudo -n -E -u test1 true")
 
         with subtest("users in group 'barfoo' should not be able to keep their environment"):
diff --git a/nixos/tests/syncthing-init.nix b/nixos/tests/syncthing-init.nix
index 9c8e0a3d087ef..0a01da52b68bc 100644
--- a/nixos/tests/syncthing-init.nix
+++ b/nixos/tests/syncthing-init.nix
@@ -24,9 +24,8 @@ in {
   testScript = ''
     machine.wait_for_unit("syncthing-init.service")
     config = machine.succeed("cat /var/lib/syncthing/.config/syncthing/config.xml")
-   
+
     assert "testFolder" in config
     assert "${testId}" in config
   '';
 })
-
diff --git a/nixos/tests/syncthing.nix b/nixos/tests/syncthing.nix
new file mode 100644
index 0000000000000..9e2a8e01e3fb8
--- /dev/null
+++ b/nixos/tests/syncthing.nix
@@ -0,0 +1,65 @@
+import ./make-test-python.nix ({ lib, pkgs, ... }: {
+  name = "syncthing";
+  meta.maintainers = with pkgs.stdenv.lib.maintainers; [ chkno ];
+
+  nodes = rec {
+    a = {
+      environment.systemPackages = with pkgs; [ curl libxml2 syncthing ];
+      services.syncthing = {
+        enable = true;
+        openDefaultPorts = true;
+      };
+    };
+    b = a;
+  };
+
+  testScript = ''
+    import json
+    import shlex
+
+    confdir = "/var/lib/syncthing/.config/syncthing"
+
+
+    def addPeer(host, name, deviceID):
+        APIKey = host.succeed(
+            "xmllint --xpath 'string(configuration/gui/apikey)' %s/config.xml" % confdir
+        ).strip()
+        oldConf = host.succeed(
+            "curl -Ss -H 'X-API-Key: %s' 127.0.0.1:8384/rest/system/config" % APIKey
+        )
+        conf = json.loads(oldConf)
+        conf["devices"].append({"deviceID": deviceID, "id": name})
+        conf["folders"].append(
+            {
+                "devices": [{"deviceID": deviceID}],
+                "id": "foo",
+                "path": "/var/lib/syncthing/foo",
+                "rescanIntervalS": 1,
+            }
+        )
+        newConf = json.dumps(conf)
+        host.succeed(
+            "curl -Ss -H 'X-API-Key: %s' 127.0.0.1:8384/rest/system/config -d %s"
+            % (APIKey, shlex.quote(newConf))
+        )
+
+
+    start_all()
+    a.wait_for_unit("syncthing.service")
+    b.wait_for_unit("syncthing.service")
+    a.wait_for_open_port(22000)
+    b.wait_for_open_port(22000)
+
+    aDeviceID = a.succeed("syncthing -home=%s -device-id" % confdir).strip()
+    bDeviceID = b.succeed("syncthing -home=%s -device-id" % confdir).strip()
+    addPeer(a, "b", bDeviceID)
+    addPeer(b, "a", aDeviceID)
+
+    a.wait_for_file("/var/lib/syncthing/foo")
+    b.wait_for_file("/var/lib/syncthing/foo")
+    a.succeed("echo a2b > /var/lib/syncthing/foo/a2b")
+    b.succeed("echo b2a > /var/lib/syncthing/foo/b2a")
+    a.wait_for_file("/var/lib/syncthing/foo/b2a")
+    b.wait_for_file("/var/lib/syncthing/foo/a2b")
+  '';
+})
diff --git a/nixos/tests/systemd-boot.nix b/nixos/tests/systemd-boot.nix
index eba4729d6de84..7a663dd9b4284 100644
--- a/nixos/tests/systemd-boot.nix
+++ b/nixos/tests/systemd-boot.nix
@@ -11,6 +11,8 @@ let
     virtualisation.useBootLoader = true;
     virtualisation.useEFIBoot = true;
     boot.loader.systemd-boot.enable = true;
+    boot.loader.efi.canTouchEfiVariables = true;
+    environment.systemPackages = [ pkgs.efibootmgr ];
   };
 in
 {
@@ -31,6 +33,36 @@ in
       machine.succeed(
           "test -e /sys/firmware/efi/efivars/LoaderEntrySelected-4a67b082-0a4c-41cf-b6c7-440b29bb8c4f"
       )
+
+      # "bootctl install" should have created an EFI entry
+      machine.succeed('efibootmgr | grep "Linux Boot Manager"')
+    '';
+  };
+
+  # Boot without having created an EFI entry--instead using default "/EFI/BOOT/BOOTX64.EFI"
+  fallback = makeTest {
+    name = "systemd-boot-fallback";
+    meta.maintainers = with pkgs.stdenv.lib.maintainers; [ danielfullmer ];
+
+    machine = { pkgs, lib, ... }: {
+      imports = [ common ];
+      boot.loader.efi.canTouchEfiVariables = mkForce false;
+    };
+
+    testScript = ''
+      machine.start()
+      machine.wait_for_unit("multi-user.target")
+
+      machine.succeed("test -e /boot/loader/entries/nixos-generation-1.conf")
+
+      # Ensure we actually booted using systemd-boot
+      # Magic number is the vendor UUID used by systemd-boot.
+      machine.succeed(
+          "test -e /sys/firmware/efi/efivars/LoaderEntrySelected-4a67b082-0a4c-41cf-b6c7-440b29bb8c4f"
+      )
+
+      # "bootctl install" should _not_ have created an EFI entry
+      machine.fail('efibootmgr | grep "Linux Boot Manager"')
     '';
   };
 
diff --git a/nixos/tests/systemd-networkd-vrf.nix b/nixos/tests/systemd-networkd-vrf.nix
index af7813a2e604a..bd4751f8e4359 100644
--- a/nixos/tests/systemd-networkd-vrf.nix
+++ b/nixos/tests/systemd-networkd-vrf.nix
@@ -159,6 +159,8 @@ in {
     node2.wait_for_unit("network.target")
     node3.wait_for_unit("network.target")
 
+    # NOTE: please keep in mind that the trailing whitespaces in the following strings
+    # are intentional as the output is compared against the raw `iproute2`-output.
     client_ipv4_table = """
     192.168.1.2 dev vrf1 proto static metric 100 
     192.168.2.3 dev vrf2 proto static metric 100
@@ -194,18 +196,16 @@ in {
         client.succeed("ping -c5 192.168.1.2")
         client.succeed("ping -c5 192.168.2.3")
 
-    # Test whether SSH through a VRF IP is possible.
-    # (Note: this seems to be an issue on Linux 5.x, so I decided to add this to
-    # ensure that we catch this when updating the default kernel).
-    # with subtest("tcp traffic through vrf works"):
-    #     node1.wait_for_open_port(22)
-    #     client.succeed(
-    #         "cat ${snakeOilPrivateKey} > privkey.snakeoil"
-    #     )
-    #     client.succeed("chmod 600 privkey.snakeoil")
-    #     client.succeed(
-    #         "ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -i privkey.snakeoil root@192.168.1.2 true"
-    #     )
+    # Test whether TCP through a VRF IP is possible.
+    with subtest("tcp traffic through vrf works"):
+        node1.wait_for_open_port(22)
+        client.succeed(
+            "cat ${snakeOilPrivateKey} > privkey.snakeoil"
+        )
+        client.succeed("chmod 600 privkey.snakeoil")
+        client.succeed(
+            "ulimit -l 2048; ip vrf exec vrf1 ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -i privkey.snakeoil root@192.168.1.2 true"
+        )
 
     # Only configured routes through the VRF from the main routing table should
     # work. Additional IPs are only reachable when binding to the vrf interface.
diff --git a/nixos/tests/systemd.nix b/nixos/tests/systemd.nix
index ca2e36a443e99..a653932fb37c7 100644
--- a/nixos/tests/systemd.nix
+++ b/nixos/tests/systemd.nix
@@ -50,6 +50,13 @@ import ./make-test-python.nix ({ pkgs, ... }: {
         fi
       '';
     };
+
+    systemd.watchdog = {
+      device = "/dev/watchdog";
+      runtimeTime = "30s";
+      rebootTime = "10min";
+      kexecTime = "5min";
+    };
   };
 
   testScript = ''
@@ -97,6 +104,11 @@ import ./make-test-python.nix ({ pkgs, ... }: {
             re.search(r"^Filesystem state: *clean$", extinfo, re.MULTILINE) is not None
         ), ("File system was not cleanly unmounted: " + extinfo)
 
+    # Regression test for https://github.com/NixOS/nixpkgs/pull/91232
+    with subtest("setting transient hostnames works"):
+        machine.succeed("hostnamectl set-hostname --transient machine-transient")
+        machine.fail("hostnamectl set-hostname machine-all")
+
     with subtest("systemd-shutdown works"):
         machine.shutdown()
         machine.wait_for_unit("multi-user.target")
@@ -117,5 +129,20 @@ import ./make-test-python.nix ({ pkgs, ... }: {
         retcode, output = machine.execute("systemctl status testservice1.service")
         assert retcode in [0, 3]  # https://bugs.freedesktop.org/show_bug.cgi?id=77507
         assert "CPU:" in output
+
+    # Test systemd is configured to manage a watchdog
+    with subtest("systemd manages hardware watchdog"):
+        machine.wait_for_unit("multi-user.target")
+
+        # It seems that the device's path doesn't appear in 'systemctl show' so
+        # check it separately.
+        assert "WatchdogDevice=/dev/watchdog" in machine.succeed(
+            "cat /etc/systemd/system.conf"
+        )
+
+        output = machine.succeed("systemctl show | grep Watchdog")
+        assert "RuntimeWatchdogUSec=30s" in output
+        assert "RebootWatchdogUSec=10m" in output
+        assert "KExecWatchdogUSec=5m" in output
   '';
 })
diff --git a/nixos/tests/taskserver.nix b/nixos/tests/taskserver.nix
index ab9b589f85930..f34782c7059a9 100644
--- a/nixos/tests/taskserver.nix
+++ b/nixos/tests/taskserver.nix
@@ -1,4 +1,4 @@
-import ./make-test.nix ({ pkgs, ... }: let
+import ./make-test-python.nix ({ pkgs, ... }: let
   snakeOil = pkgs.runCommand "snakeoil-certs" {
     outputs = [ "out" "cacert" "cert" "key" "crl" ];
     buildInputs = [ pkgs.gnutls.bin ];
@@ -105,186 +105,178 @@ in {
     newServerSystem = nodes.newServer.config.system.build.toplevel;
     switchToNewServer = "${newServerSystem}/bin/switch-to-configuration test";
   in ''
-    sub su ($$) {
-      my ($user, $cmd) = @_;
-      my $esc = $cmd =~ s/'/'\\${"'"}'/gr;
-      return "su - $user -c '$esc'";
-    }
-
-    sub setupClientsFor ($$;$) {
-      my ($org, $user, $extraInit) = @_;
-
-      for my $client ($client1, $client2) {
-        $client->nest("initialize client for user $user", sub {
-          $client->succeed(
-            (su $user, "rm -rf /home/$user/.task"),
-            (su $user, "task rc.confirmation=no config confirmation no")
-          );
-
-          my $exportinfo = $server->succeed(
-            "nixos-taskserver user export $org $user"
-          );
-
-          $exportinfo =~ s/'/'\\'''/g;
-
-          $client->nest("importing taskwarrior configuration", sub {
-            my $cmd = su $user, "eval '$exportinfo' >&2";
-            my ($status, $out) = $client->execute_($cmd);
-            if ($status != 0) {
-              $client->log("output: $out");
-              die "command `$cmd' did not succeed (exit code $status)\n";
-            }
-          });
-
-          eval { &$extraInit($client, $org, $user) };
-
-          $client->succeed(su $user,
-            "task config taskd.server server:${portStr} >&2"
-          );
-
-          $client->succeed(su $user, "task sync init >&2");
-        });
-      }
-    }
-
-    sub restartServer {
-      $server->succeed("systemctl restart taskserver.service");
-      $server->waitForOpenPort(${portStr});
-    }
-
-    sub readdImperativeUser {
-      $server->nest("(re-)add imperative user bar", sub {
-        $server->execute("nixos-taskserver org remove imperativeOrg");
-        $server->succeed(
-          "nixos-taskserver org add imperativeOrg",
-          "nixos-taskserver user add imperativeOrg bar"
-        );
-        setupClientsFor "imperativeOrg", "bar";
-      });
-    }
-
-    sub testSync ($) {
-      my $user = $_[0];
-      subtest "sync for user $user", sub {
-        $client1->succeed(su $user, "task add foo >&2");
-        $client1->succeed(su $user, "task sync >&2");
-        $client2->fail(su $user, "task list >&2");
-        $client2->succeed(su $user, "task sync >&2");
-        $client2->succeed(su $user, "task list >&2");
-      };
-    }
-
-    sub checkClientCert ($) {
-      my $user = $_[0];
-      my $cmd = "gnutls-cli".
-        " --x509cafile=/home/$user/.task/keys/ca.cert".
-        " --x509keyfile=/home/$user/.task/keys/private.key".
-        " --x509certfile=/home/$user/.task/keys/public.cert".
-        " --port=${portStr} server < /dev/null";
-      return su $user, $cmd;
-    }
+    from shlex import quote
+
+
+    def su(user, cmd):
+        return f"su - {user} -c {quote(cmd)}"
+
+
+    def no_extra_init(client, org, user):
+        pass
+
+
+    def setup_clients_for(org, user, extra_init=no_extra_init):
+        for client in [client1, client2]:
+            with client.nested(f"initialize client for user {user}"):
+                client.succeed(
+                    su(user, f"rm -rf /home/{user}/.task"),
+                    su(user, "task rc.confirmation=no config confirmation no"),
+                )
+
+                exportinfo = server.succeed(f"nixos-taskserver user export {org} {user}")
+
+                with client.nested("importing taskwarrior configuration"):
+                    client.succeed(su(user, f"eval {quote(exportinfo)} >&2"))
+
+                extra_init(client, org, user)
+
+                client.succeed(su(user, "task config taskd.server server:${portStr} >&2"))
+
+                client.succeed(su(user, "task sync init >&2"))
+
+
+    def restart_server():
+        server.systemctl("restart taskserver.service")
+        server.wait_for_open_port(${portStr})
+
+
+    def re_add_imperative_user():
+        with server.nested("(re-)add imperative user bar"):
+            server.execute("nixos-taskserver org remove imperativeOrg")
+            server.succeed(
+                "nixos-taskserver org add imperativeOrg",
+                "nixos-taskserver user add imperativeOrg bar",
+            )
+            setup_clients_for("imperativeOrg", "bar")
+
+
+    def test_sync(user):
+        with subtest(f"sync for user {user}"):
+            client1.succeed(su(user, "task add foo >&2"))
+            client1.succeed(su(user, "task sync >&2"))
+            client2.fail(su(user, "task list >&2"))
+            client2.succeed(su(user, "task sync >&2"))
+            client2.succeed(su(user, "task list >&2"))
+
+
+    def check_client_cert(user):
+        # debug level 3 is a workaround for gnutls issue https://gitlab.com/gnutls/gnutls/-/issues/1040
+        cmd = (
+            f"gnutls-cli -d 3"
+            f" --x509cafile=/home/{user}/.task/keys/ca.cert"
+            f" --x509keyfile=/home/{user}/.task/keys/private.key"
+            f" --x509certfile=/home/{user}/.task/keys/public.cert"
+            f" --port=${portStr} server < /dev/null"
+        )
+        return su(user, cmd)
+
 
     # Explicitly start the VMs so that we don't accidentally start newServer
-    $server->start;
-    $client1->start;
-    $client2->start;
+    server.start()
+    client1.start()
+    client2.start()
 
-    $server->waitForUnit("taskserver.service");
+    server.wait_for_unit("taskserver.service")
 
-    $server->succeed(
-      "nixos-taskserver user list testOrganisation | grep -qxF alice",
-      "nixos-taskserver user list testOrganisation | grep -qxF foo",
-      "nixos-taskserver user list anotherOrganisation | grep -qxF bob"
-    );
+    server.succeed(
+        "nixos-taskserver user list testOrganisation | grep -qxF alice",
+        "nixos-taskserver user list testOrganisation | grep -qxF foo",
+        "nixos-taskserver user list anotherOrganisation | grep -qxF bob",
+    )
 
-    $server->waitForOpenPort(${portStr});
+    server.wait_for_open_port(${portStr})
 
-    $client1->waitForUnit("multi-user.target");
-    $client2->waitForUnit("multi-user.target");
+    client1.wait_for_unit("multi-user.target")
+    client2.wait_for_unit("multi-user.target")
 
-    setupClientsFor "testOrganisation", "alice";
-    setupClientsFor "testOrganisation", "foo";
-    setupClientsFor "anotherOrganisation", "bob";
+    setup_clients_for("testOrganisation", "alice")
+    setup_clients_for("testOrganisation", "foo")
+    setup_clients_for("anotherOrganisation", "bob")
 
-    testSync $_ for ("alice", "bob", "foo");
+    for user in ["alice", "bob", "foo"]:
+        test_sync(user)
 
-    $server->fail("nixos-taskserver user add imperativeOrg bar");
-    readdImperativeUser;
+    server.fail("nixos-taskserver user add imperativeOrg bar")
+    re_add_imperative_user()
 
-    testSync "bar";
+    test_sync("bar")
 
-    subtest "checking certificate revocation of user bar", sub {
-      $client1->succeed(checkClientCert "bar");
+    with subtest("checking certificate revocation of user bar"):
+        client1.succeed(check_client_cert("bar"))
 
-      $server->succeed("nixos-taskserver user remove imperativeOrg bar");
-      restartServer;
+        server.succeed("nixos-taskserver user remove imperativeOrg bar")
+        restart_server()
 
-      $client1->fail(checkClientCert "bar");
+        client1.fail(check_client_cert("bar"))
 
-      $client1->succeed(su "bar", "task add destroy everything >&2");
-      $client1->fail(su "bar", "task sync >&2");
-    };
+        client1.succeed(su("bar", "task add destroy everything >&2"))
+        client1.fail(su("bar", "task sync >&2"))
 
-    readdImperativeUser;
+    re_add_imperative_user()
 
-    subtest "checking certificate revocation of org imperativeOrg", sub {
-      $client1->succeed(checkClientCert "bar");
+    with subtest("checking certificate revocation of org imperativeOrg"):
+        client1.succeed(check_client_cert("bar"))
 
-      $server->succeed("nixos-taskserver org remove imperativeOrg");
-      restartServer;
+        server.succeed("nixos-taskserver org remove imperativeOrg")
+        restart_server()
 
-      $client1->fail(checkClientCert "bar");
+        client1.fail(check_client_cert("bar"))
 
-      $client1->succeed(su "bar", "task add destroy even more >&2");
-      $client1->fail(su "bar", "task sync >&2");
-    };
+        client1.succeed(su("bar", "task add destroy even more >&2"))
+        client1.fail(su("bar", "task sync >&2"))
 
-    readdImperativeUser;
+    re_add_imperative_user()
 
-    subtest "check whether declarative config overrides user bar", sub {
-      restartServer;
-      testSync "bar";
-    };
+    with subtest("check whether declarative config overrides user bar"):
+        restart_server()
+        test_sync("bar")
 
-    subtest "check manual configuration", sub {
-      # Remove the keys from automatic CA creation, to make sure the new
-      # generation doesn't use keys from before.
-      $server->succeed('rm -rf ${cfg.dataDir}/keys/* >&2');
-
-      $server->succeed('${switchToNewServer} >&2');
-      $server->waitForUnit("taskserver.service");
-      $server->waitForOpenPort(${portStr});
-
-      $server->succeed(
-        "nixos-taskserver org add manualOrg",
-        "nixos-taskserver user add manualOrg alice"
-      );
-
-      setupClientsFor "manualOrg", "alice", sub {
-        my ($client, $org, $user) = @_;
-        my $cfgpath = "/home/$user/.task";
-
-        $client->copyFileFromHost("${snakeOil.cacert}", "$cfgpath/ca.cert");
-        for my $file ('alice.key', 'alice.cert') {
-          $client->copyFileFromHost("${snakeOil}/$file", "$cfgpath/$file");
-        }
-
-        for my $file ("$user.key", "$user.cert") {
-          $client->copyFileFromHost(
-            "${snakeOil}/$file", "$cfgpath/$file"
-          );
-        }
-        $client->copyFileFromHost(
-          "${snakeOil.cacert}", "$cfgpath/ca.cert"
-        );
-        $client->succeed(
-          (su "alice", "task config taskd.ca $cfgpath/ca.cert"),
-          (su "alice", "task config taskd.key $cfgpath/$user.key"),
-          (su $user, "task config taskd.certificate $cfgpath/$user.cert")
-        );
-      };
 
-      testSync "alice";
-    };
+    def init_manual_config(client, org, user):
+        cfgpath = f"/home/{user}/.task"
+
+        client.copy_from_host(
+            "${snakeOil.cacert}",
+            f"{cfgpath}/ca.cert",
+        )
+        for file in ["alice.key", "alice.cert"]:
+            client.copy_from_host(
+                f"${snakeOil}/{file}",
+                f"{cfgpath}/{file}",
+            )
+
+        for file in [f"{user}.key", f"{user}.cert"]:
+            client.copy_from_host(
+                f"${snakeOil}/{file}",
+                f"{cfgpath}/{file}",
+            )
+
+        client.succeed(
+            su("alice", f"task config taskd.ca {cfgpath}/ca.cert"),
+            su("alice", f"task config taskd.key {cfgpath}/{user}.key"),
+            su(user, f"task config taskd.certificate {cfgpath}/{user}.cert"),
+        )
+
+
+    with subtest("check manual configuration"):
+        # Remove the keys from automatic CA creation, to make sure the new
+        # generation doesn't use keys from before.
+        server.succeed("rm -rf ${cfg.dataDir}/keys/* >&2")
+
+        server.succeed(
+            "${switchToNewServer} >&2"
+        )
+        server.wait_for_unit("taskserver.service")
+        server.wait_for_open_port(${portStr})
+
+        server.succeed(
+            "nixos-taskserver org add manualOrg",
+            "nixos-taskserver user add manualOrg alice",
+        )
+
+        setup_clients_for("manualOrg", "alice", init_manual_config)
+
+        test_sync("alice")
   '';
 })
diff --git a/nixos/tests/tiddlywiki.nix b/nixos/tests/tiddlywiki.nix
index cf45578b0f989..822711b8939c5 100644
--- a/nixos/tests/tiddlywiki.nix
+++ b/nixos/tests/tiddlywiki.nix
@@ -44,7 +44,7 @@ import ./make-test-python.nix ({ ... }: {
           configured.succeed(
               "curl --fail -o /dev/null 127.0.0.1:3000 --user somelogin:somesecret"
           )
-      
+
       with subtest("restart preserves changes"):
           # given running wiki
           default.wait_for_unit("tiddlywiki.service")
diff --git a/nixos/tests/transmission.nix b/nixos/tests/transmission.nix
index f4f2186be1fff..37c0352dcfb8f 100644
--- a/nixos/tests/transmission.nix
+++ b/nixos/tests/transmission.nix
@@ -9,6 +9,8 @@ import ./make-test-python.nix ({ pkgs, ...} : {
 
     networking.firewall.allowedTCPPorts = [ 9091 ];
 
+    security.apparmor.enable = true;
+
     services.transmission.enable = true;
   };
 
diff --git a/nixos/tests/trezord.nix b/nixos/tests/trezord.nix
index 8d908a5224923..b7b3dd31942bb 100644
--- a/nixos/tests/trezord.nix
+++ b/nixos/tests/trezord.nix
@@ -1,9 +1,8 @@
 import ./make-test-python.nix ({ pkgs, ... }: {
   name = "trezord";
-  meta = with pkgs.stdenv.lib.maintainers; {
-    maintainers = [ mmahut "1000101" ];
+  meta = with pkgs.stdenv.lib; {
+    maintainers = with maintainers; [ mmahut _1000101 ];
   };
-
   nodes = {
     machine = { ... }: {
       services.trezord.enable = true;
diff --git a/nixos/tests/trickster.nix b/nixos/tests/trickster.nix
index e2ca00980d539..713ac8f0b2fae 100644
--- a/nixos/tests/trickster.nix
+++ b/nixos/tests/trickster.nix
@@ -1,7 +1,7 @@
 import ./make-test-python.nix ({ pkgs, ... }: {
   name = "trickster";
-  meta = with pkgs.stdenv.lib.maintainers; {
-    maintainers = [ "1000101" ];
+  meta = with pkgs.stdenv.lib; {
+    maintainers = with maintainers; [ _1000101 ];
   };
 
   nodes = {
@@ -34,4 +34,4 @@ import ./make-test-python.nix ({ pkgs, ... }: {
         "curl -L http://localhost:9090/metrics | grep 'promhttp_metric_handler_requests_total{code=\"500\"} 0'"
     )
   '';
-})
\ No newline at end of file
+})
diff --git a/nixos/tests/virtualbox.nix b/nixos/tests/virtualbox.nix
index aec8da6a2af3c..af76e6f984424 100644
--- a/nixos/tests/virtualbox.nix
+++ b/nixos/tests/virtualbox.nix
@@ -198,7 +198,6 @@ let
       systemd.services."vboxtestlog-${name}@" = {
         description = "VirtualBox Test Machine Log For ${name}";
         serviceConfig.StandardInput = "socket";
-        serviceConfig.StandardOutput = "syslog";
         serviceConfig.SyslogIdentifier = "GUEST-${name}";
         serviceConfig.ExecStart = "${pkgs.coreutils}/bin/cat";
       };
diff --git a/nixos/tests/xandikos.nix b/nixos/tests/xandikos.nix
index 0fded20ff1a9c..48c770a3d168d 100644
--- a/nixos/tests/xandikos.nix
+++ b/nixos/tests/xandikos.nix
@@ -4,7 +4,7 @@ import ./make-test-python.nix (
     {
       name = "xandikos";
 
-      meta.maintainers = [ lib.maintainers."0x4A6F" ];
+      meta.maintainers = with lib.maintainers; [ _0x4A6F ];
 
       nodes = {
         xandikos_client = {};
@@ -17,7 +17,7 @@ import ./make-test-python.nix (
           services.xandikos.enable = true;
           services.xandikos.address = "localhost";
           services.xandikos.port = 8080;
-          services.xandikos.routePrefix = "/xandikos/";
+          services.xandikos.routePrefix = "/xandikos-prefix/";
           services.xandikos.extraOptions = [
             "--defaults"
           ];
@@ -28,7 +28,7 @@ import ./make-test-python.nix (
               serverName = "xandikos.local";
               basicAuth.xandikos = "snakeOilPassword";
               locations."/xandikos/" = {
-                proxyPass = "http://localhost:8080/";
+                proxyPass = "http://localhost:8080/xandikos-prefix/";
               };
             };
           };
diff --git a/nixos/tests/xfce.nix b/nixos/tests/xfce.nix
index 99065669661a0..99e30342e5937 100644
--- a/nixos/tests/xfce.nix
+++ b/nixos/tests/xfce.nix
@@ -11,8 +11,8 @@ import ./make-test-python.nix ({ pkgs, ...} : {
 
       services.xserver.enable = true;
 
-      services.xserver.displayManager.lightdm = {
-        enable = true;
+      services.xserver.displayManager = {
+        lightdm.enable = true;
         autoLogin = {
           enable = true;
           user = "alice";
diff --git a/nixos/tests/yggdrasil.nix b/nixos/tests/yggdrasil.nix
index 9ceb79747339e..1d7541308b481 100644
--- a/nixos/tests/yggdrasil.nix
+++ b/nixos/tests/yggdrasil.nix
@@ -7,6 +7,7 @@ let
     SigningPrivateKey = "fe3add8da35316c05f6d90d3ca79bd2801e6ccab6d37e5339fef4152589398abe2c43349083bc1e998e4ec4535b4c6a8f44ca9a5a8e07336561267253b2be5f4";
   };
   bobIp6 = "201:ebbd:bde9:f138:c302:4afa:1fb6:a19a";
+  bobPrefix = "301:ebbd:bde9:f138";
   bobConfig = {
     InterfacePeers = {
       eth1 = [ "tcp://192.168.1.200:12345" ];
@@ -18,6 +19,7 @@ let
     SigningPublicKey = "de111da0ec781e45bf6c63ecb45a78c24d7d4655abfaeea83b26c36eb5c0fd5b";
     SigningPrivateKey = "2a6c21550f3fca0331df50668ffab66b6dce8237bcd5728e571e8033b363e247de111da0ec781e45bf6c63ecb45a78c24d7d4655abfaeea83b26c36eb5c0fd5b";
   };
+  danIp6 = bobPrefix + "::2";
 
 in import ./make-test-python.nix ({ pkgs, ...} : {
   name = "yggdrasil";
@@ -69,6 +71,41 @@ in import ./make-test-python.nix ({ pkgs, ...} : {
                          text = builtins.toJSON bobConfig;
                        });
         };
+
+        boot.kernel.sysctl."net.ipv6.conf.all.forwarding" = 1;
+
+        networking = {
+          bridges.br0.interfaces = [ ];
+          interfaces.br0 = {
+            ipv6.addresses = [{
+              address = bobPrefix + "::1";
+              prefixLength = 64;
+            }];
+          };
+        };
+
+        # dan is a node inside a container running on bob's host.
+        containers.dan = {
+          autoStart = true;
+          privateNetwork = true;
+          hostBridge = "br0";
+          config = { config, pkgs, ... }: {
+            networking.interfaces.eth0.ipv6 = {
+              addresses = [{
+                address = bobPrefix + "::2";
+                prefixLength = 64;
+              }];
+              routes = [{
+                address = "200::";
+                prefixLength = 7;
+                via = bobPrefix + "::1";
+              }];
+            };
+            services.httpd.enable = true;
+            services.httpd.adminAddr = "foo@example.org";
+            networking.firewall.allowedTCPPorts = [ 80 ];
+          };
+        };
       };
 
     # Carol only does local peering.  Carol's yggdrasil config is all Nix.
@@ -100,7 +137,7 @@ in import ./make-test-python.nix ({ pkgs, ...} : {
 
       bob.start()
       carol.start()
-      bob.wait_for_unit("yggdrasil.service")
+      bob.wait_for_unit("default.target")
       carol.wait_for_unit("yggdrasil.service")
 
       ip_addr_show = "ip -o -6 addr show dev ygg0 scope global"
@@ -117,10 +154,13 @@ in import ./make-test-python.nix ({ pkgs, ...} : {
 
       carol.succeed("ping -c 1 ${aliceIp6}")
       carol.succeed("ping -c 1 ${bobIp6}")
+      carol.succeed("ping -c 1 ${bobPrefix}::1")
+      carol.succeed("ping -c 8 ${danIp6}")
 
       carol.fail("journalctl -u dhcpcd | grep ygg0")
 
       alice.wait_for_unit("httpd.service")
       carol.succeed("curl --fail -g http://[${aliceIp6}]")
+      carol.succeed("curl --fail -g http://[${danIp6}]")
     '';
 })
diff --git a/nixos/tests/zfs.nix b/nixos/tests/zfs.nix
index 7ba60ee9806c5..87e6c900c98e0 100644
--- a/nixos/tests/zfs.nix
+++ b/nixos/tests/zfs.nix
@@ -46,6 +46,17 @@ let
             "zpool destroy rpool",
             "udevadm settle",
         )
+
+        machine.succeed(
+            'echo password | zpool create -o altroot="/tmp/mnt" '
+            + "-O encryption=aes-256-gcm -O keyformat=passphrase rpool /dev/vdb1",
+            "zfs create -o mountpoint=legacy rpool/root",
+            "mount -t zfs rpool/root /tmp/mnt",
+            "udevadm settle",
+            "umount /tmp/mnt",
+            "zpool destroy rpool",
+            "udevadm settle",
+        )
       '' + extraTest;
 
     };
@@ -57,18 +68,6 @@ in {
 
   unstable = makeZfsTest "unstable" {
     enableUnstable = true;
-    extraTest = ''
-      machine.succeed(
-          'echo password | zpool create -o altroot="/tmp/mnt" '
-          + "-O encryption=aes-256-gcm -O keyformat=passphrase rpool /dev/vdb1",
-          "zfs create -o mountpoint=legacy rpool/root",
-          "mount -t zfs rpool/root /tmp/mnt",
-          "udevadm settle",
-          "umount /tmp/mnt",
-          "zpool destroy rpool",
-          "udevadm settle",
-      )
-    '';
   };
 
   installer = (import ./installer.nix { }).zfsroot;
diff --git a/nixos/tests/zigbee2mqtt.nix b/nixos/tests/zigbee2mqtt.nix
new file mode 100644
index 0000000000000..b7bb21f9227af
--- /dev/null
+++ b/nixos/tests/zigbee2mqtt.nix
@@ -0,0 +1,19 @@
+import ./make-test-python.nix ({ pkgs, ... }:
+
+  {
+    machine = { pkgs, ... }:
+      {
+        services.zigbee2mqtt = {
+          enable = true;
+        };
+      };
+
+    testScript = ''
+      machine.wait_for_unit("zigbee2mqtt.service")
+      machine.wait_until_fails("systemctl status zigbee2mqtt.service")
+      machine.succeed(
+          "journalctl -eu zigbee2mqtt | grep \"Error: Error while opening serialport 'Error: Error: No such file or directory, cannot open /dev/ttyACM0'\""
+      )
+    '';
+  }
+)